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 | |
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 '')
240 files changed, 61140 insertions, 0 deletions
diff --git a/xbmc/interfaces/AnnouncementManager.cpp b/xbmc/interfaces/AnnouncementManager.cpp new file mode 100644 index 0000000..2f5101b --- /dev/null +++ b/xbmc/interfaces/AnnouncementManager.cpp @@ -0,0 +1,342 @@ +/* + * 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 "AnnouncementManager.h" + +#include "FileItem.h" +#include "music/MusicDatabase.h" +#include "music/tags/MusicInfoTag.h" +#include "playlists/PlayListTypes.h" +#include "pvr/channels/PVRChannel.h" +#include "threads/SingleLock.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" + +#include <mutex> +#include <stdio.h> + +#define LOOKUP_PROPERTY "database-lookup" + +using namespace ANNOUNCEMENT; + +const std::string CAnnouncementManager::ANNOUNCEMENT_SENDER = "xbmc"; + +CAnnouncementManager::CAnnouncementManager() : CThread("Announce") +{ +} + +CAnnouncementManager::~CAnnouncementManager() +{ + Deinitialize(); +} + +void CAnnouncementManager::Start() +{ + Create(); +} + +void CAnnouncementManager::Deinitialize() +{ + m_bStop = true; + m_queueEvent.Set(); + StopThread(); + std::unique_lock<CCriticalSection> lock(m_announcersCritSection); + m_announcers.clear(); +} + +void CAnnouncementManager::AddAnnouncer(IAnnouncer *listener) +{ + if (!listener) + return; + + std::unique_lock<CCriticalSection> lock(m_announcersCritSection); + m_announcers.push_back(listener); +} + +void CAnnouncementManager::RemoveAnnouncer(IAnnouncer *listener) +{ + if (!listener) + return; + + std::unique_lock<CCriticalSection> lock(m_announcersCritSection); + for (unsigned int i = 0; i < m_announcers.size(); i++) + { + if (m_announcers[i] == listener) + { + m_announcers.erase(m_announcers.begin() + i); + return; + } + } +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, const std::string& message) +{ + CVariant data; + Announce(flag, ANNOUNCEMENT_SENDER, message, CFileItemPtr(), data); +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& message, + const CVariant& data) +{ + Announce(flag, ANNOUNCEMENT_SENDER, message, CFileItemPtr(), data); +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& message, + const std::shared_ptr<const CFileItem>& item) +{ + CVariant data; + Announce(flag, ANNOUNCEMENT_SENDER, message, item, data); +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& message, + const std::shared_ptr<const CFileItem>& item, + const CVariant& data) +{ + Announce(flag, ANNOUNCEMENT_SENDER, message, item, data); +} + + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message) +{ + CVariant data; + Announce(flag, sender, message, CFileItemPtr(), data); +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + Announce(flag, sender, message, CFileItemPtr(), data); +} + +void CAnnouncementManager::Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const std::shared_ptr<const CFileItem>& item, + const CVariant& data) +{ + CAnnounceData announcement; + announcement.flag = flag; + announcement.sender = sender; + announcement.message = message; + announcement.data = data; + + if (item != nullptr) + announcement.item = CFileItemPtr(new CFileItem(*item)); + + { + std::unique_lock<CCriticalSection> lock(m_queueCritSection); + m_announcementQueue.push_back(announcement); + } + m_queueEvent.Set(); +} + +void CAnnouncementManager::DoAnnounce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + CLog::Log(LOGDEBUG, LOGANNOUNCE, "CAnnouncementManager - Announcement: {} from {}", message, sender); + + std::unique_lock<CCriticalSection> lock(m_announcersCritSection); + + // Make a copy of announcers. They may be removed or even remove themselves during execution of IAnnouncer::Announce()! + + std::vector<IAnnouncer *> announcers(m_announcers); + for (unsigned int i = 0; i < announcers.size(); i++) + announcers[i]->Announce(flag, sender, message, data); +} + +void CAnnouncementManager::DoAnnounce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const std::shared_ptr<CFileItem>& item, + const CVariant& data) +{ + if (item == nullptr) + { + DoAnnounce(flag, sender, message, data); + return; + } + + // Extract db id of item + CVariant object = data.isNull() || data.isObject() ? data : CVariant::VariantTypeObject; + std::string type; + int id = 0; + + if(item->HasPVRChannelInfoTag()) + { + const std::shared_ptr<PVR::CPVRChannel> channel(item->GetPVRChannelInfoTag()); + id = channel->ChannelID(); + type = "channel"; + + object["item"]["title"] = channel->ChannelName(); + object["item"]["channeltype"] = channel->IsRadio() ? "radio" : "tv"; + + if (data.isMember("player") && data["player"].isMember("playerid")) + { + object["player"]["playerid"] = + channel->IsRadio() ? PLAYLIST::TYPE_MUSIC : PLAYLIST::TYPE_VIDEO; + } + } + else if (item->HasVideoInfoTag() && !item->HasPVRRecordingInfoTag()) + { + id = item->GetVideoInfoTag()->m_iDbId; + + //! @todo Can be removed once this is properly handled when starting playback of a file + if (id <= 0 && !item->GetPath().empty() && + (!item->HasProperty(LOOKUP_PROPERTY) || item->GetProperty(LOOKUP_PROPERTY).asBoolean())) + { + CVideoDatabase videodatabase; + if (videodatabase.Open()) + { + std::string path = item->GetPath(); + std::string videoInfoTagPath(item->GetVideoInfoTag()->m_strFileNameAndPath); + if (StringUtils::StartsWith(videoInfoTagPath, "removable://")) + path = videoInfoTagPath; + if (videodatabase.LoadVideoInfo(path, *item->GetVideoInfoTag(), VideoDbDetailsNone)) + id = item->GetVideoInfoTag()->m_iDbId; + + videodatabase.Close(); + } + } + + if (!item->GetVideoInfoTag()->m_type.empty()) + type = item->GetVideoInfoTag()->m_type; + else + CVideoDatabase::VideoContentTypeToString(item->GetVideoContentType(), type); + + if (id <= 0) + { + //! @todo Can be removed once this is properly handled when starting playback of a file + item->SetProperty(LOOKUP_PROPERTY, false); + + std::string title = item->GetVideoInfoTag()->m_strTitle; + if (title.empty()) + title = item->GetLabel(); + object["item"]["title"] = title; + + switch (item->GetVideoContentType()) + { + case VideoDbContentType::MOVIES: + if (item->GetVideoInfoTag()->HasYear()) + object["item"]["year"] = item->GetVideoInfoTag()->GetYear(); + break; + case VideoDbContentType::EPISODES: + if (item->GetVideoInfoTag()->m_iEpisode >= 0) + object["item"]["episode"] = item->GetVideoInfoTag()->m_iEpisode; + if (item->GetVideoInfoTag()->m_iSeason >= 0) + object["item"]["season"] = item->GetVideoInfoTag()->m_iSeason; + if (!item->GetVideoInfoTag()->m_strShowTitle.empty()) + object["item"]["showtitle"] = item->GetVideoInfoTag()->m_strShowTitle; + break; + case VideoDbContentType::MUSICVIDEOS: + if (!item->GetVideoInfoTag()->m_strAlbum.empty()) + object["item"]["album"] = item->GetVideoInfoTag()->m_strAlbum; + if (!item->GetVideoInfoTag()->m_artist.empty()) + object["item"]["artist"] = StringUtils::Join(item->GetVideoInfoTag()->m_artist, " / "); + break; + default: + break; + } + } + } + else if (item->HasMusicInfoTag()) + { + id = item->GetMusicInfoTag()->GetDatabaseId(); + type = MediaTypeSong; + + //! @todo Can be removed once this is properly handled when starting playback of a file + if (id <= 0 && !item->GetPath().empty() && + (!item->HasProperty(LOOKUP_PROPERTY) || item->GetProperty(LOOKUP_PROPERTY).asBoolean())) + { + CMusicDatabase musicdatabase; + if (musicdatabase.Open()) + { + CSong song; + if (musicdatabase.GetSongByFileName(item->GetPath(), song, item->GetStartOffset())) + { + item->GetMusicInfoTag()->SetSong(song); + id = item->GetMusicInfoTag()->GetDatabaseId(); + } + + musicdatabase.Close(); + } + } + + if (id <= 0) + { + //! @todo Can be removed once this is properly handled when starting playback of a file + item->SetProperty(LOOKUP_PROPERTY, false); + + std::string title = item->GetMusicInfoTag()->GetTitle(); + if (title.empty()) + title = item->GetLabel(); + object["item"]["title"] = title; + + if (item->GetMusicInfoTag()->GetTrackNumber() > 0) + object["item"]["track"] = item->GetMusicInfoTag()->GetTrackNumber(); + if (!item->GetMusicInfoTag()->GetAlbum().empty()) + object["item"]["album"] = item->GetMusicInfoTag()->GetAlbum(); + if (!item->GetMusicInfoTag()->GetArtist().empty()) + object["item"]["artist"] = item->GetMusicInfoTag()->GetArtist(); + } + } + else if (item->IsVideo()) + { + // video item but has no video info tag. + type = "movie"; + object["item"]["title"] = item->GetLabel(); + } + else if (item->HasPictureInfoTag()) + { + type = "picture"; + object["item"]["file"] = item->GetPath(); + } + else + type = "unknown"; + + object["item"]["type"] = type; + if (id > 0) + object["item"]["id"] = id; + + DoAnnounce(flag, sender, message, object); +} + +void CAnnouncementManager::Process() +{ + SetPriority(ThreadPriority::LOWEST); + + while (!m_bStop) + { + std::unique_lock<CCriticalSection> lock(m_queueCritSection); + if (!m_announcementQueue.empty()) + { + auto announcement = m_announcementQueue.front(); + m_announcementQueue.pop_front(); + { + CSingleExit ex(m_queueCritSection); + DoAnnounce(announcement.flag, announcement.sender, announcement.message, announcement.item, + announcement.data); + } + } + else + { + CSingleExit ex(m_queueCritSection); + m_queueEvent.Wait(); + } + } +} diff --git a/xbmc/interfaces/AnnouncementManager.h b/xbmc/interfaces/AnnouncementManager.h new file mode 100644 index 0000000..44b6d97 --- /dev/null +++ b/xbmc/interfaces/AnnouncementManager.h @@ -0,0 +1,95 @@ +/* + * 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 "IAnnouncer.h" +#include "threads/CriticalSection.h" +#include "threads/Event.h" +#include "threads/Thread.h" +#include "utils/Variant.h" + +#include <list> +#include <memory> +#include <vector> + +class CFileItem; +class CVariant; + +namespace ANNOUNCEMENT +{ + class CAnnouncementManager : public CThread + { + public: + CAnnouncementManager(); + ~CAnnouncementManager() override; + + void Start(); + void Deinitialize(); + + void AddAnnouncer(IAnnouncer *listener); + void RemoveAnnouncer(IAnnouncer *listener); + + void Announce(AnnouncementFlag flag, const std::string& message); + void Announce(AnnouncementFlag flag, const std::string& message, const CVariant& data); + void Announce(AnnouncementFlag flag, + const std::string& message, + const std::shared_ptr<const CFileItem>& item); + void Announce(AnnouncementFlag flag, + const std::string& message, + const std::shared_ptr<const CFileItem>& item, + const CVariant& data); + + void Announce(AnnouncementFlag flag, const std::string& sender, const std::string& message); + void Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data); + void Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const std::shared_ptr<const CFileItem>& item, + const CVariant& data); + + // The sender is not related to the application name. + // Also it's part of Kodi's API - changing it will break + // a big number of python addons and third party json consumers. + static const std::string ANNOUNCEMENT_SENDER; + + protected: + void Process() override; + void DoAnnounce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const std::shared_ptr<CFileItem>& item, + const CVariant& data); + void DoAnnounce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data); + + struct CAnnounceData + { + AnnouncementFlag flag; + std::string sender; + std::string message; + std::shared_ptr<CFileItem> item; + CVariant data; + }; + std::list<CAnnounceData> m_announcementQueue; + CEvent m_queueEvent; + + private: + CAnnouncementManager(const CAnnouncementManager&) = delete; + CAnnouncementManager const& operator=(CAnnouncementManager const&) = delete; + + CCriticalSection m_announcersCritSection; + CCriticalSection m_queueCritSection; + std::vector<IAnnouncer *> m_announcers; + }; +} diff --git a/xbmc/interfaces/CMakeLists.txt b/xbmc/interfaces/CMakeLists.txt new file mode 100644 index 0000000..0b98732 --- /dev/null +++ b/xbmc/interfaces/CMakeLists.txt @@ -0,0 +1,7 @@ +set(SOURCES AnnouncementManager.cpp) + +set(HEADERS AnnouncementManager.h + IActionListener.h + IAnnouncer.h) + +core_add_library(interfaces) diff --git a/xbmc/interfaces/IActionListener.h b/xbmc/interfaces/IActionListener.h new file mode 100644 index 0000000..0419034 --- /dev/null +++ b/xbmc/interfaces/IActionListener.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 + +class CAction; + +class IActionListener +{ +public: + virtual ~IActionListener() = default; + + virtual bool OnAction(const CAction &action) = 0; +}; diff --git a/xbmc/interfaces/IAnnouncer.h b/xbmc/interfaces/IAnnouncer.h new file mode 100644 index 0000000..7c20203 --- /dev/null +++ b/xbmc/interfaces/IAnnouncer.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CVariant; +namespace ANNOUNCEMENT +{ + enum AnnouncementFlag + { + Player = 0x001, + Playlist = 0x002, + GUI = 0x004, + System = 0x008, + VideoLibrary = 0x010, + AudioLibrary = 0x020, + Application = 0x040, + Input = 0x080, + PVR = 0x100, + Other = 0x200, + Info = 0x400 + }; + + const auto ANNOUNCE_ALL = (Player | Playlist | GUI | System | VideoLibrary | AudioLibrary | Application | Input | ANNOUNCEMENT::PVR | Other); + + /*! + \brief Returns a string representation for the + given AnnouncementFlag + \param notification Specific AnnouncementFlag + \return String representation of the given AnnouncementFlag + */ + inline const char *AnnouncementFlagToString(const AnnouncementFlag ¬ification) + { + switch (notification) + { + case Player: + return "Player"; + case Playlist: + return "Playlist"; + case GUI: + return "GUI"; + case System: + return "System"; + case VideoLibrary: + return "VideoLibrary"; + case AudioLibrary: + return "AudioLibrary"; + case Application: + return "Application"; + case Input: + return "Input"; + case PVR: + return "PVR"; + case Other: + return "Other"; + case Info: + return "Info"; + default: + return "Unknown"; + } + } + + class IAnnouncer + { + public: + IAnnouncer() = default; + virtual ~IAnnouncer() = default; + virtual void Announce(AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) = 0; + }; +} 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; +}; diff --git a/xbmc/interfaces/generic/CMakeLists.txt b/xbmc/interfaces/generic/CMakeLists.txt new file mode 100644 index 0000000..8fd742d --- /dev/null +++ b/xbmc/interfaces/generic/CMakeLists.txt @@ -0,0 +1,15 @@ +set(SOURCES ILanguageInvoker.cpp + LanguageInvokerThread.cpp + RunningScriptObserver.cpp + ScriptInvocationManager.cpp + ScriptRunner.cpp) + +set(HEADERS ILanguageInvocationHandler.h + ILanguageInvoker.h + LanguageInvokerThread.h + RunningScriptsHandler.h + RunningScriptObserver.h + ScriptInvocationManager.h + ScriptRunner.h) + +core_add_library(generic_interface) diff --git a/xbmc/interfaces/generic/ILanguageInvocationHandler.h b/xbmc/interfaces/generic/ILanguageInvocationHandler.h new file mode 100644 index 0000000..233e20d --- /dev/null +++ b/xbmc/interfaces/generic/ILanguageInvocationHandler.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +class ILanguageInvoker; + +class ILanguageInvocationHandler +{ +public: + ILanguageInvocationHandler() = default; + virtual ~ILanguageInvocationHandler() = default; + + virtual bool Initialize() { return true; } + virtual void Process() { } + virtual void PulseGlobalEvent() { } + virtual void Uninitialize() { } + + virtual bool OnScriptInitialized(ILanguageInvoker *invoker) { return true; } + virtual void OnScriptStarted(ILanguageInvoker *invoker) { } + virtual void NotifyScriptAborting(ILanguageInvoker *invoker) { } + virtual void OnExecutionEnded(ILanguageInvoker* invoker) {} + virtual void OnScriptFinalized(ILanguageInvoker *invoker) { } + + virtual ILanguageInvoker* CreateInvoker() = 0; +}; diff --git a/xbmc/interfaces/generic/ILanguageInvoker.cpp b/xbmc/interfaces/generic/ILanguageInvoker.cpp new file mode 100644 index 0000000..df1193e --- /dev/null +++ b/xbmc/interfaces/generic/ILanguageInvoker.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ILanguageInvoker.h" + +#include "interfaces/generic/ILanguageInvocationHandler.h" + +#include <string> +#include <vector> + +ILanguageInvoker::ILanguageInvoker(ILanguageInvocationHandler *invocationHandler) + : m_id(-1), + m_state(InvokerStateUninitialized), + m_invocationHandler(invocationHandler) +{ } + +ILanguageInvoker::~ILanguageInvoker() = default; + +bool ILanguageInvoker::Execute(const std::string &script, const std::vector<std::string> &arguments /* = std::vector<std::string>() */) +{ + if (m_invocationHandler) + m_invocationHandler->OnScriptStarted(this); + + return execute(script, arguments); +} + +bool ILanguageInvoker::Stop(bool abort /* = false */) +{ + return stop(abort); +} + +bool ILanguageInvoker::IsActive() const +{ + return GetState() > InvokerStateUninitialized && GetState() < InvokerStateScriptDone; +} + +bool ILanguageInvoker::IsRunning() const +{ + return GetState() == InvokerStateRunning; +} + +bool ILanguageInvoker::IsStopping() const +{ + return GetState() == InvokerStateStopping; +} + +void ILanguageInvoker::pulseGlobalEvent() +{ + if (m_invocationHandler) + m_invocationHandler->PulseGlobalEvent(); +} + +bool ILanguageInvoker::onExecutionInitialized() +{ + if (m_invocationHandler == NULL) + return false; + + return m_invocationHandler->OnScriptInitialized(this); +} + +void ILanguageInvoker::AbortNotification() +{ + if (m_invocationHandler) + m_invocationHandler->NotifyScriptAborting(this); +} + +void ILanguageInvoker::onExecutionFailed() +{ + if (m_invocationHandler) + m_invocationHandler->OnExecutionEnded(this); +} + +void ILanguageInvoker::onExecutionDone() +{ + if (m_invocationHandler) + m_invocationHandler->OnExecutionEnded(this); +} + +void ILanguageInvoker::onExecutionFinalized() +{ + if (m_invocationHandler) + m_invocationHandler->OnScriptFinalized(this); +} + +void ILanguageInvoker::setState(InvokerState state) +{ + if (state <= m_state) + return; + + m_state = state; +} diff --git a/xbmc/interfaces/generic/ILanguageInvoker.h b/xbmc/interfaces/generic/ILanguageInvoker.h new file mode 100644 index 0000000..da4001e --- /dev/null +++ b/xbmc/interfaces/generic/ILanguageInvoker.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/IAddon.h" + +#include <memory> +#include <string> +#include <vector> + +class CLanguageInvokerThread; +class ILanguageInvocationHandler; + +typedef enum +{ + InvokerStateUninitialized, + InvokerStateInitialized, + InvokerStateRunning, + InvokerStateStopping, + InvokerStateScriptDone, + InvokerStateExecutionDone, + InvokerStateFailed +} InvokerState; + +class ILanguageInvoker +{ +public: + explicit ILanguageInvoker(ILanguageInvocationHandler *invocationHandler); + virtual ~ILanguageInvoker(); + + virtual bool Execute(const std::string &script, const std::vector<std::string> &arguments = std::vector<std::string>()); + virtual bool Stop(bool abort = false); + virtual bool IsStopping() const; + + void SetId(int id) { m_id = id; } + int GetId() const { return m_id; } + const ADDON::AddonPtr& GetAddon() const { return m_addon; } + void SetAddon(const ADDON::AddonPtr &addon) { m_addon = addon; } + InvokerState GetState() const { return m_state; } + bool IsActive() const; + bool IsRunning() const; + void Reset() { m_state = InvokerStateUninitialized; } + +protected: + friend class CLanguageInvokerThread; + + /** + * Called to notify the script is aborting. + */ + virtual void AbortNotification(); + + virtual bool execute(const std::string &script, const std::vector<std::string> &arguments) = 0; + virtual bool stop(bool abort) = 0; + + virtual void pulseGlobalEvent(); + virtual bool onExecutionInitialized(); + virtual void onExecutionFailed(); + virtual void onExecutionDone(); + virtual void onExecutionFinalized(); + + void setState(InvokerState state); + + ADDON::AddonPtr m_addon; + +private: + int m_id; + InvokerState m_state; + ILanguageInvocationHandler *m_invocationHandler; +}; + +typedef std::shared_ptr<ILanguageInvoker> LanguageInvokerPtr; diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.cpp b/xbmc/interfaces/generic/LanguageInvokerThread.cpp new file mode 100644 index 0000000..67f3d14 --- /dev/null +++ b/xbmc/interfaces/generic/LanguageInvokerThread.cpp @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "LanguageInvokerThread.h" + +#include "ScriptInvocationManager.h" + +#include <utility> + +CLanguageInvokerThread::CLanguageInvokerThread(LanguageInvokerPtr invoker, + CScriptInvocationManager* invocationManager, + bool reuseable) + : ILanguageInvoker(NULL), + CThread("LanguageInvoker"), + m_invoker(std::move(invoker)), + m_invocationManager(invocationManager), + m_reusable(reuseable) +{ } + +CLanguageInvokerThread::~CLanguageInvokerThread() +{ + Stop(true); +} + +InvokerState CLanguageInvokerThread::GetState() const +{ + if (m_invoker == NULL) + return InvokerStateFailed; + + return m_invoker->GetState(); +} + +void CLanguageInvokerThread::Release() +{ + m_bStop = true; + m_condition.notify_one(); +} + +bool CLanguageInvokerThread::execute(const std::string &script, const std::vector<std::string> &arguments) +{ + if (m_invoker == NULL || script.empty()) + return false; + + m_script = script; + m_args = arguments; + + if (CThread::IsRunning()) + { + std::unique_lock<std::mutex> lck(m_mutex); + m_restart = true; + m_condition.notify_one(); + } + else + Create(); + + //Todo wait until running + + return true; +} + +bool CLanguageInvokerThread::stop(bool wait) +{ + if (m_invoker == NULL) + return false; + + if (!CThread::IsRunning()) + return false; + + Release(); + + bool result = true; + if (m_invoker->GetState() < InvokerStateExecutionDone) + { + // stop the language-specific invoker + result = m_invoker->Stop(wait); + } + // stop the thread + CThread::StopThread(wait); + + return result; +} + +void CLanguageInvokerThread::OnStartup() +{ + if (m_invoker == NULL) + return; + + m_invoker->SetId(GetId()); + if (m_addon != NULL) + m_invoker->SetAddon(m_addon); +} + +void CLanguageInvokerThread::Process() +{ + if (m_invoker == NULL) + return; + + std::unique_lock<std::mutex> lckdl(m_mutex); + do + { + m_restart = false; + m_invoker->Execute(m_script, m_args); + + if (m_invoker->GetState() != InvokerStateScriptDone) + m_reusable = false; + + m_condition.wait(lckdl, [this] { return m_bStop || m_restart || !m_reusable; }); + + } while (m_reusable && !m_bStop); +} + +void CLanguageInvokerThread::OnExit() +{ + if (m_invoker == NULL) + return; + + m_invoker->onExecutionDone(); + m_invocationManager->OnExecutionDone(GetId()); +} + +void CLanguageInvokerThread::OnException() +{ + if (m_invoker == NULL) + return; + + m_invoker->onExecutionFailed(); + m_invocationManager->OnExecutionDone(GetId()); +}
\ No newline at end of file diff --git a/xbmc/interfaces/generic/LanguageInvokerThread.h b/xbmc/interfaces/generic/LanguageInvokerThread.h new file mode 100644 index 0000000..a1844ba --- /dev/null +++ b/xbmc/interfaces/generic/LanguageInvokerThread.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/generic/ILanguageInvoker.h" +#include "threads/Thread.h" + +#include <string> +#include <vector> + +class CScriptInvocationManager; + +class CLanguageInvokerThread : public ILanguageInvoker, protected CThread +{ +public: + CLanguageInvokerThread(LanguageInvokerPtr invoker, + CScriptInvocationManager* invocationManager, + bool reuseable); + ~CLanguageInvokerThread() override; + + virtual InvokerState GetState() const; + + const std::string& GetScript() const { return m_script; } + LanguageInvokerPtr GetInvoker() const { return m_invoker; } + bool Reuseable(const std::string& script) const + { + return !m_bStop && m_reusable && GetState() == InvokerStateScriptDone && m_script == script; + }; + virtual void Release(); + +protected: + bool execute(const std::string &script, const std::vector<std::string> &arguments) override; + bool stop(bool wait) override; + + void OnStartup() override; + void Process() override; + void OnExit() override; + void OnException() override; + +private: + LanguageInvokerPtr m_invoker; + CScriptInvocationManager *m_invocationManager; + std::string m_script; + std::vector<std::string> m_args; + + std::mutex m_mutex; + std::condition_variable m_condition; + bool m_restart = false; + bool m_reusable = false; +}; diff --git a/xbmc/interfaces/generic/RunningScriptObserver.cpp b/xbmc/interfaces/generic/RunningScriptObserver.cpp new file mode 100644 index 0000000..c89c9f7 --- /dev/null +++ b/xbmc/interfaces/generic/RunningScriptObserver.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "RunningScriptObserver.h" + +#include "interfaces/generic/ScriptInvocationManager.h" + +using namespace std::chrono_literals; + +CRunningScriptObserver::CRunningScriptObserver(int scriptId, CEvent& evt) + : m_scriptId(scriptId), m_event(evt), m_stopEvent(true), m_thread(this, "ScriptObs") +{ + m_thread.Create(); +} + +CRunningScriptObserver::~CRunningScriptObserver() +{ + Abort(); +} + +void CRunningScriptObserver::Run() +{ + do + { + if (!CScriptInvocationManager::GetInstance().IsRunning(m_scriptId)) + { + m_event.Set(); + break; + } + } while (!m_stopEvent.Wait(20ms)); +} + +void CRunningScriptObserver::Abort() +{ + m_stopEvent.Set(); + m_thread.StopThread(); +} diff --git a/xbmc/interfaces/generic/RunningScriptObserver.h b/xbmc/interfaces/generic/RunningScriptObserver.h new file mode 100644 index 0000000..2622411 --- /dev/null +++ b/xbmc/interfaces/generic/RunningScriptObserver.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "threads/Event.h" +#include "threads/IRunnable.h" +#include "threads/Thread.h" + +#include <atomic> + +class CRunningScriptObserver : public IRunnable +{ +public: + CRunningScriptObserver(int scriptId, CEvent& evt); + ~CRunningScriptObserver(); + + void Abort(); + +protected: + // implementation of IRunnable + void Run() override; + + int m_scriptId; + CEvent& m_event; + + CEvent m_stopEvent; + CThread m_thread; +}; diff --git a/xbmc/interfaces/generic/RunningScriptsHandler.h b/xbmc/interfaces/generic/RunningScriptsHandler.h new file mode 100644 index 0000000..59f18bb --- /dev/null +++ b/xbmc/interfaces/generic/RunningScriptsHandler.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/generic/ScriptInvocationManager.h" +#include "interfaces/generic/ScriptRunner.h" +#include "threads/CriticalSection.h" +#include "threads/Event.h" + +#include <cstdint> +#include <map> +#include <mutex> + +template<class TScript> +class CRunningScriptsHandler : protected CScriptRunner +{ +protected: + using HandleType = int; + + CRunningScriptsHandler() = default; + virtual ~CRunningScriptsHandler() = default; + + using CScriptRunner::ExecuteScript; + using CScriptRunner::GetAddon; + using CScriptRunner::SetDone; + using CScriptRunner::StartScript; + + bool RunScript(TScript* script, + const ADDON::AddonPtr& addon, + const std::string& path, + bool resume) + { + if (script == nullptr || addon == nullptr || path.empty()) + return false; + + // reuse an existing script handle or get a new one if necessary + int handle = CScriptInvocationManager::GetInstance().GetReusablePluginHandle(addon->LibPath()); + if (handle < 0) + handle = GetNewScriptHandle(script); + else + ReuseScriptHandle(handle, script); + + // run the script + auto result = CScriptRunner::RunScript(addon, path, handle, resume); + + // remove the script handle if necessary + RemoveScriptHandle(handle); + + return result; + } + + static HandleType GetNewScriptHandle(TScript* script) + { + std::unique_lock<CCriticalSection> lock(s_critical); + uint32_t handle = ++s_scriptHandleCounter; + s_scriptHandles[handle] = script; + + return handle; + } + + static void ReuseScriptHandle(HandleType handle, TScript* script) + { + std::unique_lock<CCriticalSection> lock(s_critical); + s_scriptHandles[handle] = script; + } + + static void RemoveScriptHandle(HandleType handle) + { + std::unique_lock<CCriticalSection> lock(s_critical); + s_scriptHandles.erase(handle); + } + + static TScript* GetScriptFromHandle(HandleType handle) + { + std::unique_lock<CCriticalSection> lock(s_critical); + auto scriptHandle = s_scriptHandles.find(handle); + if (scriptHandle == s_scriptHandles.end()) + return nullptr; + + return scriptHandle->second; + } + + static inline CCriticalSection& GetScriptsLock() { return s_critical; } + +private: + static std::map<HandleType, TScript*> s_scriptHandles; + static CCriticalSection s_critical; + static HandleType s_scriptHandleCounter; +}; + +template<class TScript> +std::map<typename CRunningScriptsHandler<TScript>::HandleType, TScript*> + CRunningScriptsHandler<TScript>::s_scriptHandles; +template<class TScript> +CCriticalSection CRunningScriptsHandler<TScript>::s_critical; +template<class TScript> +typename CRunningScriptsHandler<TScript>::HandleType + CRunningScriptsHandler<TScript>::s_scriptHandleCounter = 0; diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.cpp b/xbmc/interfaces/generic/ScriptInvocationManager.cpp new file mode 100644 index 0000000..9daae0f --- /dev/null +++ b/xbmc/interfaces/generic/ScriptInvocationManager.cpp @@ -0,0 +1,423 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ScriptInvocationManager.h" + +#include "interfaces/generic/ILanguageInvocationHandler.h" +#include "interfaces/generic/ILanguageInvoker.h" +#include "interfaces/generic/LanguageInvokerThread.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include <cerrno> +#include <mutex> +#include <utility> +#include <vector> + +CScriptInvocationManager::~CScriptInvocationManager() +{ + Uninitialize(); +} + +CScriptInvocationManager& CScriptInvocationManager::GetInstance() +{ + static CScriptInvocationManager s_instance; + return s_instance; +} + +void CScriptInvocationManager::Process() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + // go through all active threads and find and remove all which are done + std::vector<LanguageInvokerThread> tempList; + for (LanguageInvokerThreadMap::iterator it = m_scripts.begin(); it != m_scripts.end(); ) + { + if (it->second.done) + { + tempList.push_back(it->second); + m_scripts.erase(it++); + } + else + ++it; + } + + // remove the finished scripts from the script path map as well + for (const auto& it : tempList) + m_scriptPaths.erase(it.script); + + // we can leave the lock now + lock.unlock(); + + // finally remove the finished threads but we do it outside of any locks in + // case of any callbacks from the destruction of the CLanguageInvokerThread + tempList.clear(); + + // let the invocation handlers do their processing + for (auto& it : m_invocationHandlers) + it.second->Process(); +} + +void CScriptInvocationManager::Uninitialize() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + // execute Process() once more to handle the remaining scripts + Process(); + + // it is safe to release early, thread must be in m_scripts too + m_lastInvokerThread = nullptr; + + // make sure all scripts are done + std::vector<LanguageInvokerThread> tempList; + for (const auto& script : m_scripts) + tempList.push_back(script.second); + + m_scripts.clear(); + m_scriptPaths.clear(); + + // we can leave the lock now + lock.unlock(); + + // finally stop and remove the finished threads but we do it outside of any + // locks in case of any callbacks from the stop or destruction logic of + // CLanguageInvokerThread or the ILanguageInvoker implementation + for (auto& it : tempList) + { + if (!it.done) + it.thread->Stop(true); + } + + lock.lock(); + + tempList.clear(); + + // uninitialize all invocation handlers and then remove them + for (auto& it : m_invocationHandlers) + it.second->Uninitialize(); + + m_invocationHandlers.clear(); +} + +void CScriptInvocationManager::RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::string &extension) +{ + if (invocationHandler == NULL || extension.empty()) + return; + + std::string ext = extension; + StringUtils::ToLower(ext); + if (!StringUtils::StartsWithNoCase(ext, ".")) + ext = "." + ext; + + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_invocationHandlers.find(ext) != m_invocationHandlers.end()) + return; + + m_invocationHandlers.insert(std::make_pair(extension, invocationHandler)); + + bool known = false; + for (const auto& it : m_invocationHandlers) + { + if (it.second == invocationHandler) + { + known = true; + break; + } + } + + // automatically initialize the invocation handler if it's a new one + if (!known) + invocationHandler->Initialize(); +} + +void CScriptInvocationManager::RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::set<std::string> &extensions) +{ + if (invocationHandler == NULL || extensions.empty()) + return; + + for (const auto& extension : extensions) + RegisterLanguageInvocationHandler(invocationHandler, extension); +} + +void CScriptInvocationManager::UnregisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler) +{ + if (invocationHandler == NULL) + return; + + std::unique_lock<CCriticalSection> lock(m_critSection); + // get all extensions of the given language invoker + for (std::map<std::string, ILanguageInvocationHandler*>::iterator it = m_invocationHandlers.begin(); it != m_invocationHandlers.end(); ) + { + if (it->second == invocationHandler) + m_invocationHandlers.erase(it++); + else + ++it; + } + + // automatically uninitialize the invocation handler + invocationHandler->Uninitialize(); +} + +bool CScriptInvocationManager::HasLanguageInvoker(const std::string &script) const +{ + std::string extension = URIUtils::GetExtension(script); + StringUtils::ToLower(extension); + + std::unique_lock<CCriticalSection> lock(m_critSection); + std::map<std::string, ILanguageInvocationHandler*>::const_iterator it = m_invocationHandlers.find(extension); + return it != m_invocationHandlers.end() && it->second != NULL; +} + +int CScriptInvocationManager::GetReusablePluginHandle(const std::string& script) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_lastInvokerThread) + { + if (m_lastInvokerThread->Reuseable(script)) + return m_lastPluginHandle; + m_lastInvokerThread->Release(); + m_lastInvokerThread = nullptr; + } + return -1; +} + +LanguageInvokerPtr CScriptInvocationManager::GetLanguageInvoker(const std::string& script) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_lastInvokerThread) + { + if (m_lastInvokerThread->Reuseable(script)) + { + CLog::Log(LOGDEBUG, "{} - Reusing LanguageInvokerThread {} for script {}", __FUNCTION__, + m_lastInvokerThread->GetId(), script); + m_lastInvokerThread->GetInvoker()->Reset(); + return m_lastInvokerThread->GetInvoker(); + } + m_lastInvokerThread->Release(); + m_lastInvokerThread = nullptr; + } + + std::string extension = URIUtils::GetExtension(script); + StringUtils::ToLower(extension); + + std::map<std::string, ILanguageInvocationHandler*>::const_iterator it = m_invocationHandlers.find(extension); + if (it != m_invocationHandlers.end() && it->second != NULL) + return LanguageInvokerPtr(it->second->CreateInvoker()); + + return LanguageInvokerPtr(); +} + +int CScriptInvocationManager::ExecuteAsync( + const std::string& script, + const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */, + const std::vector<std::string>& arguments /* = std::vector<std::string>() */, + bool reuseable /* = false */, + int pluginHandle /* = -1 */) +{ + if (script.empty()) + return -1; + + if (!CFileUtils::Exists(script, false)) + { + CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script); + return -1; + } + + LanguageInvokerPtr invoker = GetLanguageInvoker(script); + return ExecuteAsync(script, invoker, addon, arguments, reuseable, pluginHandle); +} + +int CScriptInvocationManager::ExecuteAsync( + const std::string& script, + const LanguageInvokerPtr& languageInvoker, + const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */, + const std::vector<std::string>& arguments /* = std::vector<std::string>() */, + bool reuseable /* = false */, + int pluginHandle /* = -1 */) +{ + if (script.empty() || languageInvoker == NULL) + return -1; + + if (!CFileUtils::Exists(script, false)) + { + CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script); + return -1; + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + if (m_lastInvokerThread && m_lastInvokerThread->GetInvoker() == languageInvoker) + { + if (addon != NULL) + m_lastInvokerThread->SetAddon(addon); + + // After we leave the lock, m_lastInvokerThread can be released -> copy! + CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread; + lock.unlock(); + invokerThread->Execute(script, arguments); + + return invokerThread->GetId(); + } + + m_lastInvokerThread = + CLanguageInvokerThreadPtr(new CLanguageInvokerThread(languageInvoker, this, reuseable)); + if (m_lastInvokerThread == NULL) + return -1; + + if (addon != NULL) + m_lastInvokerThread->SetAddon(addon); + + m_lastInvokerThread->SetId(m_nextId++); + m_lastPluginHandle = pluginHandle; + + LanguageInvokerThread thread = {m_lastInvokerThread, script, false}; + m_scripts.insert(std::make_pair(m_lastInvokerThread->GetId(), thread)); + m_scriptPaths.insert(std::make_pair(script, m_lastInvokerThread->GetId())); + // After we leave the lock, m_lastInvokerThread can be released -> copy! + CLanguageInvokerThreadPtr invokerThread = m_lastInvokerThread; + lock.unlock(); + invokerThread->Execute(script, arguments); + + return invokerThread->GetId(); +} + +int CScriptInvocationManager::ExecuteSync( + const std::string& script, + const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */, + const std::vector<std::string>& arguments /* = std::vector<std::string>() */, + uint32_t timeoutMs /* = 0 */, + bool waitShutdown /* = false */) +{ + if (script.empty()) + return -1; + + if (!CFileUtils::Exists(script, false)) + { + CLog::Log(LOGERROR, "{} - Not executing non-existing script {}", __FUNCTION__, script); + return -1; + } + + LanguageInvokerPtr invoker = GetLanguageInvoker(script); + return ExecuteSync(script, invoker, addon, arguments, timeoutMs, waitShutdown); +} + +int CScriptInvocationManager::ExecuteSync( + const std::string& script, + const LanguageInvokerPtr& languageInvoker, + const ADDON::AddonPtr& addon /* = ADDON::AddonPtr() */, + const std::vector<std::string>& arguments /* = std::vector<std::string>() */, + uint32_t timeoutMs /* = 0 */, + bool waitShutdown /* = false */) +{ + int scriptId = ExecuteAsync(script, languageInvoker, addon, arguments); + if (scriptId < 0) + return -1; + + bool timeout = timeoutMs > 0; + while ((!timeout || timeoutMs > 0) && IsRunning(scriptId)) + { + unsigned int sleepMs = 100U; + if (timeout && timeoutMs < sleepMs) + sleepMs = timeoutMs; + + KODI::TIME::Sleep(std::chrono::milliseconds(sleepMs)); + + if (timeout) + timeoutMs -= sleepMs; + } + + if (IsRunning(scriptId)) + { + Stop(scriptId, waitShutdown); + return ETIMEDOUT; + } + + return 0; +} + +bool CScriptInvocationManager::Stop(int scriptId, bool wait /* = false */) +{ + if (scriptId < 0) + return false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + CLanguageInvokerThreadPtr invokerThread = getInvokerThread(scriptId).thread; + if (invokerThread == NULL) + return false; + + return invokerThread->Stop(wait); +} + +void CScriptInvocationManager::StopRunningScripts(bool wait /* = false */) +{ + for (auto& it : m_scripts) + { + if (!it.second.done) + Stop(it.second.script, wait); + } +} + +bool CScriptInvocationManager::Stop(const std::string &scriptPath, bool wait /* = false */) +{ + if (scriptPath.empty()) + return false; + + std::unique_lock<CCriticalSection> lock(m_critSection); + std::map<std::string, int>::const_iterator script = m_scriptPaths.find(scriptPath); + if (script == m_scriptPaths.end()) + return false; + + return Stop(script->second, wait); +} + +bool CScriptInvocationManager::IsRunning(int scriptId) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + LanguageInvokerThread invokerThread = getInvokerThread(scriptId); + if (invokerThread.thread == NULL) + return false; + + return !invokerThread.done; +} + +bool CScriptInvocationManager::IsRunning(const std::string& scriptPath) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + auto it = m_scriptPaths.find(scriptPath); + if (it == m_scriptPaths.end()) + return false; + + return IsRunning(it->second); +} + +void CScriptInvocationManager::OnExecutionDone(int scriptId) +{ + if (scriptId < 0) + return; + + std::unique_lock<CCriticalSection> lock(m_critSection); + LanguageInvokerThreadMap::iterator script = m_scripts.find(scriptId); + if (script != m_scripts.end()) + script->second.done = true; +} + +CScriptInvocationManager::LanguageInvokerThread CScriptInvocationManager::getInvokerThread(int scriptId) const +{ + if (scriptId < 0) + return LanguageInvokerThread(); + + LanguageInvokerThreadMap::const_iterator script = m_scripts.find(scriptId); + if (script == m_scripts.end()) + return LanguageInvokerThread(); + + return script->second; +} diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.h b/xbmc/interfaces/generic/ScriptInvocationManager.h new file mode 100644 index 0000000..6e409b8 --- /dev/null +++ b/xbmc/interfaces/generic/ScriptInvocationManager.h @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/IAddon.h" +#include "interfaces/generic/ILanguageInvoker.h" +#include "threads/CriticalSection.h" + +#include <map> +#include <memory> +#include <set> +#include <vector> + +class CLanguageInvokerThread; +typedef std::shared_ptr<CLanguageInvokerThread> CLanguageInvokerThreadPtr; + +class CScriptInvocationManager +{ +public: + static CScriptInvocationManager& GetInstance(); + + void Process(); + void Uninitialize(); + + void RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::string &extension); + void RegisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler, const std::set<std::string> &extensions); + void UnregisterLanguageInvocationHandler(ILanguageInvocationHandler *invocationHandler); + bool HasLanguageInvoker(const std::string &script) const; + LanguageInvokerPtr GetLanguageInvoker(const std::string& script); + + /*! + * \brief Returns addon_handle if last reusable invoker is ready to use. + */ + int GetReusablePluginHandle(const std::string& script); + + /*! + * \brief Executes the given script asynchronously in a separate thread. + * + * \param script Path to the script to be executed + * \param addon (Optional) Addon to which the script belongs + * \param arguments (Optional) List of arguments passed to the script + * \return -1 if an error occurred, otherwise the ID of the script + */ + int ExecuteAsync(const std::string& script, + const ADDON::AddonPtr& addon = ADDON::AddonPtr(), + const std::vector<std::string>& arguments = std::vector<std::string>(), + bool reuseable = false, + int pluginHandle = -1); + /*! + * \brief Executes the given script asynchronously in a separate thread. + * + * \param script Path to the script to be executed + * \param languageInvoker Language invoker to be used to execute the script + * \param addon (Optional) Addon to which the script belongs + * \param arguments (Optional) List of arguments passed to the script + * \return -1 if an error occurred, otherwise the ID of the script + */ + int ExecuteAsync(const std::string& script, + const LanguageInvokerPtr& languageInvoker, + const ADDON::AddonPtr& addon = ADDON::AddonPtr(), + const std::vector<std::string>& arguments = std::vector<std::string>(), + bool reuseable = false, + int pluginHandle = -1); + + /*! + * \brief Executes the given script synchronously. + * + * \details The script is actually executed asynchronously but the calling + * thread is blocked until either the script has finished or the given timeout + * has expired. If the given timeout has expired the script's execution is + * stopped and depending on the specified wait behaviour we wait for the + * script's execution to finish or not. + * + * \param script Path to the script to be executed + * \param addon (Optional) Addon to which the script belongs + * \param arguments (Optional) List of arguments passed to the script + * \param timeout (Optional) Timeout (in milliseconds) for the script's execution + * \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not. + * \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired + */ + int ExecuteSync(const std::string& script, + const ADDON::AddonPtr& addon = ADDON::AddonPtr(), + const std::vector<std::string>& arguments = std::vector<std::string>(), + uint32_t timeoutMs = 0, + bool waitShutdown = false); + /*! + * \brief Executes the given script synchronously. + * + * \details The script is actually executed asynchronously but the calling + * thread is blocked until either the script has finished or the given timeout + * has expired. If the given timeout has expired the script's execution is + * stopped and depending on the specified wait behaviour we wait for the + * script's execution to finish or not. + * + * \param script Path to the script to be executed + * \param languageInvoker Language invoker to be used to execute the script + * \param addon (Optional) Addon to which the script belongs + * \param arguments (Optional) List of arguments passed to the script + * \param timeout (Optional) Timeout (in milliseconds) for the script's execution + * \param waitShutdown (Optional) Whether to wait when having to forcefully stop the script's execution or not. + * \return -1 if an error occurred, 0 if the script terminated or ETIMEDOUT if the given timeout expired + */ + int ExecuteSync(const std::string& script, + const LanguageInvokerPtr& languageInvoker, + const ADDON::AddonPtr& addon = ADDON::AddonPtr(), + const std::vector<std::string>& arguments = std::vector<std::string>(), + uint32_t timeoutMs = 0, + bool waitShutdown = false); + bool Stop(int scriptId, bool wait = false); + bool Stop(const std::string &scriptPath, bool wait = false); + + /*! + *\brief Stop all running scripts + *\param wait if kodi should wait for each script to finish (default false) + */ + void StopRunningScripts(bool wait = false); + + bool IsRunning(int scriptId) const; + bool IsRunning(const std::string& scriptPath) const; + +protected: + friend class CLanguageInvokerThread; + + void OnExecutionDone(int scriptId); + +private: + CScriptInvocationManager() = default; + CScriptInvocationManager(const CScriptInvocationManager&) = delete; + CScriptInvocationManager const& operator=(CScriptInvocationManager const&) = delete; + virtual ~CScriptInvocationManager(); + + typedef struct { + CLanguageInvokerThreadPtr thread; + std::string script; + bool done; + } LanguageInvokerThread; + typedef std::map<int, LanguageInvokerThread> LanguageInvokerThreadMap; + typedef std::map<std::string, ILanguageInvocationHandler*> LanguageInvocationHandlerMap; + + LanguageInvokerThread getInvokerThread(int scriptId) const; + + LanguageInvocationHandlerMap m_invocationHandlers; + LanguageInvokerThreadMap m_scripts; + CLanguageInvokerThreadPtr m_lastInvokerThread; + int m_lastPluginHandle = -1; + + std::map<std::string, int> m_scriptPaths; + int m_nextId = 0; + mutable CCriticalSection m_critSection; +}; diff --git a/xbmc/interfaces/generic/ScriptRunner.cpp b/xbmc/interfaces/generic/ScriptRunner.cpp new file mode 100644 index 0000000..b88dbac --- /dev/null +++ b/xbmc/interfaces/generic/ScriptRunner.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ScriptRunner.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "dialogs/GUIDialogBusy.h" +#include "dialogs/GUIDialogProgress.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "interfaces/generic/RunningScriptObserver.h" +#include "interfaces/generic/ScriptInvocationManager.h" +#include "messaging/ApplicationMessenger.h" +#include "threads/SystemClock.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <vector> + +using namespace std::chrono_literals; + +ADDON::AddonPtr CScriptRunner::GetAddon() const +{ + return m_addon; +} + +CScriptRunner::CScriptRunner() : m_scriptDone(true) +{ } + +bool CScriptRunner::StartScript(const ADDON::AddonPtr& addon, const std::string& path) +{ + return RunScriptInternal(addon, path, 0, false); +} + +bool CScriptRunner::RunScript(const ADDON::AddonPtr& addon, + const std::string& path, + int handle, + bool resume) +{ + return RunScriptInternal(addon, path, handle, resume, true); +} + +void CScriptRunner::SetDone() +{ + m_scriptDone.Set(); +} + +int CScriptRunner::ExecuteScript(const ADDON::AddonPtr& addon, const std::string& path, bool resume) +{ + return ExecuteScript(addon, path, -1, resume); +} + +int CScriptRunner::ExecuteScript(const ADDON::AddonPtr& addon, + const std::string& path, + int handle, + bool resume) +{ + if (addon == nullptr || path.empty()) + return false; + + CURL url(path); + + // get options and remove them from the URL because we can then use the url + // to generate the base path which is passed to the add-on script + auto options = url.GetOptions(); + url.SetOptions(""); + + // setup our parameters to send the script + std::vector<std::string> argv = {url.Get(), // base path + StringUtils::Format("{:d}", handle), options, + StringUtils::Format("resume:{}", resume)}; + + bool reuseLanguageInvoker = false; + const auto reuseLanguageInvokerIt = addon->ExtraInfo().find("reuselanguageinvoker"); + if (reuseLanguageInvokerIt != addon->ExtraInfo().end()) + reuseLanguageInvoker = reuseLanguageInvokerIt->second == "true"; + + // run the script + CLog::Log(LOGDEBUG, "CScriptRunner: running add-on script {:s}('{:s}', '{:s}', '{:s}')", + addon->Name(), argv[0], argv[1], argv[2]); + int scriptId = CScriptInvocationManager::GetInstance().ExecuteAsync(addon->LibPath(), addon, argv, + reuseLanguageInvoker, handle); + if (scriptId < 0) + CLog::Log(LOGERROR, "CScriptRunner: unable to run add-on script {:s}", addon->Name()); + + return scriptId; +} + +bool CScriptRunner::RunScriptInternal(const ADDON::AddonPtr& addon, + const std::string& path, + int handle, + bool resume, + bool wait /* = true */) +{ + if (addon == nullptr || path.empty()) + return false; + + // reset our wait event + m_scriptDone.Reset(); + + // store the add-on + m_addon = addon; + + int scriptId = ExecuteScript(addon, path, handle, resume); + if (scriptId < 0) + return false; + + // we don't need to wait for the script to end + if (!wait) + return true; + + // wait for our script to finish + return WaitOnScriptResult(scriptId, addon->LibPath(), addon->Name()); +} + +bool CScriptRunner::WaitOnScriptResult(int scriptId, + const std::string& path, + const std::string& name) +{ + bool cancelled = false; + + // Add-on scripts can be called from the main and other threads. If called + // form the main thread, we need to bring up the BusyDialog in order to + // keep the render loop alive + if (CServiceBroker::GetAppMessenger()->IsProcessThread()) + { + if (!m_scriptDone.Wait(20ms)) + { + // observe the script until it's finished while showing the busy dialog + CRunningScriptObserver scriptObs(scriptId, m_scriptDone); + + auto& wm = CServiceBroker::GetGUI()->GetWindowManager(); + if (wm.IsModalDialogTopmost(WINDOW_DIALOG_PROGRESS)) + { + auto progress = wm.GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + if (!progress->WaitOnEvent(m_scriptDone)) + cancelled = true; + } + else if (!CGUIDialogBusy::WaitOnEvent(m_scriptDone, 200)) + cancelled = true; + + scriptObs.Abort(); + } + } + else + { + // wait for the script to finish or be cancelled + while (!IsCancelled() && CScriptInvocationManager::GetInstance().IsRunning(scriptId) && + !m_scriptDone.Wait(20ms)) + ; + + // give the script 30 seconds to exit before we attempt to stop it + XbmcThreads::EndTime<> timer(30s); + while (!timer.IsTimePast() && CScriptInvocationManager::GetInstance().IsRunning(scriptId) && + !m_scriptDone.Wait(20ms)) + ; + } + + if (cancelled || IsCancelled()) + { + // cancel the script + if (scriptId != -1 && CScriptInvocationManager::GetInstance().IsRunning(scriptId)) + { + CLog::Log(LOGDEBUG, "CScriptRunner: cancelling add-on script {:s} (id = {:d})", name, + scriptId); + CScriptInvocationManager::GetInstance().Stop(scriptId); + } + } + + return !cancelled && !IsCancelled() && IsSuccessful(); +} diff --git a/xbmc/interfaces/generic/ScriptRunner.h b/xbmc/interfaces/generic/ScriptRunner.h new file mode 100644 index 0000000..77d511d --- /dev/null +++ b/xbmc/interfaces/generic/ScriptRunner.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "addons/IAddon.h" +#include "threads/Event.h" + +#include <string> + +class CScriptRunner +{ +protected: + CScriptRunner(); + virtual ~CScriptRunner() = default; + + virtual bool IsSuccessful() const = 0; + virtual bool IsCancelled() const = 0; + + ADDON::AddonPtr GetAddon() const; + + bool StartScript(const ADDON::AddonPtr& addon, const std::string& path); + bool RunScript(const ADDON::AddonPtr& addon, const std::string& path, int handle, bool resume); + + void SetDone(); + + static int ExecuteScript(const ADDON::AddonPtr& addon, const std::string& path, bool resume); + static int ExecuteScript(const ADDON::AddonPtr& addon, + const std::string& path, + int handle, + bool resume); + +private: + bool RunScriptInternal(const ADDON::AddonPtr& addon, + const std::string& path, + int handle, + bool resume, + bool wait = true); + bool WaitOnScriptResult(int scriptId, const std::string& path, const std::string& name); + + ADDON::AddonPtr m_addon; + + CEvent m_scriptDone; +}; diff --git a/xbmc/interfaces/info/CMakeLists.txt b/xbmc/interfaces/info/CMakeLists.txt new file mode 100644 index 0000000..621ecfc --- /dev/null +++ b/xbmc/interfaces/info/CMakeLists.txt @@ -0,0 +1,10 @@ +set(SOURCES InfoBool.cpp + InfoExpression.cpp + SkinVariable.cpp) + +set(HEADERS Info.h + InfoBool.h + InfoExpression.h + SkinVariable.h) + +core_add_library(info_interface) diff --git a/xbmc/interfaces/info/Info.h b/xbmc/interfaces/info/Info.h new file mode 100644 index 0000000..e454e3c --- /dev/null +++ b/xbmc/interfaces/info/Info.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +namespace INFO +{ +/*! Default context of the INFO interface + @note when info conditions are evaluated different contexts can be passed. Context usually refers to the window ids where + the conditions are being evaluated. By default conditions, skin variables and labels are initialized using the DEFAULT_CONTEXT + value unless specifically bound to a given window. + */ +constexpr int DEFAULT_CONTEXT = 0; +} // namespace INFO diff --git a/xbmc/interfaces/info/InfoBool.cpp b/xbmc/interfaces/info/InfoBool.cpp new file mode 100644 index 0000000..65b1f44 --- /dev/null +++ b/xbmc/interfaces/info/InfoBool.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "InfoBool.h" + +#include "utils/StringUtils.h" + +namespace INFO +{ + InfoBool::InfoBool(const std::string &expression, int context, unsigned int &refreshCounter) + : m_value(false), + m_context(context), + m_listItemDependent(false), + m_expression(expression), + m_refreshCounter(0), + m_parentRefreshCounter(refreshCounter) + { + StringUtils::ToLower(m_expression); + } +} diff --git a/xbmc/interfaces/info/InfoBool.h b/xbmc/interfaces/info/InfoBool.h new file mode 100644 index 0000000..6906276 --- /dev/null +++ b/xbmc/interfaces/info/InfoBool.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <memory> +#include <string> + +class CGUIListItem; + +namespace INFO +{ +/*! + \ingroup info + \brief Base class, wrapping boolean conditions and expressions + */ +class InfoBool +{ +public: + InfoBool(const std::string &expression, int context, unsigned int &refreshCounter); + virtual ~InfoBool() = default; + + virtual void Initialize() {} + + /*! \brief Get the value of this info bool + This is called to update (if dirty) and fetch the value of the info bool + \param contextWindow the context (window id) where this condition is being evaluated + \param item the item used to evaluate the bool + */ + inline bool Get(int contextWindow, const CGUIListItem* item = nullptr) + { + if (item && m_listItemDependent) + Update(contextWindow, item); + else if (m_refreshCounter != m_parentRefreshCounter || m_refreshCounter == 0) + { + Update(contextWindow, nullptr); + m_refreshCounter = m_parentRefreshCounter; + } + return m_value; + } + + bool operator==(const InfoBool &right) const + { + return (m_context == right.m_context && + m_expression == right.m_expression); + } + + bool operator<(const InfoBool &right) const + { + if (m_context < right.m_context) + return true; + else if (m_context == right.m_context) + return m_expression < right.m_expression; + else + return false; + } + + /*! \brief Update the value of this info bool + This is called if and only if the info bool is dirty, allowing it to update it's current value + */ + virtual void Update(int contextWindow, const CGUIListItem* item) {} + + const std::string &GetExpression() const { return m_expression; } + bool ListItemDependent() const { return m_listItemDependent; } +protected: + + bool m_value; ///< current value + int m_context; ///< contextual information to go with the condition + bool m_listItemDependent; ///< do not cache if a listitem pointer is given + std::string m_expression; ///< original expression + +private: + unsigned int m_refreshCounter; + unsigned int &m_parentRefreshCounter; +}; + +typedef std::shared_ptr<InfoBool> InfoPtr; +}; diff --git a/xbmc/interfaces/info/InfoExpression.cpp b/xbmc/interfaces/info/InfoExpression.cpp new file mode 100644 index 0000000..dc9aa63 --- /dev/null +++ b/xbmc/interfaces/info/InfoExpression.cpp @@ -0,0 +1,311 @@ +/* + * 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 "InfoExpression.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "utils/log.h" + +#include <list> +#include <memory> +#include <stack> + +using namespace INFO; + +void InfoSingle::Initialize() +{ + m_condition = CServiceBroker::GetGUI()->GetInfoManager().TranslateSingleString(m_expression, m_listItemDependent); +} + +void InfoSingle::Update(int contextWindow, const CGUIListItem* item) +{ + // use propagated context in case this info has the default context (i.e. if not tied to a specific window) + // its value might depend on the context in which the evaluation was called + int context = m_context == DEFAULT_CONTEXT ? contextWindow : m_context; + m_value = CServiceBroker::GetGUI()->GetInfoManager().GetBool(m_condition, context, item); +} + +void InfoExpression::Initialize() +{ + if (!Parse(m_expression)) + { + CLog::Log(LOGERROR, "Error parsing boolean expression {}", m_expression); + m_expression_tree = std::make_shared<InfoLeaf>(CServiceBroker::GetGUI()->GetInfoManager().Register("false", 0), false); + } +} + +void InfoExpression::Update(int contextWindow, const CGUIListItem* item) +{ + // use propagated context in case this info expression has the default context (i.e. if not tied to a specific window) + // its value might depend on the context in which the evaluation was called + int context = m_context == DEFAULT_CONTEXT ? contextWindow : m_context; + m_value = m_expression_tree->Evaluate(context, item); +} + +/* Expressions are rewritten at parse time into a form which favours the + * formation of groups of associative nodes. These groups are then reordered at + * evaluation time such that nodes whose value renders the evaluation of the + * remainder of the group unnecessary tend to be evaluated first (these are + * true nodes for OR subexpressions, or false nodes for AND subexpressions). + * The end effect is to minimise the number of leaf nodes that need to be + * evaluated in order to determine the value of the expression. The runtime + * adaptability has the advantage of not being customised for any particular skin. + * + * The modifications to the expression at parse time fall into two groups: + * 1) Moving logical NOTs so that they are only applied to leaf nodes. + * For example, rewriting ![A+B]|C as !A|!B|C allows reordering such that + * any of the three leaves can be evaluated first. + * 2) Combining adjacent AND or OR operations such that each path from the root + * to a leaf encounters a strictly alternating pattern of AND and OR + * operations. So [A|B]|[C|D+[[E|F]|G] becomes A|B|C|[D+[E|F|G]]. + */ + +bool InfoExpression::InfoLeaf::Evaluate(int contextWindow, const CGUIListItem* item) +{ + return m_invert ^ m_info->Get(contextWindow, item); +} + +InfoExpression::InfoAssociativeGroup::InfoAssociativeGroup( + node_type_t type, + const InfoSubexpressionPtr &left, + const InfoSubexpressionPtr &right) + : m_type(type) +{ + AddChild(right); + AddChild(left); +} + +void InfoExpression::InfoAssociativeGroup::AddChild(const InfoSubexpressionPtr &child) +{ + m_children.push_front(child); // largely undoes the effect of parsing right-associative +} + +void InfoExpression::InfoAssociativeGroup::Merge(const std::shared_ptr<InfoAssociativeGroup>& other) +{ + m_children.splice(m_children.end(), other->m_children); +} + +bool InfoExpression::InfoAssociativeGroup::Evaluate(int contextWindow, const CGUIListItem* item) +{ + /* Handle either AND or OR by using the relation + * A AND B == !(!A OR !B) + * to convert ANDs into ORs + */ + std::list<InfoSubexpressionPtr>::iterator last = m_children.end(); + std::list<InfoSubexpressionPtr>::iterator it = m_children.begin(); + bool use_and = (m_type == NODE_AND); + bool result = use_and ^ (*it)->Evaluate(contextWindow, item); + while (!result && ++it != last) + { + result = use_and ^ (*it)->Evaluate(contextWindow, item); + if (result) + { + /* Move this child to the head of the list so we evaluate faster next time */ + m_children.push_front(*it); + m_children.erase(it); + } + } + return use_and ^ result; +} + +/* Expressions are parsed using the shunting-yard algorithm. Binary operators + * (AND/OR) are treated as right-associative so that we don't need to make a + * special case for the unary NOT operator. This has no effect upon the answers + * generated, though the initial sequence of evaluation of leaves may be + * different from what you might expect. + */ + +InfoExpression::operator_t InfoExpression::GetOperator(char ch) +{ + if (ch == '[') + return OPERATOR_LB; + else if (ch == ']') + return OPERATOR_RB; + else if (ch == '!') + return OPERATOR_NOT; + else if (ch == '+') + return OPERATOR_AND; + else if (ch == '|') + return OPERATOR_OR; + else + return OPERATOR_NONE; +} + +void InfoExpression::OperatorPop(std::stack<operator_t> &operator_stack, bool &invert, std::stack<InfoSubexpressionPtr> &nodes) +{ + operator_t op2 = operator_stack.top(); + operator_stack.pop(); + if (op2 == OPERATOR_NOT) + { + invert = !invert; + } + else + { + // At this point, it can only be OPERATOR_AND or OPERATOR_OR + if (invert) + op2 = (operator_t) (OPERATOR_AND ^ OPERATOR_OR ^ op2); + node_type_t new_type = op2 == OPERATOR_AND ? NODE_AND : NODE_OR; + + InfoSubexpressionPtr right = nodes.top(); + nodes.pop(); + InfoSubexpressionPtr left = nodes.top(); + + node_type_t right_type = right->Type(); + node_type_t left_type = left->Type(); + + // Combine associative operations into the same node where possible + if (left_type == new_type && right_type == new_type) + /* For example: AND + * / \ ____ AND ____ + * AND AND -> / / \ \ + * / \ / \ leaf leaf leaf leaf + * leaf leaf leaf leaf + */ + std::static_pointer_cast<InfoAssociativeGroup>(left)->Merge(std::static_pointer_cast<InfoAssociativeGroup>(right)); + else if (left_type == new_type) + /* For example: AND AND + * / \ / | \ + * AND OR -> leaf leaf OR + * / \ / \ / \ + * leaf leaf leaf leaf leaf leaf + */ + std::static_pointer_cast<InfoAssociativeGroup>(left)->AddChild(right); + else + { + nodes.pop(); + if (right_type == new_type) + { + /* For example: AND AND + * / \ / | \ + * OR AND -> OR leaf leaf + * / \ / \ / \ + * leaf leaf leaf leaf leaf leaf + */ + std::static_pointer_cast<InfoAssociativeGroup>(right)->AddChild(left); + nodes.push(right); + } + else + /* For example: AND which can't be simplified, and + * / \ requires a new AND node to be + * OR OR created with the two OR nodes + * / \ / \ as children + * leaf leaf leaf leaf + */ + nodes.push(std::make_shared<InfoAssociativeGroup>(new_type, left, right)); + } + } +} + +bool InfoExpression::Parse(const std::string &expression) +{ + const char *s = expression.c_str(); + std::string operand; + std::stack<operator_t> operator_stack; + bool invert = false; + std::stack<InfoSubexpressionPtr> nodes; + // The next two are for syntax-checking purposes + bool after_binaryoperator = true; + int bracket_count = 0; + + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + + char c; + // Skip leading whitespace - don't want it to count as an operand if that's all there is + while (isspace((unsigned char)(c=*s))) + s++; + + while ((c = *s++) != '\0') + { + operator_t op; + if ((op = GetOperator(c)) != OPERATOR_NONE) + { + // Character is an operator + if ((!after_binaryoperator && (c == '!' || c == '[')) || + (after_binaryoperator && (c == ']' || c == '+' || c == '|'))) + { + CLog::Log(LOGERROR, "Misplaced {}", c); + return false; + } + if (c == '[') + bracket_count++; + else if (c == ']' && bracket_count-- == 0) + { + CLog::Log(LOGERROR, "Unmatched ]"); + return false; + } + if (!operand.empty()) + { + InfoPtr info = infoMgr.Register(operand, m_context); + if (!info) + { + CLog::Log(LOGERROR, "Bad operand '{}'", operand); + return false; + } + /* Propagate any listItem dependency from the operand to the expression */ + m_listItemDependent |= info->ListItemDependent(); + nodes.push(std::make_shared<InfoLeaf>(info, invert)); + /* Reuse operand string for next operand */ + operand.clear(); + } + + // Handle any higher-priority stacked operators, except when the new operator is left-bracket. + // For a right-bracket, this will stop with the matching left-bracket at the top of the operator stack. + if (op != OPERATOR_LB) + { + while (!operator_stack.empty() && operator_stack.top() > op) + OperatorPop(operator_stack, invert, nodes); + } + if (op == OPERATOR_RB) + operator_stack.pop(); // remove the matching left-bracket + else + operator_stack.push(op); + if (op == OPERATOR_NOT) + invert = !invert; + + if (c == '+' || c == '|') + after_binaryoperator = true; + // Skip trailing whitespace - don't want it to count as an operand if that's all there is + while (isspace((unsigned char)(c=*s))) s++; + } + else + { + // Character is part of operand + operand += c; + after_binaryoperator = false; + } + } + if (bracket_count > 0) + { + CLog::Log(LOGERROR, "Unmatched ["); + return false; + } + if (after_binaryoperator) + { + CLog::Log(LOGERROR, "Missing operand"); + return false; + } + if (!operand.empty()) + { + InfoPtr info = infoMgr.Register(operand, m_context); + if (!info) + { + CLog::Log(LOGERROR, "Bad operand '{}'", operand); + return false; + } + /* Propagate any listItem dependency from the operand to the expression */ + m_listItemDependent |= info->ListItemDependent(); + nodes.push(std::make_shared<InfoLeaf>(info, invert)); + } + while (!operator_stack.empty()) + OperatorPop(operator_stack, invert, nodes); + + m_expression_tree = nodes.top(); + return true; +} diff --git a/xbmc/interfaces/info/InfoExpression.h b/xbmc/interfaces/info/InfoExpression.h new file mode 100644 index 0000000..1a0877c --- /dev/null +++ b/xbmc/interfaces/info/InfoExpression.h @@ -0,0 +1,117 @@ +/* + * 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 "InfoBool.h" + +#include <list> +#include <stack> +#include <utility> +#include <vector> + +class CGUIListItem; + +namespace INFO +{ +/*! \brief Class to wrap active boolean conditions + */ +class InfoSingle : public InfoBool +{ +public: + InfoSingle(const std::string& expression, int context, unsigned int& refreshCounter) + : InfoBool(expression, context, refreshCounter) + { + } + void Initialize() override; + + void Update(int contextWindow, const CGUIListItem* item) override; + +private: + int m_condition; ///< actual condition this represents +}; + +/*! \brief Class to wrap active boolean expressions + */ +class InfoExpression : public InfoBool +{ +public: + InfoExpression(const std::string& expression, int context, unsigned int& refreshCounter) + : InfoBool(expression, context, refreshCounter) + { + } + ~InfoExpression() override = default; + + void Initialize() override; + + void Update(int contextWindow, const CGUIListItem* item) override; + +private: + typedef enum + { + OPERATOR_NONE = 0, + OPERATOR_LB, // 1 + OPERATOR_RB, // 2 + OPERATOR_OR, // 3 + OPERATOR_AND, // 4 + OPERATOR_NOT, // 5 + } operator_t; + + typedef enum + { + NODE_LEAF, + NODE_AND, + NODE_OR, + } node_type_t; + + // An abstract base class for nodes in the expression tree + class InfoSubexpression + { + public: + virtual ~InfoSubexpression(void) = default; // so we can destruct derived classes using a pointer to their base class + virtual bool Evaluate(int contextWindow, const CGUIListItem* item) = 0; + virtual node_type_t Type() const=0; + }; + + typedef std::shared_ptr<InfoSubexpression> InfoSubexpressionPtr; + + // A leaf node in the expression tree + class InfoLeaf : public InfoSubexpression + { + public: + InfoLeaf(InfoPtr info, bool invert) : m_info(std::move(info)), m_invert(invert) {} + bool Evaluate(int contextWindow, const CGUIListItem* item) override; + node_type_t Type() const override { return NODE_LEAF; } + + private: + InfoPtr m_info; + bool m_invert; + }; + + // A branch node in the expression tree + class InfoAssociativeGroup : public InfoSubexpression + { + public: + InfoAssociativeGroup(node_type_t type, const InfoSubexpressionPtr &left, const InfoSubexpressionPtr &right); + void AddChild(const InfoSubexpressionPtr &child); + void Merge(const std::shared_ptr<InfoAssociativeGroup>& other); + bool Evaluate(int contextWindow, const CGUIListItem* item) override; + node_type_t Type() const override { return m_type; } + + private: + node_type_t m_type; + std::list<InfoSubexpressionPtr> m_children; + }; + + static operator_t GetOperator(char ch); + static void OperatorPop(std::stack<operator_t> &operator_stack, bool &invert, std::stack<InfoSubexpressionPtr> &nodes); + bool Parse(const std::string &expression); + InfoSubexpressionPtr m_expression_tree; +}; + +}; diff --git a/xbmc/interfaces/info/SkinVariable.cpp b/xbmc/interfaces/info/SkinVariable.cpp new file mode 100644 index 0000000..53980b9 --- /dev/null +++ b/xbmc/interfaces/info/SkinVariable.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 "SkinVariable.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "utils/XBMCTinyXML.h" + +using namespace INFO; +using namespace KODI; + +const CSkinVariableString* CSkinVariable::CreateFromXML(const TiXmlElement& node, int context) +{ + const char* name = node.Attribute("name"); + if (name) + { + CSkinVariableString* tmp = new CSkinVariableString; + tmp->m_name = name; + tmp->m_context = context; + const TiXmlElement* valuenode = node.FirstChildElement("value"); + while (valuenode) + { + CSkinVariableString::ConditionLabelPair pair; + const char *condition = valuenode->Attribute("condition"); + if (condition) + pair.m_condition = CServiceBroker::GetGUI()->GetInfoManager().Register(condition, context); + + auto label = valuenode->FirstChild() ? valuenode->FirstChild()->ValueStr() : ""; + pair.m_label = GUILIB::GUIINFO::CGUIInfoLabel(label); + tmp->m_conditionLabelPairs.push_back(pair); + if (!pair.m_condition) + break; // once we reach default value (without condition) break iterating + + valuenode = valuenode->NextSiblingElement("value"); + } + if (!tmp->m_conditionLabelPairs.empty()) + return tmp; + delete tmp; + } + return NULL; +} + +CSkinVariableString::CSkinVariableString() = default; + +int CSkinVariableString::GetContext() const +{ + return m_context; +} + +const std::string& CSkinVariableString::GetName() const +{ + return m_name; +} + +std::string CSkinVariableString::GetValue(int contextWindow, + bool preferImage /* = false */, + const CGUIListItem* item /* = nullptr */) const +{ + for (const auto& it : m_conditionLabelPairs) + { + // use propagated context in case this skin variable has the default context (i.e. if not tied to a specific window) + // nested skin variables are supported + int context = m_context == INFO::DEFAULT_CONTEXT ? contextWindow : m_context; + if (!it.m_condition || it.m_condition->Get(context, item)) + { + if (item) + return it.m_label.GetItemLabel(item, preferImage); + else + { + return it.m_label.GetLabel(context, preferImage); + } + } + } + return ""; +} diff --git a/xbmc/interfaces/info/SkinVariable.h b/xbmc/interfaces/info/SkinVariable.h new file mode 100644 index 0000000..f00f96f --- /dev/null +++ b/xbmc/interfaces/info/SkinVariable.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/guiinfo/GUIInfoLabel.h" +#include "interfaces/info/InfoBool.h" + +#include <string> +#include <vector> + +class TiXmlElement; + +namespace INFO +{ +class CSkinVariableString; + +class CSkinVariable +{ +public: + static const CSkinVariableString* CreateFromXML(const TiXmlElement& node, int context); +}; + +class CSkinVariableString +{ +public: + const std::string& GetName() const; + int GetContext() const; + std::string GetValue(int contextWindow, + bool preferImage = false, + const CGUIListItem* item = nullptr) const; + +private: + CSkinVariableString(); + + std::string m_name; + int m_context; + + struct ConditionLabelPair + { + INFO::InfoPtr m_condition; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_label; + }; + + typedef std::vector<ConditionLabelPair> VECCONDITIONLABELPAIR; + VECCONDITIONLABELPAIR m_conditionLabelPairs; + + friend class CSkinVariable; +}; + +} diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.cpp b/xbmc/interfaces/json-rpc/AddonsOperations.cpp new file mode 100644 index 0000000..db6c90a --- /dev/null +++ b/xbmc/interfaces/json-rpc/AddonsOperations.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "AddonsOperations.h" + +#include "JSONUtils.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "addons/AddonDatabase.h" +#include "addons/AddonManager.h" +#include "addons/PluginSource.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace ADDON; + +JSONRPC_STATUS CAddonsOperations::GetAddons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<AddonType> addonTypes; + AddonType addonType = CAddonInfo::TranslateType(parameterObject["type"].asString()); + CPluginSource::Content content = CPluginSource::Translate(parameterObject["content"].asString()); + CVariant enabled = parameterObject["enabled"]; + CVariant installed = parameterObject["installed"]; + + // ignore the "content" parameter if the type is specified but not a plugin or script + if (addonType != AddonType::UNKNOWN && addonType != AddonType::PLUGIN && + addonType != AddonType::SCRIPT) + content = CPluginSource::UNKNOWN; + + if (addonType >= AddonType::VIDEO && addonType <= AddonType::EXECUTABLE) + { + addonTypes.push_back(AddonType::PLUGIN); + addonTypes.push_back(AddonType::SCRIPT); + + switch (addonType) + { + case AddonType::VIDEO: + content = CPluginSource::VIDEO; + break; + case AddonType::AUDIO: + content = CPluginSource::AUDIO; + break; + case AddonType::IMAGE: + content = CPluginSource::IMAGE; + break; + case AddonType::GAME: + content = CPluginSource::GAME; + break; + case AddonType::EXECUTABLE: + content = CPluginSource::EXECUTABLE; + break; + default: + break; + } + } + else + addonTypes.push_back(addonType); + + VECADDONS addons; + for (const auto& typeIt : addonTypes) + { + VECADDONS typeAddons; + if (typeIt == AddonType::UNKNOWN) + { + if (!enabled.isBoolean()) //All + { + if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons); + if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean())) + CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons); + } + else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled + CServiceBroker::GetAddonMgr().GetAddons(typeAddons); + else if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons); + } + else + { + if (!enabled.isBoolean()) //All + { + if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetInstalledAddons(typeAddons, typeIt); + if (!installed.isBoolean() || (installed.isBoolean() && !installed.asBoolean())) + CServiceBroker::GetAddonMgr().GetInstallableAddons(typeAddons, typeIt); + } + else if (enabled.asBoolean() && (!installed.isBoolean() || installed.asBoolean())) //Enabled + CServiceBroker::GetAddonMgr().GetAddons(typeAddons, typeIt); + else if (!installed.isBoolean() || installed.asBoolean()) + CServiceBroker::GetAddonMgr().GetDisabledAddons(typeAddons, typeIt); + } + + addons.insert(addons.end(), typeAddons.begin(), typeAddons.end()); + } + + // remove library addons + for (int index = 0; index < (int)addons.size(); index++) + { + std::shared_ptr<CPluginSource> plugin; + if (content != CPluginSource::UNKNOWN) + plugin = std::dynamic_pointer_cast<CPluginSource>(addons.at(index)); + + if ((addons.at(index)->Type() <= AddonType::UNKNOWN || + addons.at(index)->Type() >= AddonType::MAX_TYPES) || + ((content != CPluginSource::UNKNOWN && plugin == NULL) || + (plugin != NULL && !plugin->Provides(content)))) + { + addons.erase(addons.begin() + index); + index--; + } + } + + int start, end; + HandleLimits(parameterObject, result, addons.size(), start, end); + + CAddonDatabase addondb; + for (int index = start; index < end; index++) + FillDetails(addons.at(index), parameterObject["properties"], result["addons"], addondb, true); + + return OK; +} + +JSONRPC_STATUS CAddonsOperations::GetAddonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_NO) || + addon.get() == NULL || addon->Type() <= AddonType::UNKNOWN || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + CAddonDatabase addondb; + FillDetails(addon, parameterObject["properties"], result["addon"], addondb); + + return OK; +} + +JSONRPC_STATUS CAddonsOperations::SetAddonEnabled(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_NO) || + addon == nullptr || addon->Type() <= AddonType::UNKNOWN || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + bool disabled = false; + if (parameterObject["enabled"].isBoolean()) + { + disabled = !parameterObject["enabled"].asBoolean(); + } + // we need to toggle the current disabled state of the addon + else if (parameterObject["enabled"].isString()) + { + disabled = !CServiceBroker::GetAddonMgr().IsAddonDisabled(id); + } + else + { + return InvalidParams; + } + + bool success = disabled + ? CServiceBroker::GetAddonMgr().DisableAddon(id, AddonDisabledReason::USER) + : CServiceBroker::GetAddonMgr().EnableAddon(id); + + return success ? ACK : InvalidParams; +} + +JSONRPC_STATUS CAddonsOperations::ExecuteAddon(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string id = parameterObject["addonid"].asString(); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(id, addon, OnlyEnabled::CHOICE_YES) || + addon.get() == NULL || addon->Type() < AddonType::VISUALIZATION || + addon->Type() >= AddonType::MAX_TYPES) + return InvalidParams; + + std::string argv; + CVariant params = parameterObject["params"]; + if (params.isObject()) + { + for (CVariant::const_iterator_map it = params.begin_map(); it != params.end_map(); ++it) + { + if (it != params.begin_map()) + argv += ","; + argv += it->first + "=" + it->second.asString(); + } + } + else if (params.isArray()) + { + for (CVariant::const_iterator_array it = params.begin_array(); it != params.end_array(); ++it) + { + if (it != params.begin_array()) + argv += ","; + argv += StringUtils::Paramify(it->asString()); + } + } + else if (params.isString()) + { + if (!params.empty()) + argv = StringUtils::Paramify(params.asString()); + } + + std::string cmd; + if (params.empty()) + cmd = StringUtils::Format("RunAddon({})", id); + else + cmd = StringUtils::Format("RunAddon({}, {})", id, argv); + + if (params["wait"].asBoolean()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + + return ACK; +} + +static CVariant Serialize(const AddonPtr& addon) +{ + CVariant variant; + variant["addonid"] = addon->ID(); + variant["type"] = CAddonInfo::TranslateType(addon->Type(), false); + variant["name"] = addon->Name(); + variant["version"] = addon->Version().asString(); + variant["summary"] = addon->Summary(); + variant["description"] = addon->Description(); + variant["path"] = addon->Path(); + variant["author"] = addon->Author(); + variant["thumbnail"] = addon->Icon(); + variant["disclaimer"] = addon->Disclaimer(); + variant["fanart"] = addon->FanArt(); + + variant["dependencies"] = CVariant(CVariant::VariantTypeArray); + for (const auto& dep : addon->GetDependencies()) + { + CVariant info(CVariant::VariantTypeObject); + info["addonid"] = dep.id; + info["minversion"] = dep.versionMin.asString(); + info["version"] = dep.version.asString(); + info["optional"] = dep.optional; + variant["dependencies"].push_back(std::move(info)); + } + if (addon->LifecycleState() == AddonLifecycleState::BROKEN) + variant["broken"] = addon->LifecycleStateDescription(); + else + variant["broken"] = false; + if (addon->LifecycleState() == AddonLifecycleState::DEPRECATED) + variant["deprecated"] = addon->LifecycleStateDescription(); + else + variant["deprecated"] = false; + variant["extrainfo"] = CVariant(CVariant::VariantTypeArray); + for (const auto& kv : addon->ExtraInfo()) + { + CVariant info(CVariant::VariantTypeObject); + info["key"] = kv.first; + info["value"] = kv.second; + variant["extrainfo"].push_back(std::move(info)); + } + variant["rating"] = -1; + return variant; +} + +void CAddonsOperations::FillDetails(const std::shared_ptr<ADDON::IAddon>& addon, + const CVariant& fields, + CVariant& result, + CAddonDatabase& addondb, + bool append /* = false */) +{ + if (addon.get() == NULL) + return; + + CVariant addonInfo = Serialize(addon); + + CVariant object; + object["addonid"] = addonInfo["addonid"]; + object["type"] = addonInfo["type"]; + + for (unsigned int index = 0; index < fields.size(); index++) + { + std::string field = fields[index].asString(); + + // we need to manually retrieve the enabled / installed state of every addon + // from the addon database because it can't be read from addon.xml + if (field == "enabled") + { + object[field] = !CServiceBroker::GetAddonMgr().IsAddonDisabled(addon->ID()); + } + else if (field == "installed") + { + object[field] = CServiceBroker::GetAddonMgr().IsAddonInstalled(addon->ID()); + } + else if (field == "fanart" || field == "thumbnail") + { + std::string url = addonInfo[field].asString(); + // We need to check the existence of fanart and thumbnails as the addon simply + // holds where the art will be, not whether it exists. + bool needsRecaching; + std::string image = CServiceBroker::GetTextureCache()->CheckCachedImage(url, needsRecaching); + if (!image.empty() || CFileUtils::Exists(url)) + object[field] = CTextureUtils::GetWrappedImageURL(url); + else + object[field] = ""; + } + else if (addonInfo.isMember(field)) + object[field] = addonInfo[field]; + } + + if (append) + result.append(object); + else + result = object; +} diff --git a/xbmc/interfaces/json-rpc/AddonsOperations.h b/xbmc/interfaces/json-rpc/AddonsOperations.h new file mode 100644 index 0000000..727f418 --- /dev/null +++ b/xbmc/interfaces/json-rpc/AddonsOperations.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +#include <memory> + +namespace ADDON +{ +class CAddonDatabase; +class IAddon; +} + +class CVariant; + +namespace JSONRPC +{ + class CAddonsOperations : public CJSONUtils + { + public: + static JSONRPC_STATUS GetAddons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAddonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetAddonEnabled(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ExecuteAddon(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + private: + static void FillDetails(const std::shared_ptr<ADDON::IAddon>& addon, + const CVariant& fields, + CVariant& result, + ADDON::CAddonDatabase& addondb, + bool append = false); + }; +} diff --git a/xbmc/interfaces/json-rpc/ApplicationOperations.cpp b/xbmc/interfaces/json-rpc/ApplicationOperations.cpp new file mode 100644 index 0000000..aab680a --- /dev/null +++ b/xbmc/interfaces/json-rpc/ApplicationOperations.cpp @@ -0,0 +1,165 @@ +/* + * 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 "ApplicationOperations.h" + +#include "CompileInfo.h" +#include "InputOperations.h" +#include "LangInfo.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationVolumeHandling.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <cmath> +#include <string.h> + +using namespace JSONRPC; + +JSONRPC_STATUS CApplicationOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CApplicationOperations::SetVolume(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + bool up = false; + if (parameterObject["volume"].isInteger()) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + int oldVolume = static_cast<int>(appVolume->GetVolumePercent()); + int volume = static_cast<int>(parameterObject["volume"].asInteger()); + + appVolume->SetVolume(static_cast<float>(volume), true); + + up = oldVolume < volume; + } + else if (parameterObject["volume"].isString()) + { + JSONRPC_STATUS ret; + std::string direction = parameterObject["volume"].asString(); + if (direction.compare("increment") == 0) + { + ret = CInputOperations::SendAction(ACTION_VOLUME_UP, false, true); + up = true; + } + else if (direction.compare("decrement") == 0) + { + ret = CInputOperations::SendAction(ACTION_VOLUME_DOWN, false, true); + up = false; + } + else + return InvalidParams; + + if (ret != ACK && ret != OK) + return ret; + } + else + return InvalidParams; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VOLUME_SHOW, + up ? ACTION_VOLUME_UP : ACTION_VOLUME_DOWN); + + return GetPropertyValue("volume", result); +} + +JSONRPC_STATUS CApplicationOperations::SetMute(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if ((parameterObject["mute"].isString() && + parameterObject["mute"].asString().compare("toggle") == 0) || + (parameterObject["mute"].isBoolean() && + parameterObject["mute"].asBoolean() != appVolume->IsMuted())) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(ACTION_MUTE))); + else if (!parameterObject["mute"].isBoolean() && !parameterObject["mute"].isString()) + return InvalidParams; + + return GetPropertyValue("muted", result); +} + +JSONRPC_STATUS CApplicationOperations::Quit(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); + return ACK; +} + +JSONRPC_STATUS CApplicationOperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + if (property == "volume" || property == "muted") + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appVolume = components.GetComponent<CApplicationVolumeHandling>(); + if (property == "volume") + result = static_cast<int>(std::lroundf(appVolume->GetVolumePercent())); + else if (property == "muted") + result = appVolume->IsMuted(); + } + else if (property == "name") + result = CCompileInfo::GetAppName(); + else if (property == "version") + { + result = CVariant(CVariant::VariantTypeObject); + result["major"] = CCompileInfo::GetMajor(); + result["minor"] = CCompileInfo::GetMinor(); + result["revision"] = CCompileInfo::GetSCMID(); + std::string tag = CCompileInfo::GetSuffix(); + if (StringUtils::StartsWithNoCase(tag, "alpha")) + { + result["tag"] = "alpha"; + result["tagversion"] = StringUtils::Mid(tag, 5); + } + else if (StringUtils::StartsWithNoCase(tag, "beta")) + { + result["tag"] = "beta"; + result["tagversion"] = StringUtils::Mid(tag, 4); + } + else if (StringUtils::StartsWithNoCase(tag, "rc")) + { + result["tag"] = "releasecandidate"; + result["tagversion"] = StringUtils::Mid(tag, 2); + } + else if (tag.empty()) + result["tag"] = "stable"; + else + result["tag"] = "prealpha"; + } + else if (property == "sorttokens") + { + result = CVariant(CVariant::VariantTypeArray); // Ensure no tokens returns as [] + std::set<std::string> sortTokens = g_langInfo.GetSortTokens(); + for (const auto& token : sortTokens) + result.append(token); + } + else if (property == "language") + result = g_langInfo.GetLocale().ToShortString(); + else + return InvalidParams; + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/ApplicationOperations.h b/xbmc/interfaces/json-rpc/ApplicationOperations.h new file mode 100644 index 0000000..464f824 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ApplicationOperations.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CApplicationOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetVolume(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMute(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Quit(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp new file mode 100644 index 0000000..3c73a84 --- /dev/null +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -0,0 +1,1372 @@ +/* + * 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 "AudioLibrary.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "filesystem/Directory.h" +#include "messaging/ApplicationMessenger.h" +#include "music/Album.h" +#include "music/Artist.h" +#include "music/MusicDatabase.h" +#include "music/MusicDbUrl.h" +#include "music/MusicThumbLoader.h" +#include "music/Song.h" +#include "music/tags/MusicInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +using namespace MUSIC_INFO; +using namespace JSONRPC; +using namespace XFILE; + +JSONRPC_STATUS CAudioLibrary::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + CMusicDatabase musicdatabase; + // Make db connection once if one or more properties needs db access + for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); + it != parameterObject["properties"].end_array(); ++it) + { + std::string propertyName = it->asString(); + if (propertyName == "librarylastupdated" || propertyName == "librarylastcleaned" || + propertyName == "artistlinksupdated" || propertyName == "songslastadded" || + propertyName == "albumslastadded" || propertyName == "artistslastadded" || + propertyName == "songsmodified" || propertyName == "albumsmodified" || + propertyName == "artistsmodified") + { + if (!musicdatabase.Open()) + return InternalError; + else + break; + } + } + + for (CVariant::const_iterator_array it = parameterObject["properties"].begin_array(); + it != parameterObject["properties"].end_array(); ++it) + { + std::string propertyName = it->asString(); + CVariant property; + if (propertyName == "missingartistid") + property = (int)BLANKARTIST_ID; + else if (propertyName == "librarylastupdated") + property = musicdatabase.GetLibraryLastUpdated(); + else if (propertyName == "librarylastcleaned") + property = musicdatabase.GetLibraryLastCleaned(); + else if (propertyName == "artistlinksupdated") + property = musicdatabase.GetArtistLinksUpdated(); + else if (propertyName == "songslastadded") + property = musicdatabase.GetSongsLastAdded(); + else if (propertyName == "albumslastadded") + property = musicdatabase.GetAlbumsLastAdded(); + else if (propertyName == "artistslastadded") + property = musicdatabase.GetArtistsLastAdded(); + else if (propertyName == "genreslastadded") + property = musicdatabase.GetGenresLastAdded(); + else if (propertyName == "songsmodified") + property = musicdatabase.GetSongsLastModified(); + else if (propertyName == "albumsmodified") + property = musicdatabase.GetAlbumsLastModified(); + else if (propertyName == "artistsmodified") + property = musicdatabase.GetArtistsLastModified(); + + properties[propertyName] = property; + } + + result = properties; + return OK; +} + + +JSONRPC_STATUS CAudioLibrary::GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://artists/")) + return InternalError; + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, any negative parameter overrides implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of (song) genreid/genre, albumid/album or songid/song or rules type filter is allowed by filter syntax + if (filter.isMember("genreid")) //Deprecated. Use "songgenre" or "artistgenre" + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + if (filter.isMember("songgenreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["songgenreid"].asInteger())); + else if (filter.isMember("songgenre")) + musicUrl.AddOption("genre", filter["songgenre"].asString()); + else if (filter.isMember("albumid")) + musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger())); + else if (filter.isMember("album")) + musicUrl.AddOption("album", filter["album"].asString()); + else if (filter.isMember("songid")) + musicUrl.AddOption("songid", static_cast<int>(filter["songid"].asInteger())); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("artists", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + bool albumArtistsOnly = !CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MUSICLIBRARY_SHOWCOMPILATIONARTISTS); + if (parameterObject["albumartistsonly"].isBoolean()) + albumArtistsOnly = parameterObject["albumartistsonly"].asBoolean(); + musicUrl.AddOption("albumartistsonly", albumArtistsOnly); + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + musicdatabase.SetTranslateBlankArtist(false); + if (!musicdatabase.GetArtistsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int artistID = (int)parameterObject["artistid"].asInteger(); + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://artists/")) + return InternalError; + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + musicUrl.AddOption("artistid", artistID); + + CFileItemList items; + CDatabase::Filter filter; + if (!musicdatabase.GetArtistsByWhere(musicUrl.ToString(), filter, items) || items.Size() != 1) + return InvalidParams; + + // Add "artist" to "properties" array by default + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + param["properties"].append("artist"); + + //Get roleids, roles etc. if needed + JSONRPC_STATUS ret = GetAdditionalArtistDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("artistid", false, "artistdetails", items[0], param, param["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://albums/")) + return InternalError; + + if (parameterObject["includesingles"].asBoolean()) + musicUrl.AddOption("show_singles", true); + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of genreid/genre, artistid/artist or rules type filter is allowed by filter syntax + if (filter.isMember("artistid")) + musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger())); + else if (filter.isMember("artist")) + musicUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("albums", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + if (!musicdatabase.GetAlbumsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + if (!result.isNull()) + { + bool bFetchArt = fields.find("art") != fields.end(); + bool bFetchFanart = fields.find("fanart") != fields.end(); + if (bFetchArt || bFetchFanart) + { + CThumbLoader* thumbLoader = new CMusicThumbLoader(); + thumbLoader->OnLoaderStart(); + + std::set<std::string> artfields; + if (bFetchArt) + artfields.insert("art"); + if (bFetchFanart) + artfields.insert("fanart"); + + for (unsigned int index = 0; index < result["albums"].size(); index++) + { + CFileItem item; + item.GetMusicInfoTag()->SetDatabaseId(result["albums"][index]["albumid"].asInteger32(), MediaTypeAlbum); + + // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag + // CFileItemPtr itemptr(new CFileItem(item)); + // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["albums"][index], thumbLoader); + + thumbLoader->FillLibraryArt(item); + + if (bFetchFanart) + { + if (item.HasArt("fanart")) + result["albums"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart")); + else + result["albums"][index]["fanart"] = ""; + } + if (bFetchArt) + { + CGUIListItem::ArtMap artMap = item.GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + result["albums"][index]["art"] = artObj; + } + } + + delete thumbLoader; + } + } + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int albumID = (int)parameterObject["albumid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CAlbum album; + if (!musicdatabase.GetAlbum(albumID, album, false)) + return InvalidParams; + + std::string path = StringUtils::Format("musicdb://albums/{}/", albumID); + + CFileItemPtr albumItem; + FillAlbumItem(album, path, albumItem); + + CFileItemList items; + items.Add(albumItem); + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("albumid", false, "albumdetails", items[0], parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CMusicDbUrl musicUrl; + if (!musicUrl.FromString("musicdb://songs/")) + return InternalError; + + if (parameterObject["singlesonly"].asBoolean()) + musicUrl.AddOption("singles", true); + else if (!parameterObject["includesingles"].asBoolean()) + musicUrl.AddOption("singles", false); + + bool allroles = false; + if (parameterObject["allroles"].isBoolean()) + allroles = parameterObject["allroles"].asBoolean(); + + const CVariant &filter = parameterObject["filter"]; + + if (allroles) + musicUrl.AddOption("roleid", -1000); //All roles, override implicit roleid=1 filter required for backward compatibility + else if (filter.isMember("roleid")) + musicUrl.AddOption("roleid", static_cast<int>(filter["roleid"].asInteger())); + else if (filter.isMember("role")) + musicUrl.AddOption("role", filter["role"].asString()); + // Only one of genreid/genre, artistid/artist, albumid/album or rules type filter is allowed by filter syntax + if (filter.isMember("artistid")) + musicUrl.AddOption("artistid", static_cast<int>(filter["artistid"].asInteger())); + else if (filter.isMember("artist")) + musicUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + musicUrl.AddOption("genreid", static_cast<int>(filter["genreid"].asInteger())); + else if (filter.isMember("genre")) + musicUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("albumid")) + musicUrl.AddOption("albumid", static_cast<int>(filter["albumid"].asInteger())); + else if (filter.isMember("album")) + musicUrl.AddOption("album", filter["album"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("songs", filter, xsp)) + return InvalidParams; + + musicUrl.AddOption("xsp", xsp); + } + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int total; + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + if (!musicdatabase.GetSongsByWhereJSON(fields, musicUrl.ToString(), result, total, sorting)) + return InternalError; + + if (!result.isNull()) + { + bool bFetchArt = fields.find("art") != fields.end(); + bool bFetchFanart = fields.find("fanart") != fields.end(); + bool bFetchThumb = fields.find("thumbnail") != fields.end(); + if (bFetchArt || bFetchFanart || bFetchThumb) + { + CThumbLoader* thumbLoader = new CMusicThumbLoader(); + thumbLoader->OnLoaderStart(); + + std::set<std::string> artfields; + if (bFetchArt) + artfields.insert("art"); + if (bFetchFanart) + artfields.insert("fanart"); + if (bFetchThumb) + artfields.insert("thumbnail"); + + for (unsigned int index = 0; index < result["songs"].size(); index++) + { + CFileItem item; + // Only needs song and album id (if we have it) set to get art + // Getting art is quicker if "albumid" has been fetched + item.GetMusicInfoTag()->SetDatabaseId(result["songs"][index]["songid"].asInteger32(), MediaTypeSong); + if (result["songs"][index].isMember("albumid")) + item.GetMusicInfoTag()->SetAlbumId(result["songs"][index]["albumid"].asInteger32()); + else + item.GetMusicInfoTag()->SetAlbumId(-1); + + // Could use FillDetails, but it does unnecessary serialization of empty MusiInfoTag + // CFileItemPtr itemptr(new CFileItem(item)); + // FillDetails(item.GetMusicInfoTag(), itemptr, artfields, result["songs"][index], thumbLoader); + + thumbLoader->FillLibraryArt(item); + + if (bFetchThumb) + { + if (item.HasArt("thumb")) + result["songs"][index]["thumbnail"] = CTextureUtils::GetWrappedImageURL(item.GetArt("thumb")); + else + result["songs"][index]["thumbnail"] = ""; + } + if (bFetchFanart) + { + if (item.HasArt("fanart")) + result["songs"][index]["fanart"] = CTextureUtils::GetWrappedImageURL(item.GetArt("fanart")); + else + result["songs"][index]["fanart"] = ""; + } + if (bFetchArt) + { + CGUIListItem::ArtMap artMap = item.GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + result["songs"][index]["art"] = artObj; + } + } + + delete thumbLoader; + } + } + + int start, end; + HandleLimits(parameterObject, result, total, start, end); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int idSong = (int)parameterObject["songid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CSong song; + if (!musicdatabase.GetSong(idSong, song)) + return InvalidParams; + + CFileItemList items; + CFileItemPtr item = CFileItemPtr(new CFileItem(song)); + FillItemArtistIDs(song.GetArtistIDArray(), item); + items.Add(item); + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItem("songid", true, "songdetails", items[0], parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + VECALBUMS albums; + if (!musicdatabase.GetRecentlyAddedAlbums(albums)) + return InternalError; + + CFileItemList items; + for (unsigned int index = 0; index < albums.size(); index++) + { + std::string path = + StringUtils::Format("musicdb://recentlyaddedalbums/{}/", albums[index].idAlbum); + + CFileItemPtr item; + FillAlbumItem(albums[index], path, item); + items.Add(item); + } + + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("albumid", false, "albums", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + int amount = (int)parameterObject["albumlimit"].asInteger(); + if (amount < 0) + amount = 0; + + CFileItemList items; + if (!musicdatabase.GetRecentlyAddedAlbumSongs("musicdb://songs/", items, (unsigned int)amount)) + return InternalError; + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("songid", true, "songs", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + VECALBUMS albums; + if (!musicdatabase.GetRecentlyPlayedAlbums(albums)) + return InternalError; + + CFileItemList items; + for (unsigned int index = 0; index < albums.size(); index++) + { + std::string path = + StringUtils::Format("musicdb://recentlyplayedalbums/{}/", albums[index].idAlbum); + + CFileItemPtr item; + FillAlbumItem(albums[index], path, item); + items.Add(item); + } + + JSONRPC_STATUS ret = GetAdditionalAlbumDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("albumid", false, "albums", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CFileItemList items; + if (!musicdatabase.GetRecentlyPlayedAlbumSongs("musicdb://songs/", items)) + return InternalError; + + JSONRPC_STATUS ret = GetAdditionalSongDetails(parameterObject, items, musicdatabase); + if (ret != OK) + return ret; + + HandleFileItemList("songid", true, "songs", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + // Check if sources for genre wanted + bool sourcesneeded(false); + std::set<std::string> checkProperties; + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + sourcesneeded = (additionalProperties.find("sourceid") != additionalProperties.end()); + + CFileItemList items; + if (!musicdatabase.GetGenresJSON(items, sourcesneeded)) + return InternalError; + + HandleFileItemList("genreid", false, "genres", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CFileItemList items; + if (!musicdatabase.GetRolesNav("musicdb://songs/", items)) + return InternalError; + + /* need to set strTitle in each item*/ + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + items[i]->GetMusicInfoTag()->SetTitle(items[i]->GetLabel()); + + HandleFileItemList("roleid", false, "roles", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS JSONRPC::CAudioLibrary::GetSources(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + // Add "file" to "properties" array by default + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + if (!param["properties"].isMember("file")) + param["properties"].append("file"); + + CFileItemList items; + if (!musicdatabase.GetSources(items)) + return InternalError; + + HandleFileItemList("sourceid", true, "sources", items, param, result); + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + if (parameterObject["item"].isMember("albumid")) + { + mediaType = MediaTypeAlbum; + mediaID = parameterObject["item"]["albumid"].asInteger32(); + } + if (parameterObject["item"].isMember("artistid")) + { + mediaType = MediaTypeArtist; + mediaID = parameterObject["item"]["artistid"].asInteger32(); + } + if (mediaID == -1) + return InternalError; + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CVariant availablearttypes = CVariant(CVariant::VariantTypeArray); + for (const auto& artType : musicdatabase.GetAvailableArtTypesForItem(mediaID, mediaType)) + { + availablearttypes.append(artType); + } + result = CVariant(CVariant::VariantTypeObject); + result["availablearttypes"] = availablearttypes; + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + if (parameterObject["item"].isMember("albumid")) + { + mediaType = MediaTypeAlbum; + mediaID = parameterObject["item"]["albumid"].asInteger32(); + } + if (parameterObject["item"].isMember("artistid")) + { + mediaType = MediaTypeArtist; + mediaID = parameterObject["item"]["artistid"].asInteger32(); + } + if (mediaID == -1) + return InternalError; + + std::string artType = parameterObject["arttype"].asString(); + StringUtils::ToLower(artType); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CVariant availableart = CVariant(CVariant::VariantTypeArray); + for (const auto& artentry : musicdatabase.GetAvailableArtForItem(mediaID, mediaType, artType)) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url); + item["arttype"] = artentry.m_aspect; + if (!artentry.m_preview.empty()) + item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview); + availableart.append(item); + } + result = CVariant(CVariant::VariantTypeObject); + result["availableart"] = availableart; + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["artistid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CArtist artist; + if (!musicdatabase.GetArtist(id, artist) || artist.idArtist <= 0) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "artist")) + artist.strArtist = parameterObject["artist"].asString(); + if (ParameterNotNull(parameterObject, "instrument")) + CopyStringArray(parameterObject["instrument"], artist.instruments); + if (ParameterNotNull(parameterObject, "style")) + CopyStringArray(parameterObject["style"], artist.styles); + if (ParameterNotNull(parameterObject, "mood")) + CopyStringArray(parameterObject["mood"], artist.moods); + if (ParameterNotNull(parameterObject, "born")) + artist.strBorn = parameterObject["born"].asString(); + if (ParameterNotNull(parameterObject, "formed")) + artist.strFormed = parameterObject["formed"].asString(); + if (ParameterNotNull(parameterObject, "description")) + artist.strBiography = parameterObject["description"].asString(); + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], artist.genre); + if (ParameterNotNull(parameterObject, "died")) + artist.strDied = parameterObject["died"].asString(); + if (ParameterNotNull(parameterObject, "disbanded")) + artist.strDisbanded = parameterObject["disbanded"].asString(); + if (ParameterNotNull(parameterObject, "yearsactive")) + CopyStringArray(parameterObject["yearsactive"], artist.yearsActive); + if (ParameterNotNull(parameterObject, "musicbrainzartistid")) + artist.strMusicBrainzArtistID = parameterObject["musicbrainzartistid"].asString(); + if (ParameterNotNull(parameterObject, "sortname")) + artist.strSortName = parameterObject["sortname"].asString(); + if (ParameterNotNull(parameterObject, "type")) + artist.strType = parameterObject["type"].asString(); + if (ParameterNotNull(parameterObject, "gender")) + artist.strGender = parameterObject["gender"].asString(); + if (ParameterNotNull(parameterObject, "disambiguation")) + artist.strDisambiguation = parameterObject["disambiguation"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + musicdatabase.GetArtForItem(artist.idArtist, MediaTypeArtist, artist.art); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + artist.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + artist.art.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + // Remove null art now, as not done by update + if (!musicdatabase.RemoveArtForItem(artist.idArtist, MediaTypeArtist, removedArtwork)) + return InternalError; + } + + // Update artist including adding or replacing (but not removing) art + if (!musicdatabase.UpdateArtist(artist)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["albumid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CAlbum album; + // Get current album details, but not songs as we do not want to update them here + if (!musicdatabase.GetAlbum(id, album, false) || album.idAlbum <= 0) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "title")) + album.strAlbum = parameterObject["title"].asString(); + if (ParameterNotNull(parameterObject, "displayartist")) + album.strArtistDesc = parameterObject["displayartist"].asString(); + // Set album sort string before processing artist credits + if (ParameterNotNull(parameterObject, "sortartist")) + album.strArtistSort = parameterObject["sortartist"].asString(); + + // Match up artist names and mbids to make new artist credits + // Mbid values only apply if there are names + if (ParameterNotNull(parameterObject, "artist")) + { + std::vector<std::string> artists; + std::vector<std::string> mbids; + CopyStringArray(parameterObject["artist"], artists); + // Check for Musicbrainz ids + if (ParameterNotNull(parameterObject, "musicbrainzalbumartistid")) + CopyStringArray(parameterObject["musicbrainzalbumartistid"], mbids); + // When display artist is not provided and yet artists is changing make by concatenation + if (!ParameterNotNull(parameterObject, "displayartist")) + album.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + album.SetArtistCredits(artists, std::vector<std::string>(), mbids); + // On updatealbum artists will be changed + album.bArtistSongMerge = true; + } + + if (ParameterNotNull(parameterObject, "description")) + album.strReview = parameterObject["description"].asString(); + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], album.genre); + if (ParameterNotNull(parameterObject, "theme")) + CopyStringArray(parameterObject["theme"], album.themes); + if (ParameterNotNull(parameterObject, "mood")) + CopyStringArray(parameterObject["mood"], album.moods); + if (ParameterNotNull(parameterObject, "style")) + CopyStringArray(parameterObject["style"], album.styles); + if (ParameterNotNull(parameterObject, "type")) + album.strType = parameterObject["type"].asString(); + if (ParameterNotNull(parameterObject, "albumlabel")) + album.strLabel = parameterObject["albumlabel"].asString(); + if (ParameterNotNull(parameterObject, "rating")) + album.fRating = parameterObject["rating"].asFloat(); + if (ParameterNotNull(parameterObject, "userrating")) + album.iUserrating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "votes")) + album.iVotes = static_cast<int>(parameterObject["votes"].asInteger()); + if (ParameterNotNull(parameterObject, "year")) + album.strReleaseDate = parameterObject["year"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainzalbumid")) + album.strMusicBrainzAlbumID = parameterObject["musicbrainzalbumid"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainzreleasegroupid")) + album.strReleaseGroupMBID = parameterObject["musicbrainzreleasegroupid"].asString(); + if (ParameterNotNull(parameterObject, "isboxset")) + album.bBoxedSet = parameterObject["isboxset"].asBoolean(); + if (ParameterNotNull(parameterObject, "originaldate")) + album.strOrigReleaseDate = parameterObject["originaldate"].asString(); + if (ParameterNotNull(parameterObject, "releasedate")) + album.strReleaseDate = parameterObject["releasedate"].asString(); + if (ParameterNotNull(parameterObject, "albumstatus")) + album.strReleaseStatus = parameterObject["albumstatus"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + musicdatabase.GetArtForItem(album.idAlbum, MediaTypeAlbum, album.art); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + album.art[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + album.art.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + // Remove null art now, as not done by update + if (!musicdatabase.RemoveArtForItem(album.idAlbum, MediaTypeAlbum, removedArtwork)) + return InternalError; + } + + // Update artist including adding or replacing (but not removing) art + if (!musicdatabase.UpdateAlbum(album)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["songid"].asInteger(); + + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return InternalError; + + CSong song; + if (!musicdatabase.GetSong(id, song) || song.idSong != id) + return InvalidParams; + + if (ParameterNotNull(parameterObject, "title")) + song.strTitle = parameterObject["title"].asString(); + + if (ParameterNotNull(parameterObject, "displayartist")) + song.strArtistDesc = parameterObject["displayartist"].asString(); + // Set album sort string before processing artist credits + if (ParameterNotNull(parameterObject, "sortartist")) + song.strArtistSort = parameterObject["sortartist"].asString(); + + // Match up artist names and mbids to make new artist credits + // Mbid values only apply if there are names + bool updateartists = false; + if (ParameterNotNull(parameterObject, "artist")) + { + std::vector<std::string> artists, mbids; + updateartists = true; + CopyStringArray(parameterObject["artist"], artists); + // Check for Musicbrainz ids + if (ParameterNotNull(parameterObject, "musicbrainzartistid")) + CopyStringArray(parameterObject["musicbrainzartistid"], mbids); + // When display artist is not provided and yet artists is changing make by concatenation + if (!ParameterNotNull(parameterObject, "displayartist")) + song.strArtistDesc = StringUtils::Join(artists, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + song.SetArtistCredits(artists, std::vector<std::string>(), mbids); + } + + if (ParameterNotNull(parameterObject, "genre")) + CopyStringArray(parameterObject["genre"], song.genre); + if (ParameterNotNull(parameterObject, "year")) + song.strReleaseDate = parameterObject["year"].asString(); + if (ParameterNotNull(parameterObject, "rating")) + song.rating = parameterObject["rating"].asFloat(); + if (ParameterNotNull(parameterObject, "userrating")) + song.userrating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "track")) + song.iTrack = (song.iTrack & 0xffff0000) | ((int)parameterObject["track"].asInteger() & 0xffff); + if (ParameterNotNull(parameterObject, "disc")) + song.iTrack = (song.iTrack & 0xffff) | ((int)parameterObject["disc"].asInteger() << 16); + if (ParameterNotNull(parameterObject, "duration")) + song.iDuration = (int)parameterObject["duration"].asInteger(); + if (ParameterNotNull(parameterObject, "comment")) + song.strComment = parameterObject["comment"].asString(); + if (ParameterNotNull(parameterObject, "musicbrainztrackid")) + song.strMusicBrainzTrackID = parameterObject["musicbrainztrackid"].asString(); + if (ParameterNotNull(parameterObject, "playcount")) + song.iTimesPlayed = static_cast<int>(parameterObject["playcount"].asInteger()); + if (ParameterNotNull(parameterObject, "lastplayed")) + song.lastPlayed.SetFromDBDateTime(parameterObject["lastplayed"].asString()); + if (ParameterNotNull(parameterObject, "mood")) + song.strMood = parameterObject["mood"].asString(); + if (ParameterNotNull(parameterObject, "disctitle")) + song.strDiscSubtitle = parameterObject["disctitle"].asString(); + if (ParameterNotNull(parameterObject, "bpm")) + song.iBPM = static_cast<int>(parameterObject["bpm"].asInteger()); + if (ParameterNotNull(parameterObject, "originaldate")) + song.strOrigReleaseDate = parameterObject["originaldate"].asString(); + if (ParameterNotNull(parameterObject, "albumreleasedate")) + song.strReleaseDate = parameterObject["albumreleasedate"].asString(); + + // Update existing art. Any existing artwork that isn't specified in this request stays as is. + // If the value is null then the existing art with that type is removed. + if (ParameterNotNull(parameterObject, "art")) + { + // Get current artwork + std::map<std::string, std::string> artwork; + musicdatabase.GetArtForItem(song.idSong, MediaTypeSong, artwork); + + std::set<std::string> removedArtwork; + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + else if (artIt->second.isNull()) + { + artwork.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + //Update artwork, not done in update song + musicdatabase.SetArtForItem(song.idSong, MediaTypeSong, artwork); + if (!musicdatabase.RemoveArtForItem(song.idSong, MediaTypeSong, removedArtwork)) + return InternalError; + } + + // Update song (not including artwork) + if (!musicdatabase.UpdateSong(song, updateartists)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd = + StringUtils::Format("updatelibrary(music, {}, {})", StringUtils::Paramify(directory), + parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd; + if (parameterObject["options"].isMember("path")) + cmd = StringUtils::Format("exportlibrary2(music, singlefile, {}, albums, albumartists)", + StringUtils::Paramify(parameterObject["options"]["path"].asString())); + else + { + cmd = "exportlibrary2(music, library, dummy, albums, albumartists"; + if (parameterObject["options"]["images"].isBoolean() && + parameterObject["options"]["images"].asBoolean() == true) + cmd += ", artwork"; + if (parameterObject["options"]["overwrite"].isBoolean() && + parameterObject["options"]["overwrite"].asBoolean() == true) + cmd += ", overwrite"; + cmd += ")"; + } + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CAudioLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd = StringUtils::Format( + "cleanlibrary(music, {})", parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +bool CAudioLibrary::FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + CMusicDatabase musicdatabase; + if (strFilename.empty()) + return false; + + bool filled = false; + if (musicdatabase.Open()) + { + if (CDirectory::Exists(strFilename)) + { + CAlbum album; + int albumid = musicdatabase.GetAlbumIdByPath(strFilename); + if (musicdatabase.GetAlbum(albumid, album, false)) + { + item->SetFromAlbum(album); + FillItemArtistIDs(album.GetArtistIDArray(), item); + + CFileItemList items; + items.Add(item); + + if (GetAdditionalAlbumDetails(parameterObject, items, musicdatabase) == OK) + filled = true; + } + } + else + { + CSong song; + if (musicdatabase.GetSongByFileName(strFilename, song)) + { + item->SetFromSong(song); + FillItemArtistIDs(song.GetArtistIDArray(), item); + + CFileItemList items; + items.Add(item); + if (GetAdditionalSongDetails(parameterObject, items, musicdatabase) == OK) + filled = true; + } + } + } + + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(strFilename, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(strFilename)); + } + + return filled; +} + +bool CAudioLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CMusicDatabase musicdatabase; + if (!musicdatabase.Open()) + return false; + + std::string file = parameterObject["file"].asString(); + int artistID = (int)parameterObject["artistid"].asInteger(-1); + int albumID = (int)parameterObject["albumid"].asInteger(-1); + int genreID = (int)parameterObject["genreid"].asInteger(-1); + + bool success = false; + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(file, fileItem, parameterObject)) + { + success = true; + list.Add(fileItem); + } + + if (artistID != -1 || albumID != -1 || genreID != -1) + success |= musicdatabase.GetSongsNav("musicdb://songs/", list, genreID, artistID, albumID); + + int songID = (int)parameterObject["songid"].asInteger(-1); + if (songID != -1) + { + CSong song; + if (musicdatabase.GetSong(songID, song)) + { + list.Add(CFileItemPtr(new CFileItem(song))); + success = true; + } + } + + if (success) + { + // If we retrieved the list of songs by "artistid" + // we sort by album (and implicitly by track number) + if (artistID != -1) + list.Sort(SortByAlbum, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + // If we retrieve the list of songs by "genreid" + // we sort by artist (and implicitly by album and track number) + else if (genreID != -1) + list.Sort(SortByArtist, SortOrderAscending, CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_IGNORETHEWHENSORTING) ? SortAttributeIgnoreArticle : SortAttributeNone); + // otherwise we sort by track number + else + list.Sort(SortByTrackNumber, SortOrderAscending); + + } + + return success; +} + +void CAudioLibrary::FillItemArtistIDs(const std::vector<int>& artistids, + std::shared_ptr<CFileItem>& item) +{ + // Add artistIds as separate property as not part of CMusicInfoTag + CVariant artistidObj(CVariant::VariantTypeArray); + for (const auto& artistid : artistids) + artistidObj.push_back(artistid); + + item->SetProperty("artistid", artistidObj); +} + +void CAudioLibrary::FillAlbumItem(const CAlbum& album, + const std::string& path, + std::shared_ptr<CFileItem>& item) +{ + item = CFileItemPtr(new CFileItem(path, album)); + // Add album artistIds as separate property as not part of CMusicInfoTag + std::vector<int> artistids = album.GetArtistIDArray(); + FillItemArtistIDs(artistids, item); +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalDetails(const CVariant ¶meterObject, CFileItemList &items) +{ + if (items.IsEmpty()) + return OK; + + CMusicDatabase musicdb; + if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeArtist)) + return GetAdditionalArtistDetails(parameterObject, items, musicdb); + else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeAlbum)) + return GetAdditionalAlbumDetails(parameterObject, items, musicdb); + else if (CMediaTypes::IsMediaType(items.GetContent(), MediaTypeSong)) + return GetAdditionalSongDetails(parameterObject, items, musicdb); + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalArtistDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("roles"); + checkProperties.insert("songgenres"); + checkProperties.insert("isalbumartist"); + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + if (additionalProperties.find("roles") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetRolesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("songgenres") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetGenresByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("isalbumartist") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetIsAlbumArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetSourcesByArtist(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalAlbumDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("songgenres"); + checkProperties.insert("sourceid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + if (additionalProperties.find("songgenres") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetGenresByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + musicdatabase.GetSourcesByAlbum(item->GetMusicInfoTag()->GetDatabaseId(), item.get()); + } + } + + return OK; +} + +JSONRPC_STATUS CAudioLibrary::GetAdditionalSongDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase) +{ + if (!musicdatabase.Open()) + return InternalError; + + std::set<std::string> checkProperties; + checkProperties.insert("genreid"); + checkProperties.insert("sourceid"); + // Query (songview join songartistview) returns song.strAlbumArtists = CMusicInfoTag.m_strAlbumArtistDesc only + // Actual album artist data, if required, comes from album_artist and artist tables. + // It may differ from just splitting album artist description string + checkProperties.insert("albumartist"); + checkProperties.insert("albumartistid"); + checkProperties.insert("musicbrainzalbumartistid"); + std::set<std::string> additionalProperties; + if (!CheckForAdditionalProperties(parameterObject["properties"], checkProperties, additionalProperties)) + return OK; + + for (int i = 0; i < items.Size(); i++) + { + CFileItemPtr item = items[i]; + if (additionalProperties.find("genreid") != additionalProperties.end()) + { + std::vector<int> genreids; + if (musicdatabase.GetGenresBySong(item->GetMusicInfoTag()->GetDatabaseId(), genreids)) + { + CVariant genreidObj(CVariant::VariantTypeArray); + for (const auto& genreid : genreids) + genreidObj.push_back(genreid); + + item->SetProperty("genreid", genreidObj); + } + } + if (additionalProperties.find("sourceid") != additionalProperties.end()) + { + musicdatabase.GetSourcesBySong(item->GetMusicInfoTag()->GetDatabaseId(), item->GetPath(), item.get()); + } + if (item->GetMusicInfoTag()->GetAlbumId() > 0) + { + if (additionalProperties.find("albumartist") != additionalProperties.end() || + additionalProperties.find("albumartistid") != additionalProperties.end() || + additionalProperties.find("musicbrainzalbumartistid") != additionalProperties.end()) + { + musicdatabase.GetArtistsByAlbum(item->GetMusicInfoTag()->GetAlbumId(), item.get()); + } + } + } + + return OK; +} + +bool CAudioLibrary::CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties) +{ + if (!properties.isArray() || properties.empty()) + return false; + + std::set<std::string> checkingProperties = checkProperties; + for (CVariant::const_iterator_array itr = properties.begin_array(); + itr != properties.end_array() && !checkingProperties.empty(); ++itr) + { + if (!itr->isString()) + continue; + + std::string property = itr->asString(); + if (checkingProperties.find(property) != checkingProperties.end()) + { + checkingProperties.erase(property); + foundProperties.insert(property); + } + } + + return !foundProperties.empty(); +} diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.h b/xbmc/interfaces/json-rpc/AudioLibrary.h new file mode 100644 index 0000000..9946f9d --- /dev/null +++ b/xbmc/interfaces/json-rpc/AudioLibrary.h @@ -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. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +#include <memory> +#include <set> +#include <string> +#include <vector> + +class CAlbum; +class CFileitem; +class CFileitemList; +class CMusicDatabase; +class CVariant; + +namespace JSONRPC +{ + class CAudioLibrary : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetArtists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRoles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSources(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + static JSONRPC_STATUS GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + + static JSONRPC_STATUS GetRecentlyAddedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyPlayedAlbums(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyPlayedSongs(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetArtistDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetAlbumDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSongDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + + static JSONRPC_STATUS GetAdditionalDetails(const CVariant ¶meterObject, CFileItemList &items); + static JSONRPC_STATUS GetAdditionalArtistDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + static JSONRPC_STATUS GetAdditionalAlbumDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + static JSONRPC_STATUS GetAdditionalSongDetails(const CVariant& parameterObject, + const CFileItemList& items, + CMusicDatabase& musicdatabase); + + private: + static void FillAlbumItem(const CAlbum& album, + const std::string& path, + std::shared_ptr<CFileItem>& item); + static void FillItemArtistIDs(const std::vector<int>& artistids, + std::shared_ptr<CFileItem>& item); + + static bool CheckForAdditionalProperties(const CVariant &properties, const std::set<std::string> &checkProperties, std::set<std::string> &foundProperties); + }; +} diff --git a/xbmc/interfaces/json-rpc/CMakeLists.txt b/xbmc/interfaces/json-rpc/CMakeLists.txt new file mode 100644 index 0000000..59ced89 --- /dev/null +++ b/xbmc/interfaces/json-rpc/CMakeLists.txt @@ -0,0 +1,48 @@ +set(SOURCES AddonsOperations.cpp + ApplicationOperations.cpp + AudioLibrary.cpp + FavouritesOperations.cpp + FileItemHandler.cpp + FileOperations.cpp + GUIOperations.cpp + InputOperations.cpp + JSONRPC.cpp + JSONServiceDescription.cpp + JSONUtils.cpp + PlayerOperations.cpp + PlaylistOperations.cpp + ProfilesOperations.cpp + PVROperations.cpp + SettingsOperations.cpp + SystemOperations.cpp + TextureOperations.cpp + VideoLibrary.cpp + XBMCOperations.cpp) + +set(HEADERS AddonsOperations.h + ApplicationOperations.h + AudioLibrary.h + FavouritesOperations.h + FileItemHandler.h + FileOperations.h + GUIOperations.h + IClient.h + IJSONRPCAnnouncer.h + InputOperations.h + ITransportLayer.h + JSONRPC.h + JSONRPCUtils.h + JSONServiceDescription.h + JSONUtils.h + PlayerOperations.h + PlaylistOperations.h + ProfilesOperations.h + PVROperations.h + SettingsOperations.h + SystemOperations.h + TextureOperations.h + VideoLibrary.h + XBMCOperations.h) + +core_add_library(jsonrpc_interface) +add_dependencies(${CORE_LIBRARY} generate_json_header) diff --git a/xbmc/interfaces/json-rpc/FavouritesOperations.cpp b/xbmc/interfaces/json-rpc/FavouritesOperations.cpp new file mode 100644 index 0000000..99277e5 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FavouritesOperations.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FavouritesOperations.h" + +#include "ServiceBroker.h" +#include "favourites/FavouritesService.h" +#include "favourites/FavouritesURL.h" +#include "guilib/WindowIDs.h" +#include "input/WindowTranslator.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" + +#include <vector> + +using namespace JSONRPC; + +JSONRPC_STATUS CFavouritesOperations::GetFavourites(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList favourites; + CServiceBroker::GetFavouritesService().GetAll(favourites); + + std::string type = !parameterObject["type"].isNull() ? parameterObject["type"].asString() : ""; + + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + for (int i = 0; i < favourites.Size(); i++) + { + CVariant object; + CFileItemPtr item = favourites.Get(i); + + const CFavouritesURL url(item->GetPath()); + if (!url.IsValid()) + continue; + + const CFavouritesURL::Action function = url.GetAction(); + + object["title"] = item->GetLabel(); + if (fields.find("thumbnail") != fields.end()) + object["thumbnail"] = item->GetArt("thumb"); + + if (function == CFavouritesURL::Action::ACTIVATE_WINDOW) + { + object["type"] = "window"; + if (fields.find("window") != fields.end()) + { + object["window"] = CWindowTranslator::TranslateWindow(url.GetWindowID()); + } + if (fields.find("windowparameter") != fields.end()) + { + object["windowparameter"] = url.GetTarget(); + } + } + else if (function == CFavouritesURL::Action::PLAY_MEDIA) + { + object["type"] = "media"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else if (function == CFavouritesURL::Action::RUN_SCRIPT) + { + object["type"] = "script"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else if (function == CFavouritesURL::Action::START_ANDROID_ACTIVITY) + { + object["type"] = "androidapp"; + if (fields.find("path") != fields.end()) + object["path"] = url.GetTarget(); + } + else + object["type"] = "unknown"; + + if (type.empty() || type.compare(object["type"].asString()) == 0) + result["favourites"].append(object); + } + + int start, end; + HandleLimits(parameterObject, result, result["favourites"].size(), start, end); + + return OK; +} + +JSONRPC_STATUS CFavouritesOperations::AddFavourite(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string type = parameterObject["type"].asString(); + + if (type.compare("unknown") == 0) + return InvalidParams; + + if ((type.compare("media") == 0 || type.compare("script") == 0 || type.compare("androidapp") == 0) && !ParameterNotNull(parameterObject, "path")) + { + result["method"] = "Favourites.AddFavourite"; + result["stack"]["message"] = "Missing parameter"; + result["stack"]["name"] = "path"; + result["stack"]["type"] = "string"; + return InvalidParams; + } + + if (type.compare("window") == 0 && !ParameterNotNull(parameterObject, "window")) + { + result["method"] = "Favourites.AddFavourite"; + result["stack"]["message"] = "Missing parameter"; + result["stack"]["name"] = "window"; + result["stack"]["type"] = "string"; + return InvalidParams; + } + + std::string title = parameterObject["title"].asString(); + std::string path = parameterObject["path"].asString(); + + CFileItem item; + int contextWindow = 0; + if (type.compare("window") == 0) + { + item = CFileItem(parameterObject["windowparameter"].asString(), true); + contextWindow = CWindowTranslator::TranslateWindow(parameterObject["window"].asString()); + if (contextWindow == WINDOW_INVALID) + return InvalidParams; + } + else if (type.compare("script") == 0) + { + if (!URIUtils::IsScript(path)) + path = "script://" + path; + item = CFileItem(path, false); + } + else if (type.compare("androidapp") == 0) + { + if (!URIUtils::IsAndroidApp(path)) + path = "androidapp://" + path; + item = CFileItem(path, false); + } + else if (type.compare("media") == 0) + { + item = CFileItem(path, false); + } + else + return InvalidParams; + + item.SetLabel(title); + if (ParameterNotNull(parameterObject,"thumbnail")) + item.SetArt("thumb", parameterObject["thumbnail"].asString()); + + if (CServiceBroker::GetFavouritesService().AddOrRemove(item, contextWindow)) + return ACK; + else + return FailedToExecute; +} diff --git a/xbmc/interfaces/json-rpc/FavouritesOperations.h b/xbmc/interfaces/json-rpc/FavouritesOperations.h new file mode 100644 index 0000000..bb24b61 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FavouritesOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CFavouritesOperations : public CJSONUtils + { + public: + static JSONRPC_STATUS GetFavourites(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS AddFavourite(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/FileItemHandler.cpp b/xbmc/interfaces/json-rpc/FileItemHandler.cpp new file mode 100644 index 0000000..9d649d3 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileItemHandler.cpp @@ -0,0 +1,554 @@ +/* + * 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 "FileItemHandler.h" + +#include "AudioLibrary.h" +#include "FileOperations.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "addons/kodi-dev-kit/include/kodi/c-api/addon-instance/pvr/pvr_epg.h" // EPG_TAG_INVALID_UID +#include "filesystem/Directory.h" +#include "music/MusicThumbLoader.h" +#include "music/tags/MusicInfoTag.h" +#include "pictures/PictureInfoTag.h" +#include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimers.h" +#include "utils/FileUtils.h" +#include "utils/ISerializable.h" +#include "utils/SortUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoTag.h" +#include "video/VideoThumbLoader.h" + +#include <map> +#include <string.h> + +using namespace MUSIC_INFO; +using namespace JSONRPC; +using namespace XFILE; + +bool CFileItemHandler::GetField(const std::string& field, + const CVariant& info, + const std::shared_ptr<CFileItem>& item, + CVariant& result, + bool& fetchedArt, + CThumbLoader* thumbLoader /* = NULL */) +{ + if (result.isMember(field) && !result[field].empty()) + return true; + + // overwrite serialized values + if (item) + { + if (field == "mimetype" && item->GetMimeType().empty()) + { + item->FillInMimeType(false); + result[field] = item->GetMimeType(); + return true; + } + + if (item->HasPVRChannelInfoTag()) + { + // Translate PVR.Details.Broadcast -> List.Item.Base format + if (field == "cast") + { + // string -> Video.Cast + const std::vector<std::string> actors = + StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR); + + result[field] = CVariant(CVariant::VariantTypeArray); + for (const auto& actor : actors) + { + CVariant actorVar; + actorVar["name"] = actor; + result[field].push_back(actorVar); + } + return true; + } + else if (field == "director" || field == "writer") + { + // string -> Array.String + result[field] = StringUtils::Split(info[field].asString(), EPG_STRING_TOKEN_SEPARATOR); + return true; + } + else if (field == "isrecording") + { + result[field] = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel( + *item->GetPVRChannelInfoTag()); + return true; + } + } + + if (item->HasEPGInfoTag()) + { + if (field == "hastimer") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer != nullptr); + return true; + } + else if (field == "hasreminder") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer && timer->IsReminder()); + return true; + } + else if (field == "hastimerrule") + { + const std::shared_ptr<PVR::CPVRTimerInfoTag> timer = + CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(item->GetEPGInfoTag()); + result[field] = (timer && timer->HasParent()); + return true; + } + else if (field == "hasrecording") + { + const std::shared_ptr<PVR::CPVRRecording> recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag( + item->GetEPGInfoTag()); + result[field] = (recording != nullptr); + return true; + } + else if (field == "recording") + { + const std::shared_ptr<PVR::CPVRRecording> recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag( + item->GetEPGInfoTag()); + result[field] = recording ? recording->m_strFileNameAndPath : ""; + return true; + } + } + } + + // check for serialized values + if (info.isMember(field) && !info[field].isNull()) + { + result[field] = info[field]; + return true; + } + + // check if the field requires special handling + if (item) + { + if (item->IsAlbum()) + { + if (field == "albumlabel") + { + result[field] = item->GetProperty("album_label"); + return true; + } + if (item->HasProperty("album_" + field + "_array")) + { + result[field] = item->GetProperty("album_" + field + "_array"); + return true; + } + if (item->HasProperty("album_" + field)) + { + result[field] = item->GetProperty("album_" + field); + return true; + } + } + + if (item->HasProperty("artist_" + field + "_array")) + { + result[field] = item->GetProperty("artist_" + field + "_array"); + return true; + } + if (item->HasProperty("artist_" + field)) + { + result[field] = item->GetProperty("artist_" + field); + return true; + } + + if (field == "art") + { + if (thumbLoader && !item->GetProperty("libraryartfilled").asBoolean() && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || + (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + + CGUIListItem::ArtMap artMap = item->GetArt(); + CVariant artObj(CVariant::VariantTypeObject); + for (const auto& artIt : artMap) + { + if (!artIt.second.empty()) + artObj[artIt.first] = CTextureUtils::GetWrappedImageURL(artIt.second); + } + + result["art"] = artObj; + return true; + } + + if (field == "thumbnail") + { + if (thumbLoader != NULL && !item->HasArt("thumb") && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + else if (item->HasPictureInfoTag() && !item->HasArt("thumb")) + item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(item->GetPath())); + + if (item->HasArt("thumb")) + result["thumbnail"] = CTextureUtils::GetWrappedImageURL(item->GetArt("thumb")); + else + result["thumbnail"] = ""; + + return true; + } + + if (field == "fanart") + { + if (thumbLoader != NULL && !item->HasArt("fanart") && !fetchedArt && + ((item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > -1) || (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > -1))) + { + thumbLoader->FillLibraryArt(*item); + fetchedArt = true; + } + + if (item->HasArt("fanart")) + result["fanart"] = CTextureUtils::GetWrappedImageURL(item->GetArt("fanart")); + else + result["fanart"] = ""; + + return true; + } + + if (item->HasVideoInfoTag() && item->GetVideoContentType() == VideoDbContentType::TVSHOWS) + { + if (item->GetVideoInfoTag()->m_iSeason < 0 && field == "season") + { + result[field] = (int)item->GetProperty("totalseasons").asInteger(); + return true; + } + if (field == "watchedepisodes") + { + result[field] = (int)item->GetProperty("watchedepisodes").asInteger(); + return true; + } + } + + if (item->HasProperty(field)) + { + result[field] = item->GetProperty(field); + return true; + } + } + + return false; +} + +void CFileItemHandler::FillDetails(const ISerializable* info, + const std::shared_ptr<CFileItem>& item, + std::set<std::string>& fields, + CVariant& result, + CThumbLoader* thumbLoader /* = NULL */) +{ + if (info == NULL || fields.empty()) + return; + + CVariant serialization; + info->Serialize(serialization); + + bool fetchedArt = false; + + std::set<std::string> originalFields = fields; + + for (const auto& fieldIt : originalFields) + { + if (GetField(fieldIt, serialization, item, result, fetchedArt, thumbLoader) && + result.isMember(fieldIt) && !result[fieldIt].empty()) + fields.erase(fieldIt); + } +} + +void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool sortLimit /* = true */) +{ + HandleFileItemList(ID, allowFile, resultname, items, parameterObject, result, items.Size(), sortLimit); +} + +void CFileItemHandler::HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, int size, bool sortLimit /* = true */) +{ + int start, end; + HandleLimits(parameterObject, result, size, start, end); + + if (sortLimit) + Sort(items, parameterObject); + else + { + start = 0; + end = items.Size(); + } + + CThumbLoader *thumbLoader = NULL; + if (end - start > 0) + { + if (items.Get(start)->HasVideoInfoTag()) + thumbLoader = new CVideoThumbLoader(); + else if (items.Get(start)->HasMusicInfoTag()) + thumbLoader = new CMusicThumbLoader(); + + if (thumbLoader != NULL) + thumbLoader->OnLoaderStart(); + } + + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + result[resultname].reserve(static_cast<size_t>(end - start)); + for (int i = start; i < end; i++) + { + CFileItemPtr item = items.Get(i); + HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, true, thumbLoader); + } + + delete thumbLoader; +} + +void CFileItemHandler::HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const CVariant& validFields, + CVariant& result, + bool append /* = true */, + CThumbLoader* thumbLoader /* = NULL */) +{ + std::set<std::string> fields; + if (parameterObject.isMember("properties") && parameterObject["properties"].isArray()) + { + for (CVariant::const_iterator_array field = parameterObject["properties"].begin_array(); + field != parameterObject["properties"].end_array(); ++field) + fields.insert(field->asString()); + } + + HandleFileItem(ID, allowFile, resultname, item, parameterObject, fields, result, append, thumbLoader); +} + +void CFileItemHandler::HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const std::set<std::string>& validFields, + CVariant& result, + bool append /* = true */, + CThumbLoader* thumbLoader /* = NULL */) +{ + CVariant object; + std::set<std::string> fields(validFields.begin(), validFields.end()); + + if (item.get()) + { + std::set<std::string>::const_iterator fileField = fields.find("file"); + if (fileField != fields.end()) + { + if (allowFile) + { + if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->GetPath().empty()) + object["file"] = item->GetVideoInfoTag()->GetPath().c_str(); + if (item->HasMusicInfoTag() && !item->GetMusicInfoTag()->GetURL().empty()) + object["file"] = item->GetMusicInfoTag()->GetURL().c_str(); + if (item->HasPVRTimerInfoTag() && !item->GetPVRTimerInfoTag()->Path().empty()) + object["file"] = item->GetPVRTimerInfoTag()->Path().c_str(); + + if (!object.isMember("file")) + object["file"] = item->GetDynPath().c_str(); + } + fields.erase(fileField); + } + + fileField = fields.find("mediapath"); + if (fileField != fields.end()) + { + object["mediapath"] = item->GetPath().c_str(); + fields.erase(fileField); + } + + fileField = fields.find("dynpath"); + if (fileField != fields.end()) + { + object["dynpath"] = item->GetDynPath().c_str(); + fields.erase(fileField); + } + + if (ID) + { + if (item->HasPVRChannelInfoTag() && item->GetPVRChannelInfoTag()->ChannelID() > 0) + object[ID] = item->GetPVRChannelInfoTag()->ChannelID(); + else if (item->HasEPGInfoTag() && item->GetEPGInfoTag()->DatabaseID() > 0) + object[ID] = item->GetEPGInfoTag()->DatabaseID(); + else if (item->HasPVRRecordingInfoTag() && item->GetPVRRecordingInfoTag()->RecordingID() > 0) + object[ID] = item->GetPVRRecordingInfoTag()->RecordingID(); + else if (item->HasPVRTimerInfoTag() && item->GetPVRTimerInfoTag()->TimerID() > 0) + object[ID] = item->GetPVRTimerInfoTag()->TimerID(); + else if (item->HasMusicInfoTag() && item->GetMusicInfoTag()->GetDatabaseId() > 0) + object[ID] = item->GetMusicInfoTag()->GetDatabaseId(); + else if (item->HasVideoInfoTag() && item->GetVideoInfoTag()->m_iDbId > 0) + object[ID] = item->GetVideoInfoTag()->m_iDbId; + + if (StringUtils::CompareNoCase(ID, "id") == 0) + { + if (item->HasPVRChannelInfoTag()) + object["type"] = "channel"; + else if (item->HasPVRRecordingInfoTag()) + object["type"] = "recording"; + else if (item->HasMusicInfoTag()) + { + std::string type = item->GetMusicInfoTag()->GetType(); + if (type == MediaTypeAlbum || type == MediaTypeSong || type == MediaTypeArtist) + object["type"] = type; + else if (!item->m_bIsFolder) + object["type"] = MediaTypeSong; + } + else if (item->HasVideoInfoTag() && !item->GetVideoInfoTag()->m_type.empty()) + { + std::string type = item->GetVideoInfoTag()->m_type; + if (type == MediaTypeMovie || type == MediaTypeTvShow || type == MediaTypeEpisode || type == MediaTypeMusicVideo) + object["type"] = type; + } + else if (item->HasPictureInfoTag()) + object["type"] = "picture"; + + if (!object.isMember("type")) + object["type"] = "unknown"; + + if (fields.find("filetype") != fields.end()) + { + if (item->m_bIsFolder) + object["filetype"] = "directory"; + else + object["filetype"] = "file"; + } + } + } + + bool deleteThumbloader = false; + if (thumbLoader == NULL) + { + if (item->HasVideoInfoTag()) + thumbLoader = new CVideoThumbLoader(); + else if (item->HasMusicInfoTag()) + thumbLoader = new CMusicThumbLoader(); + + if (thumbLoader != NULL) + { + deleteThumbloader = true; + thumbLoader->OnLoaderStart(); + } + } + + if (item->HasPVRChannelInfoTag()) + FillDetails(item->GetPVRChannelInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRChannelGroupMemberInfoTag()) + FillDetails(item->GetPVRChannelGroupMemberInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasEPGInfoTag()) + FillDetails(item->GetEPGInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRRecordingInfoTag()) + FillDetails(item->GetPVRRecordingInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasPVRTimerInfoTag()) + FillDetails(item->GetPVRTimerInfoTag().get(), item, fields, object, thumbLoader); + if (item->HasVideoInfoTag()) + FillDetails(item->GetVideoInfoTag(), item, fields, object, thumbLoader); + if (item->HasMusicInfoTag()) + FillDetails(item->GetMusicInfoTag(), item, fields, object, thumbLoader); + if (item->HasPictureInfoTag()) + FillDetails(item->GetPictureInfoTag(), item, fields, object, thumbLoader); + + FillDetails(item.get(), item, fields, object, thumbLoader); + + if (deleteThumbloader) + delete thumbLoader; + + object["label"] = item->GetLabel().c_str(); + } + else + object = CVariant(CVariant::VariantTypeNull); + + if (resultname) + { + if (append) + result[resultname].append(object); + else + result[resultname] = object; + } +} + +bool CFileItemHandler::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CAudioLibrary::FillFileItemList(parameterObject, list); + CVideoLibrary::FillFileItemList(parameterObject, list); + CFileOperations::FillFileItemList(parameterObject, list); + + std::string file = parameterObject["file"].asString(); + if (!file.empty() && + (URIUtils::IsURL(file) || (CFileUtils::Exists(file) && !CDirectory::Exists(file)))) + { + bool added = false; + for (int index = 0; index < list.Size(); index++) + { + if (list[index]->GetDynPath() == file || + list[index]->GetMusicInfoTag()->GetURL() == file || list[index]->GetVideoInfoTag()->GetPath() == file) + { + added = true; + break; + } + } + + if (!added) + { + CFileItemPtr item = CFileItemPtr(new CFileItem(file, false)); + if (item->IsPicture()) + { + CPictureInfoTag picture; + picture.Load(item->GetPath()); + *item->GetPictureInfoTag() = picture; + } + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(file, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(file)); + } + list.Add(item); + } + } + + return (list.Size() > 0); +} + +void CFileItemHandler::Sort(CFileItemList &items, const CVariant ¶meterObject) +{ + SortDescription sorting; + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return; + + items.Sort(sorting); +} diff --git a/xbmc/interfaces/json-rpc/FileItemHandler.h b/xbmc/interfaces/json-rpc/FileItemHandler.h new file mode 100644 index 0000000..ba7e636 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileItemHandler.h @@ -0,0 +1,64 @@ +/* + * 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 "JSONRPC.h" +#include "JSONUtils.h" + +#include <memory> +#include <set> + +class CFileItem; +class CFileItemList; +class CThumbLoader; +class CVariant; +class ISerializable; + +namespace JSONRPC +{ + class CFileItemHandler : public CJSONUtils + { + protected: + static void FillDetails(const ISerializable* info, + const std::shared_ptr<CFileItem>& item, + std::set<std::string>& fields, + CVariant& result, + CThumbLoader* thumbLoader = nullptr); + static void HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool sortLimit = true); + static void HandleFileItemList(const char *ID, bool allowFile, const char *resultname, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, int size, bool sortLimit = true); + static void HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const CVariant& validFields, + CVariant& result, + bool append = true, + CThumbLoader* thumbLoader = nullptr); + static void HandleFileItem(const char* ID, + bool allowFile, + const char* resultname, + const std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject, + const std::set<std::string>& validFields, + CVariant& result, + bool append = true, + CThumbLoader* thumbLoader = nullptr); + + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + private: + static void Sort(CFileItemList &items, const CVariant& parameterObject); + static bool GetField(const std::string& field, + const CVariant& info, + const std::shared_ptr<CFileItem>& item, + CVariant& result, + bool& fetchedArt, + CThumbLoader* thumbLoader = nullptr); + }; +} diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp new file mode 100644 index 0000000..6329a78 --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2005-2020 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "FileOperations.h" + +#include "AudioLibrary.h" +#include "FileItem.h" +#include "MediaSource.h" +#include "ServiceBroker.h" +#include "URL.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "filesystem/Directory.h" +#include "media/MediaLockState.h" +#include "settings/AdvancedSettings.h" +#include "settings/MediaSourceSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" + +using namespace XFILE; +using namespace JSONRPC; + +JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + VECSOURCES *sources = CMediaSourceSettings::GetInstance().GetSources(media); + if (sources) + { + CFileItemList items; + for (unsigned int i = 0; i < (unsigned int)sources->size(); i++) + { + // Do not show sources which are locked + if (sources->at(i).m_iHasLock == LOCK_STATE_LOCKED) + continue; + + items.Add(CFileItemPtr(new CFileItem(sources->at(i)))); + } + + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (items[i]->IsSmb()) + { + CURL url(items[i]->GetPath()); + items[i]->SetPath(url.GetWithoutUserDetails()); + } + } + + CVariant param = parameterObject; + param["properties"] = CVariant(CVariant::VariantTypeArray); + param["properties"].append("file"); + + HandleFileItemList(NULL, true, "sources", items, param, result); + } + + return OK; +} + +JSONRPC_STATUS CFileOperations::GetDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + CFileItemList items; + std::string strPath = parameterObject["directory"].asString(); + + if (!CFileUtils::RemoteAccessAllowed(strPath)) + return InvalidParams; + + std::vector<std::string> regexps; + std::string extensions; + if (media == "video") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + items.SetProperty("set_videodb_details", + CVideoLibrary::GetDetailsFromJsonParameters(parameterObject)); + } + else if (media == "music") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + } + else if (media == "pictures") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + } + + if (CDirectory::GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS)) + { + // we might need to get additional information for music items + if (media == "music") + { + JSONRPC_STATUS status = CAudioLibrary::GetAdditionalDetails(parameterObject, items); + if (status != OK) + return status; + } + + CFileItemList filteredFiles; + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) + continue; + + if (items[i]->IsSmb()) + { + CURL url(items[i]->GetPath()); + items[i]->SetPath(url.GetWithoutUserDetails()); + } + + if ((media == "video" && items[i]->HasVideoInfoTag()) || + (media == "music" && items[i]->HasMusicInfoTag()) || + (media == "picture" && items[i]->HasPictureInfoTag()) || + media == "files" || + URIUtils::IsUPnP(items.GetPath())) + filteredFiles.Add(items[i]); + else + { + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(items[i], fileItem, media, parameterObject)) + filteredFiles.Add(fileItem); + else + filteredFiles.Add(items[i]); + } + } + + // Check if the "properties" list exists + // and make sure it contains the "file" and "filetype" + // fields + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + + bool hasFileField = false; + for (CVariant::const_iterator_array itr = param["properties"].begin_array(); + itr != param["properties"].end_array(); ++itr) + { + if (itr->asString().compare("file") == 0) + { + hasFileField = true; + break; + } + } + + if (!hasFileField) + param["properties"].append("file"); + param["properties"].append("filetype"); + + HandleFileItemList("id", true, "files", filteredFiles, param, result); + + return OK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CFileOperations::GetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string file = parameterObject["file"].asString(); + if (!CFileUtils::Exists(file)) + return InvalidParams; + + if (!CFileUtils::RemoteAccessAllowed(file)) + return InvalidParams; + + std::string path = URIUtils::GetDirectory(file); + + CFileItemList items; + if (path.empty()) + return InvalidParams; + + CFileItemPtr item; + if (CDirectory::GetDirectory(path, items, "", DIR_FLAG_DEFAULTS) && items.Contains(file)) + item = items.Get(file); + else + item = CFileItemPtr(new CFileItem(file, false)); + + if (!URIUtils::IsUPnP(file)) + FillFileItem(item, item, parameterObject["media"].asString(), parameterObject); + + // Check if the "properties" list exists + // and make sure it contains the "file" + // field + CVariant param = parameterObject; + if (!param.isMember("properties")) + param["properties"] = CVariant(CVariant::VariantTypeArray); + + bool hasFileField = false; + for (CVariant::const_iterator_array itr = param["properties"].begin_array(); + itr != param["properties"].end_array(); ++itr) + { + if (itr->asString().compare("file") == 0) + { + hasFileField = true; + break; + } + } + + if (!hasFileField) + param["properties"].append("file"); + param["properties"].append("filetype"); + + HandleFileItem("id", true, "filedetails", item, parameterObject, param["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CFileOperations::SetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + if (media.compare("video") != 0) + return InvalidParams; + + std::string file = parameterObject["file"].asString(); + if (!CFileUtils::Exists(file)) + return InvalidParams; + + if (!CFileUtils::RemoteAccessAllowed(file)) + return InvalidParams; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int fileId = videodatabase.AddFile(file); + + CVideoInfoTag infos; + if (!videodatabase.GetFileInfo("", infos, fileId)) + return InvalidParams; + + CDateTime lastPlayed = infos.m_lastPlayed; + int playcount = infos.GetPlayCount(); + if (!parameterObject["lastplayed"].isNull()) + { + lastPlayed.Reset(); + SetFromDBDateTime(parameterObject["lastplayed"], lastPlayed); + playcount = lastPlayed.IsValid() ? std::max(1, playcount) : 0; + } + if (!parameterObject["playcount"].isNull()) + playcount = parameterObject["playcount"].asInteger(); + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + videodatabase.SetPlayCount(CFileItem(infos), playcount, lastPlayed); + + CVideoLibrary::UpdateResumePoint(parameterObject, infos, videodatabase); + + videodatabase.GetFileInfo("", infos, fileId); + CJSONRPCUtils::NotifyItemUpdated(infos, std::map<std::string, std::string>{}); + return ACK; +} + +JSONRPC_STATUS CFileOperations::PrepareDownload(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string protocol; + if (transport->PrepareDownload(parameterObject["path"].asString().c_str(), result["details"], protocol)) + { + result["protocol"] = protocol; + + if ((transport->GetCapabilities() & FileDownloadDirect) == FileDownloadDirect) + result["mode"] = "direct"; + else + result["mode"] = "redirect"; + + return OK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CFileOperations::Download(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return transport->Download(parameterObject["path"].asString().c_str(), result) ? OK : InvalidParams; +} + +bool CFileOperations::FillFileItem( + const std::shared_ptr<CFileItem>& originalItem, + std::shared_ptr<CFileItem>& item, + const std::string& media /* = "" */, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + if (originalItem.get() == NULL) + return false; + + // copy all the available details + *item = *originalItem; + + bool status = false; + std::string strFilename = originalItem->GetPath(); + if (!strFilename.empty() && (CDirectory::Exists(strFilename) || CFileUtils::Exists(strFilename))) + { + if (media == "video") + status = CVideoLibrary::FillFileItem(strFilename, item, parameterObject); + else if (media == "music") + status = CAudioLibrary::FillFileItem(strFilename, item, parameterObject); + + if (status && item->GetLabel().empty()) + { + std::string label = originalItem->GetLabel(); + if (label.empty()) + { + bool isDir = CDirectory::Exists(strFilename); + label = CUtil::GetTitleFromPath(strFilename, isDir); + if (label.empty()) + label = URIUtils::GetFileName(strFilename); + } + + item->SetLabel(label); + } + else if (!status) + { + if (originalItem->GetLabel().empty()) + { + bool isDir = CDirectory::Exists(strFilename); + std::string label = CUtil::GetTitleFromPath(strFilename, isDir); + if (label.empty()) + return false; + + item->SetLabel(label); + item->SetPath(strFilename); + item->m_bIsFolder = isDir; + } + else + *item = *originalItem; + + status = true; + } + } + + return status; +} + +bool CFileOperations::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + if (parameterObject.isMember("directory")) + { + std::string media = parameterObject["media"].asString(); + StringUtils::ToLower(media); + + std::string strPath = parameterObject["directory"].asString(); + if (!strPath.empty()) + { + CFileItemList items; + std::string extensions; + std::vector<std::string> regexps; + + if (media == "video") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + } + else if (media == "music") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_audioExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + } + else if (media == "pictures") + { + regexps = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_pictureExcludeFromListingRegExps; + extensions = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + } + + CDirectory directory; + if (directory.GetDirectory(strPath, items, extensions, DIR_FLAG_DEFAULTS)) + { + // Sort folders and files by filename to avoid reverse item order bug on some platforms, + // but leave items from a playlist, smartplaylist or upnp container in order supplied + if (!items.IsPlayList() && !items.IsSmartPlayList() && !URIUtils::IsUPnP(items.GetPath())) + items.Sort(SortByFile, SortOrderAscending); + + CFileItemList filteredDirectories; + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + { + if (CUtil::ExcludeFileOrFolder(items[i]->GetPath(), regexps)) + continue; + + if (items[i]->m_bIsFolder) + filteredDirectories.Add(items[i]); + else if ((media == "video" && items[i]->HasVideoInfoTag()) || + (media == "music" && items[i]->HasMusicInfoTag())) + list.Add(items[i]); + else + { + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(items[i], fileItem, media, parameterObject)) + list.Add(fileItem); + else if (media == "files") + list.Add(items[i]); + } + } + + if (parameterObject.isMember("recursive") && parameterObject["recursive"].isBoolean()) + { + for (int i = 0; i < filteredDirectories.Size(); i++) + { + CVariant val = parameterObject; + val["directory"] = filteredDirectories[i]->GetPath(); + FillFileItemList(val, list); + } + } + + return true; + } + } + } + + return false; +} diff --git a/xbmc/interfaces/json-rpc/FileOperations.h b/xbmc/interfaces/json-rpc/FileOperations.h new file mode 100644 index 0000000..6af05bb --- /dev/null +++ b/xbmc/interfaces/json-rpc/FileOperations.h @@ -0,0 +1,39 @@ +/* + * 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 "FileItemHandler.h" +#include "JSONRPC.h" + +#include <memory> + +class CFileItem; +class CVariant; + +namespace JSONRPC +{ + class CFileOperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetRootDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetDirectory(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetFileDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS PrepareDownload(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Download(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::shared_ptr<CFileItem>& originalItem, + std::shared_ptr<CFileItem>& item, + const std::string& media = "", + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + }; +} diff --git a/xbmc/interfaces/json-rpc/GUIOperations.cpp b/xbmc/interfaces/json-rpc/GUIOperations.cpp new file mode 100644 index 0000000..06b1f75 --- /dev/null +++ b/xbmc/interfaces/json-rpc/GUIOperations.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2011-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "GUIOperations.h" + +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonType.h" +#include "application/Application.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/StereoscopicsManager.h" +#include "input/Key.h" +#include "input/WindowTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "rendering/RenderSystem.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace ADDON; + +JSONRPC_STATUS CGUIOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CGUIOperations::ActivateWindow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int iWindow = CWindowTranslator::TranslateWindow(parameterObject["window"].asString()); + if (iWindow != WINDOW_INVALID) + { + std::vector<std::string> params; + for (CVariant::const_iterator_array param = parameterObject["parameters"].begin_array(); + param != parameterObject["parameters"].end_array(); ++param) + { + if (param->isString() && !param->empty()) + params.push_back(param->asString()); + } + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTIVATE_WINDOW, iWindow, 0, nullptr, "", + params); + return ACK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CGUIOperations::ShowNotification(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string image = parameterObject["image"].asString(); + std::string title = parameterObject["title"].asString(); + std::string message = parameterObject["message"].asString(); + unsigned int displaytime = (unsigned int)parameterObject["displaytime"].asUnsignedInteger(); + + if (image.compare("info") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, title, message, displaytime); + else if (image.compare("warning") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, title, message, displaytime); + else if (image.compare("error") == 0) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, title, message, displaytime); + else + CGUIDialogKaiToast::QueueNotification(image, title, message, displaytime); + + return ACK; +} + +JSONRPC_STATUS CGUIOperations::SetFullscreen(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if ((parameterObject["fullscreen"].isString() && + parameterObject["fullscreen"].asString().compare("toggle") == 0) || + (parameterObject["fullscreen"].isBoolean() && + parameterObject["fullscreen"].asBoolean() != g_application.IsFullScreen())) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(ACTION_SHOW_GUI))); + } + else if (!parameterObject["fullscreen"].isBoolean() && !parameterObject["fullscreen"].isString()) + return InvalidParams; + + return GetPropertyValue("fullscreen", result); +} + +JSONRPC_STATUS CGUIOperations::SetStereoscopicMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CAction action = CStereoscopicsManager::ConvertActionCommandToAction("SetStereoMode", parameterObject["mode"].asString()); + if (action.GetID() != ACTION_NONE) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(action))); + return ACK; + } + + return InvalidParams; +} + +JSONRPC_STATUS CGUIOperations::GetStereoscopicModes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + for (int i = RENDER_STEREO_MODE_OFF; i < RENDER_STEREO_MODE_COUNT; i++) + { + RENDER_STEREO_MODE mode = (RENDER_STEREO_MODE) i; + if (CServiceBroker::GetRenderSystem()->SupportsStereo(mode)) + result["stereoscopicmodes"].push_back(GetStereoModeObjectFromGuiMode(mode)); + } + + return OK; +} + +JSONRPC_STATUS CGUIOperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + if (property == "currentwindow") + { + result["label"] = CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + CServiceBroker::GetGUI()->GetInfoManager().TranslateString("System.CurrentWindow"), + INFO::DEFAULT_CONTEXT); + result["id"] = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog(); + } + else if (property == "currentcontrol") + result["label"] = CServiceBroker::GetGUI()->GetInfoManager().GetLabel( + CServiceBroker::GetGUI()->GetInfoManager().TranslateString("System.CurrentControl"), + INFO::DEFAULT_CONTEXT); + else if (property == "skin") + { + std::string skinId = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN); + AddonPtr addon; + if (!CServiceBroker::GetAddonMgr().GetAddon(skinId, addon, AddonType::SKIN, + OnlyEnabled::CHOICE_YES)) + return InternalError; + + result["id"] = skinId; + if (addon.get()) + result["name"] = addon->Name(); + } + else if (property == "fullscreen") + result = g_application.IsFullScreen(); + else if (property == "stereoscopicmode") + { + const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager(); + + result = GetStereoModeObjectFromGuiMode(stereoscopicsManager.GetStereoMode()); + } + else + return InvalidParams; + + return OK; +} + +CVariant CGUIOperations::GetStereoModeObjectFromGuiMode(const RENDER_STEREO_MODE &mode) +{ + const CStereoscopicsManager &stereoscopicsManager = CServiceBroker::GetGUI()->GetStereoscopicsManager(); + + CVariant modeObj(CVariant::VariantTypeObject); + modeObj["mode"] = stereoscopicsManager.ConvertGuiStereoModeToString(mode); + modeObj["label"] = stereoscopicsManager.GetLabelForStereoMode(mode); + return modeObj; +} diff --git a/xbmc/interfaces/json-rpc/GUIOperations.h b/xbmc/interfaces/json-rpc/GUIOperations.h new file mode 100644 index 0000000..04c3f81 --- /dev/null +++ b/xbmc/interfaces/json-rpc/GUIOperations.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" +#include "rendering/RenderSystemTypes.h" + +class CVariant; + +namespace JSONRPC +{ + class CGUIOperations + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ActivateWindow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ShowNotification(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetFullscreen(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetStereoscopicMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetStereoscopicModes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + static CVariant GetStereoModeObjectFromGuiMode(const RENDER_STEREO_MODE &mode); + }; +} diff --git a/xbmc/interfaces/json-rpc/IClient.h b/xbmc/interfaces/json-rpc/IClient.h new file mode 100644 index 0000000..18a14df --- /dev/null +++ b/xbmc/interfaces/json-rpc/IClient.h @@ -0,0 +1,21 @@ +/* + * 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 + +namespace JSONRPC +{ + class IClient + { + public: + virtual ~IClient() = default; + virtual int GetPermissionFlags() = 0; + virtual int GetAnnouncementFlags() = 0; + virtual bool SetAnnouncementFlags(int flags) = 0; + }; +} diff --git a/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h b/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h new file mode 100644 index 0000000..af7d2e8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/IJSONRPCAnnouncer.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/IAnnouncer.h" +#include "utils/JSONVariantWriter.h" +#include "utils/Variant.h" + +namespace JSONRPC +{ + class IJSONRPCAnnouncer : public ANNOUNCEMENT::IAnnouncer + { + public: + ~IJSONRPCAnnouncer() override = default; + + protected: + static std::string AnnouncementToJSONRPC(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& method, + const CVariant& data, + bool compactOutput) + { + CVariant root; + root["jsonrpc"] = "2.0"; + + std::string namespaceMethod = ANNOUNCEMENT::AnnouncementFlagToString(flag); + namespaceMethod += "."; + namespaceMethod += method; + root["method"] = namespaceMethod; + + root["params"]["data"] = data; + root["params"]["sender"] = sender; + + std::string str; + CJSONVariantWriter::Write(root, str, compactOutput); + + return str; + } + }; +} diff --git a/xbmc/interfaces/json-rpc/ITransportLayer.h b/xbmc/interfaces/json-rpc/ITransportLayer.h new file mode 100644 index 0000000..11a0c78 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ITransportLayer.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +class CVariant; + +namespace JSONRPC +{ + enum TransportLayerCapability + { + Response = 0x1, + Announcing = 0x2, + FileDownloadRedirect = 0x4, + FileDownloadDirect = 0x8 + }; + + #define TRANSPORT_LAYER_CAPABILITY_ALL (Response | Announcing | FileDownloadRedirect | FileDownloadDirect) + + class ITransportLayer + { + public: + virtual ~ITransportLayer() = default; + virtual bool PrepareDownload(const char *path, CVariant &details, std::string &protocol) = 0; + virtual bool Download(const char *path, CVariant &result) = 0; + virtual int GetCapabilities() = 0; + }; +} diff --git a/xbmc/interfaces/json-rpc/InputOperations.cpp b/xbmc/interfaces/json-rpc/InputOperations.cpp new file mode 100644 index 0000000..0b637fb --- /dev/null +++ b/xbmc/interfaces/json-rpc/InputOperations.cpp @@ -0,0 +1,180 @@ +/* + * 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 "InputOperations.h" + +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" +#include "input/ButtonTranslator.h" +#include "input/actions/ActionIDs.h" +#include "input/actions/ActionTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +//! @todo the breakage of the screensaver should be refactored +//! to one central super duper place for getting rid of +//! 1 million dupes +bool CInputOperations::handleScreenSaver() +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + return appPower->WakeUpScreenSaverAndDPMS(); +} + +JSONRPC_STATUS CInputOperations::SendAction(int actionID, bool wakeScreensaver /* = true */, bool waitResult /* = false */) +{ + if (!wakeScreensaver || !handleScreenSaver()) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetSystemIdleTimer(); + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().PlayActionSound(actionID); + + if (waitResult) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + } + return ACK; +} + +JSONRPC_STATUS CInputOperations::activateWindow(int windowID) +{ + if(!handleScreenSaver()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTIVATE_WINDOW, windowID, 0); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::SendText(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CGUIKeyboardFactory::SendTextToActiveKeyboard(parameterObject["text"].asString(), parameterObject["done"].asBoolean())) + return ACK; + + CGUIWindow *window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); + if (!window) + return ACK; + + CGUIMessage msg(GUI_MSG_SET_TEXT, 0, window->GetFocusedControlID()); + msg.SetLabel(parameterObject["text"].asString()); + msg.SetParam1(parameterObject["done"].asBoolean() ? 1 : 0); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, window->GetID()); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::ExecuteAction(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + unsigned int action; + if (!CActionTranslator::TranslateString(parameterObject["action"].asString(), action)) + return InvalidParams; + + return SendAction(action); +} + +JSONRPC_STATUS CInputOperations::ButtonEvent(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + std::string button = parameterObject["button"].asString(); + std::string keymap = parameterObject["keymap"].asString(); + int holdtime = static_cast<int>(parameterObject["holdtime"].asInteger()); + if (holdtime < 0) + { + return InvalidParams; + } + + uint32_t keycode = CButtonTranslator::TranslateString(keymap, button); + if (keycode == 0) + { + return InvalidParams; + } + + XBMC_Event* newEvent = new XBMC_Event; + newEvent->type = XBMC_BUTTON; + newEvent->keybutton.button = keycode; + newEvent->keybutton.holdtime = holdtime; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EVENT, -1, -1, static_cast<void*>(newEvent)); + + return ACK; +} + +JSONRPC_STATUS CInputOperations::Left(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_LEFT); +} + +JSONRPC_STATUS CInputOperations::Right(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_RIGHT); +} + +JSONRPC_STATUS CInputOperations::Down(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_DOWN); +} + +JSONRPC_STATUS CInputOperations::Up(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_MOVE_UP); +} + +JSONRPC_STATUS CInputOperations::Select(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SELECT_ITEM); +} + +JSONRPC_STATUS CInputOperations::Back(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_NAV_BACK); +} + +JSONRPC_STATUS CInputOperations::ContextMenu(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_CONTEXT_MENU); +} + +JSONRPC_STATUS CInputOperations::Info(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SHOW_INFO); +} + +JSONRPC_STATUS CInputOperations::Home(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return activateWindow(WINDOW_HOME); +} + +JSONRPC_STATUS CInputOperations::ShowCodec(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return MethodNotFound; +} + +JSONRPC_STATUS CInputOperations::ShowOSD(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_SHOW_OSD); +} + +JSONRPC_STATUS CInputOperations::ShowPlayerProcessInfo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return SendAction(ACTION_PLAYER_PROCESS_INFO); +} diff --git a/xbmc/interfaces/json-rpc/InputOperations.h b/xbmc/interfaces/json-rpc/InputOperations.h new file mode 100644 index 0000000..a431a1c --- /dev/null +++ b/xbmc/interfaces/json-rpc/InputOperations.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CInputOperations + { + public: + static JSONRPC_STATUS SendText(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ExecuteAction(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ButtonEvent(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + + static JSONRPC_STATUS Left(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Right(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Down(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Up(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Select(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Back(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ContextMenu(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Info(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Home(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS ShowCodec(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ShowOSD(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ShowPlayerProcessInfo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SendAction(int actionID, bool wakeScreensaver = true, bool waitResult = false); + + private: + static JSONRPC_STATUS activateWindow(int windowID); + static bool handleScreenSaver(); + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONRPC.cpp b/xbmc/interfaces/json-rpc/JSONRPC.cpp new file mode 100644 index 0000000..4b86188 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPC.cpp @@ -0,0 +1,392 @@ +/* + * 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 "JSONRPC.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "ServiceBroker.h" +#include "ServiceDescription.h" +#include "TextureDatabase.h" +#include "addons/Addon.h" +#include "addons/IAddon.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "dbwrappers/DatabaseQuery.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIMessage.h" +#include "guilib/GUIWindowManager.h" +#include "input/WindowTranslator.h" +#include "input/actions/ActionTranslator.h" +#include "interfaces/AnnouncementManager.h" +#include "playlists/SmartPlayList.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <string.h> + +using namespace JSONRPC; + +bool CJSONRPC::m_initialized = false; + +void CJSONRPC::Initialize() +{ + if (m_initialized) + return; + + // Add some types/enums at runtime + std::vector<std::string> enumList; + for (int addonType = static_cast<int>(ADDON::AddonType::UNKNOWN); + addonType < static_cast<int>(ADDON::AddonType::MAX_TYPES); addonType++) + enumList.push_back( + ADDON::CAddonInfo::TranslateType(static_cast<ADDON::AddonType>(addonType), false)); + CJSONServiceDescription::AddEnum("Addon.Types", enumList); + + enumList.clear(); + CActionTranslator::GetActions(enumList); + CJSONServiceDescription::AddEnum("Input.Action", enumList); + + enumList.clear(); + CWindowTranslator::GetWindows(enumList); + CJSONServiceDescription::AddEnum("GUI.Window", enumList); + + // filter-related enums + std::vector<std::string> smartplaylistList; + CDatabaseQueryRule::GetAvailableOperators(smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Operators", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("movies", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Movies", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("tvshows", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.TVShows", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("episodes", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Episodes", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("musicvideos", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.MusicVideos", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("artists", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Artists", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("albums", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Albums", smartplaylistList); + + smartplaylistList.clear(); + CSmartPlaylist::GetAvailableFields("songs", smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Songs", smartplaylistList); + + smartplaylistList.clear(); + CTextureRule::GetAvailableFields(smartplaylistList); + CJSONServiceDescription::AddEnum("List.Filter.Fields.Textures", smartplaylistList); + + unsigned int size = sizeof(JSONRPC_SERVICE_TYPES) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddType(JSONRPC_SERVICE_TYPES[index]); + + size = sizeof(JSONRPC_SERVICE_METHODS) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddBuiltinMethod(JSONRPC_SERVICE_METHODS[index]); + + size = sizeof(JSONRPC_SERVICE_NOTIFICATIONS) / sizeof(char*); + + for (unsigned int index = 0; index < size; index++) + CJSONServiceDescription::AddNotification(JSONRPC_SERVICE_NOTIFICATIONS[index]); + + CJSONServiceDescription::ResolveReferences(); + + m_initialized = true; + CLog::Log(LOGINFO, "JSONRPC v{}: Successfully initialized", + CJSONServiceDescription::GetVersion()); +} + +void CJSONRPC::Cleanup() +{ + CJSONServiceDescription::Cleanup(); + m_initialized = false; +} + +JSONRPC_STATUS CJSONRPC::Introspect(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + return CJSONServiceDescription::Print(result, transport, client, + parameterObject["getdescriptions"].asBoolean(), parameterObject["getmetadata"].asBoolean(), parameterObject["filterbytransport"].asBoolean(), + parameterObject["filter"]["id"].asString(), parameterObject["filter"]["type"].asString(), parameterObject["filter"]["getreferences"].asBoolean()); +} + +JSONRPC_STATUS CJSONRPC::Version(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + result["version"]["major"] = 0; + result["version"]["minor"] = 0; + result["version"]["patch"] = 0; + + const char* version = CJSONServiceDescription::GetVersion(); + if (version != NULL) + { + std::vector<std::string> parts = StringUtils::Split(version, "."); + if (!parts.empty()) + result["version"]["major"] = (int)strtol(parts[0].c_str(), NULL, 10); + if (parts.size() > 1) + result["version"]["minor"] = (int)strtol(parts[1].c_str(), NULL, 10); + if (parts.size() > 2) + result["version"]["patch"] = (int)strtol(parts[2].c_str(), NULL, 10); + } + + return OK; +} + +JSONRPC_STATUS CJSONRPC::Permission(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = client->GetPermissionFlags(); + + for (int i = 1; i <= OPERATION_PERMISSION_ALL; i *= 2) + result[PermissionToString((OperationPermission)i)] = (flags & i) == i; + + return OK; +} + +JSONRPC_STATUS CJSONRPC::Ping(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + CVariant temp = "pong"; + result.swap(temp); + return OK; +} + +JSONRPC_STATUS CJSONRPC::GetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = client->GetAnnouncementFlags(); + + for (int i = 1; i <= ANNOUNCEMENT::ANNOUNCE_ALL; i *= 2) + result["notifications"][AnnouncementFlagToString((ANNOUNCEMENT::AnnouncementFlag)i)] = (flags & i) == i; + + return OK; +} + +JSONRPC_STATUS CJSONRPC::SetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + int flags = 0; + int oldFlags = client->GetAnnouncementFlags(); + + if (parameterObject.isMember("notifications")) + { + CVariant notifications = parameterObject["notifications"]; + if ((notifications["Player"].isNull() && (oldFlags & ANNOUNCEMENT::Player)) || + (notifications["Player"].isBoolean() && notifications["Player"].asBoolean())) + flags |= ANNOUNCEMENT::Player; + if ((notifications["Playlist"].isNull() && (oldFlags & ANNOUNCEMENT::Playlist)) || + (notifications["Playlist"].isBoolean() && notifications["Playlist"].asBoolean())) + flags |= ANNOUNCEMENT::Playlist; + if ((notifications["GUI"].isNull() && (oldFlags & ANNOUNCEMENT::GUI)) || + (notifications["GUI"].isBoolean() && notifications["GUI"].asBoolean())) + flags |= ANNOUNCEMENT::GUI; + if ((notifications["System"].isNull() && (oldFlags & ANNOUNCEMENT::System)) || + (notifications["System"].isBoolean() && notifications["System"].asBoolean())) + flags |= ANNOUNCEMENT::System; + if ((notifications["VideoLibrary"].isNull() && (oldFlags & ANNOUNCEMENT::VideoLibrary)) || + (notifications["VideoLibrary"].isBoolean() && notifications["VideoLibrary"].asBoolean())) + flags |= ANNOUNCEMENT::VideoLibrary; + if ((notifications["AudioLibrary"].isNull() && (oldFlags & ANNOUNCEMENT::AudioLibrary)) || + (notifications["AudioLibrary"].isBoolean() && notifications["AudioLibrary"].asBoolean())) + flags |= ANNOUNCEMENT::AudioLibrary; + if ((notifications["Application"].isNull() && (oldFlags & ANNOUNCEMENT::Other)) || + (notifications["Application"].isBoolean() && notifications["Application"].asBoolean())) + flags |= ANNOUNCEMENT::Application; + if ((notifications["Input"].isNull() && (oldFlags & ANNOUNCEMENT::Input)) || + (notifications["Input"].isBoolean() && notifications["Input"].asBoolean())) + flags |= ANNOUNCEMENT::Input; + if ((notifications["Other"].isNull() && (oldFlags & ANNOUNCEMENT::Other)) || + (notifications["Other"].isBoolean() && notifications["Other"].asBoolean())) + flags |= ANNOUNCEMENT::Other; + } + + if (!client->SetAnnouncementFlags(flags)) + return BadPermission; + + return GetConfiguration(method, transport, client, parameterObject, result); +} + +JSONRPC_STATUS CJSONRPC::NotifyAll(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result) +{ + if (parameterObject["data"].isNull()) + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Other, + parameterObject["sender"].asString(), + parameterObject["message"].asString()); + else + { + CVariant data = parameterObject["data"]; + CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Other, + parameterObject["sender"].asString(), + parameterObject["message"].asString(), data); + } + + return ACK; +} + +std::string CJSONRPC::MethodCall(const std::string &inputString, ITransportLayer *transport, IClient *client) +{ + CVariant inputroot, outputroot, result; + bool hasResponse = false; + + CLog::Log(LOGDEBUG, LOGJSONRPC, "JSONRPC: Incoming request: {}", inputString); + + if (CJSONVariantParser::Parse(inputString, inputroot) && !inputroot.isNull()) + { + if (inputroot.isArray()) + { + if (inputroot.size() <= 0) + { + CLog::Log(LOGERROR, "JSONRPC: Empty batch call"); + BuildResponse(inputroot, InvalidRequest, CVariant(), outputroot); + hasResponse = true; + } + else + { + for (CVariant::const_iterator_array itr = inputroot.begin_array(); + itr != inputroot.end_array(); ++itr) + { + CVariant response; + if (HandleMethodCall(*itr, response, transport, client)) + { + outputroot.append(response); + hasResponse = true; + } + } + } + } + else + hasResponse = HandleMethodCall(inputroot, outputroot, transport, client); + } + else + { + CLog::Log(LOGERROR, "JSONRPC: Failed to parse '{}'", inputString); + BuildResponse(inputroot, ParseError, CVariant(), outputroot); + hasResponse = true; + } + + std::string str; + if (hasResponse) + CJSONVariantWriter::Write(outputroot, str, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact); + + return str; +} + +bool CJSONRPC::HandleMethodCall(const CVariant& request, CVariant& response, ITransportLayer *transport, IClient *client) +{ + JSONRPC_STATUS errorCode = OK; + CVariant result; + bool isNotification = false; + + if (IsProperJSONRPC(request)) + { + isNotification = !request.isMember("id"); + + std::string methodName = request["method"].asString(); + StringUtils::ToLower(methodName); + + JSONRPC::MethodCall method; + CVariant params; + + if ((errorCode = CJSONServiceDescription::CheckCall(methodName.c_str(), request["params"], transport, client, isNotification, method, params)) == OK) + errorCode = method(methodName, transport, client, params, result); + else + result = params; + } + else + { + std::string str; + CJSONVariantWriter::Write(request, str, true); + + CLog::Log(LOGERROR, "JSONRPC: Failed to parse '{}'", str); + errorCode = InvalidRequest; + } + + BuildResponse(request, errorCode, result, response); + + return !isNotification; +} + +inline bool CJSONRPC::IsProperJSONRPC(const CVariant& inputroot) +{ + return inputroot.isMember("jsonrpc") && inputroot["jsonrpc"].isString() && inputroot["jsonrpc"] == CVariant("2.0") && inputroot.isMember("method") && inputroot["method"].isString() && (!inputroot.isMember("params") || inputroot["params"].isArray() || inputroot["params"].isObject()); +} + +inline void CJSONRPC::BuildResponse(const CVariant& request, JSONRPC_STATUS code, const CVariant& result, CVariant& response) +{ + response["jsonrpc"] = "2.0"; + response["id"] = request.isMember("id") ? request["id"] : CVariant(); + + switch (code) + { + case OK: + response["result"] = result; + break; + case ACK: + response["result"] = "OK"; + break; + case InvalidRequest: + response["error"]["code"] = InvalidRequest; + response["error"]["message"] = "Invalid request."; + break; + case InvalidParams: + response["error"]["code"] = InvalidParams; + response["error"]["message"] = "Invalid params."; + if (!result.isNull()) + response["error"]["data"] = result; + break; + case MethodNotFound: + response["error"]["code"] = MethodNotFound; + response["error"]["message"] = "Method not found."; + break; + case ParseError: + response["error"]["code"] = ParseError; + response["error"]["message"] = "Parse error."; + break; + case BadPermission: + response["error"]["code"] = BadPermission; + response["error"]["message"] = "Bad client permission."; + break; + case FailedToExecute: + response["error"]["code"] = FailedToExecute; + response["error"]["message"] = "Failed to execute method."; + break; + default: + response["error"]["code"] = InternalError; + response["error"]["message"] = "Internal error."; + break; + } +} + +void CJSONRPCUtils::NotifyItemUpdated() +{ + CGUIMessage message(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); +} + +void CJSONRPCUtils::NotifyItemUpdated(const CVideoInfoTag& info, + const std::map<std::string, std::string>& artwork) +{ + CFileItemPtr msgItem(new CFileItem(info)); + if (!artwork.empty()) + msgItem->SetArt(artwork); + CGUIMessage message(GUI_MSG_NOTIFY_ALL, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(), 0, + GUI_MSG_UPDATE_ITEM, 0, msgItem); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); +} diff --git a/xbmc/interfaces/json-rpc/JSONRPC.h b/xbmc/interfaces/json-rpc/JSONRPC.h new file mode 100644 index 0000000..2e94576 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPC.h @@ -0,0 +1,72 @@ +/* + * 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 "JSONRPCUtils.h" +#include "JSONServiceDescription.h" + +#include <iostream> +#include <map> +#include <stdio.h> +#include <string> + +class CVariant; + +namespace JSONRPC +{ + /*! + \ingroup jsonrpc + \brief JSON RPC handler + + Sets up and manages all needed information to process + JSON-RPC requests and answering with the appropriate + JSON-RPC response (actual response or error message). + */ + class CJSONRPC + { + public: + /*! + \brief Initializes the JSON-RPC handler + */ + static void Initialize(); + + static void Cleanup(); + + /* + \brief Handles an incoming JSON-RPC request + \param inputString received JSON-RPC request + \param transport Transport protocol on which the request arrived + \param client Client which sent the request + \return JSON-RPC response to be sent back to the client + + Parses the received input string for the called method and provided + parameters. If the request does not conform to the JSON-RPC 2.0 + specification an error is returned. Otherwise the parameters provided + in the request are checked for validity and completeness. If the request + is valid and the requested method exists it is called and executed. + */ + static std::string MethodCall(const std::string &inputString, ITransportLayer *transport, IClient *client); + + static JSONRPC_STATUS Introspect(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Version(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Permission(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS Ping(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS GetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS SetConfiguration(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + static JSONRPC_STATUS NotifyAll(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + + private: + static bool HandleMethodCall(const CVariant& request, CVariant& response, ITransportLayer *transport, IClient *client); + static inline bool IsProperJSONRPC(const CVariant& inputroot); + + inline static void BuildResponse(const CVariant& request, JSONRPC_STATUS code, const CVariant& result, CVariant& response); + + static bool m_initialized; + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONRPCUtils.h b/xbmc/interfaces/json-rpc/JSONRPCUtils.h new file mode 100644 index 0000000..0c0a8f8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONRPCUtils.h @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "IClient.h" +#include "ITransportLayer.h" + +#include <map> +#include <string> + +class CVariant; +class CVideoInfoTag; + +namespace JSONRPC +{ + /*! + \ingroup jsonrpc + \brief Possible statuc codes of a response + to a JSON-RPC request + */ + enum JSONRPC_STATUS + { + OK = 0, + ACK = -1, + InvalidRequest = -32600, + MethodNotFound = -32601, + InvalidParams = -32602, + InternalError = -32603, + ParseError = -32700, + //-32099..-32000 Reserved for implementation-defined server-errors. + BadPermission = -32099, + FailedToExecute = -32100 + }; + + /*! + \brief Function pointer for JSON-RPC methods + */ + typedef JSONRPC_STATUS (*MethodCall) (const std::string &method, ITransportLayer *transport, IClient *client, const CVariant& parameterObject, CVariant &result); + + /*! + \ingroup jsonrpc + \brief Permission categories for json rpc methods + + A JSON-RPC method will only be called if the caller + has the correct permissions to execute the method. + The method call needs to be perfectly threadsafe. + */ + enum OperationPermission + { + ReadData = 0x1, + ControlPlayback = 0x2, + ControlNotify = 0x4, + ControlPower = 0x8, + UpdateData = 0x10, + RemoveData = 0x20, + Navigate = 0x40, + WriteFile = 0x80, + ControlSystem = 0x100, + ControlGUI = 0x200, + ManageAddon = 0x400, + ExecuteAddon = 0x800, + ControlPVR = 0x1000 + }; + + const int OPERATION_PERMISSION_ALL = (ReadData | ControlPlayback | ControlNotify | ControlPower | + UpdateData | RemoveData | Navigate | WriteFile | ControlSystem | + ControlGUI | ManageAddon | ExecuteAddon | ControlPVR); + + const int OPERATION_PERMISSION_NOTIFICATION = (ControlPlayback | ControlNotify | ControlPower | UpdateData | + RemoveData | Navigate | WriteFile | ControlSystem | + ControlGUI | ManageAddon | ExecuteAddon | ControlPVR); + + /*! + \brief Returns a string representation for the + given OperationPermission + \param permission Specific OperationPermission + \return String representation of the given OperationPermission + */ + inline const char *PermissionToString(const OperationPermission &permission) + { + switch (permission) + { + case ReadData: + return "ReadData"; + case ControlPlayback: + return "ControlPlayback"; + case ControlNotify: + return "ControlNotify"; + case ControlPower: + return "ControlPower"; + case UpdateData: + return "UpdateData"; + case RemoveData: + return "RemoveData"; + case Navigate: + return "Navigate"; + case WriteFile: + return "WriteFile"; + case ControlSystem: + return "ControlSystem"; + case ControlGUI: + return "ControlGUI"; + case ManageAddon: + return "ManageAddon"; + case ExecuteAddon: + return "ExecuteAddon"; + case ControlPVR: + return "ControlPVR"; + default: + return "Unknown"; + } + } + + /*! + \brief Returns a OperationPermission value for the given + string representation + \param permission String representation of the OperationPermission + \return OperationPermission value of the given string representation + */ + inline OperationPermission StringToPermission(const std::string& permission) + { + if (permission.compare("ControlPlayback") == 0) + return ControlPlayback; + if (permission.compare("ControlNotify") == 0) + return ControlNotify; + if (permission.compare("ControlPower") == 0) + return ControlPower; + if (permission.compare("UpdateData") == 0) + return UpdateData; + if (permission.compare("RemoveData") == 0) + return RemoveData; + if (permission.compare("Navigate") == 0) + return Navigate; + if (permission.compare("WriteFile") == 0) + return WriteFile; + if (permission.compare("ControlSystem") == 0) + return ControlSystem; + if (permission.compare("ControlGUI") == 0) + return ControlGUI; + if (permission.compare("ManageAddon") == 0) + return ManageAddon; + if (permission.compare("ExecuteAddon") == 0) + return ExecuteAddon; + if (permission.compare("ControlPVR") == 0) + return ControlPVR; + + return ReadData; + } + + class CJSONRPCUtils + { + public: + static void NotifyItemUpdated(); + static void NotifyItemUpdated(const CVideoInfoTag& info, + const std::map<std::string, std::string>& artwork); + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp new file mode 100644 index 0000000..da100b2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp @@ -0,0 +1,2153 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "JSONServiceDescription.h" + +#include "AddonsOperations.h" +#include "ApplicationOperations.h" +#include "AudioLibrary.h" +#include "FavouritesOperations.h" +#include "FileOperations.h" +#include "GUIOperations.h" +#include "InputOperations.h" +#include "JSONRPC.h" +#include "PVROperations.h" +#include "PlayerOperations.h" +#include "PlaylistOperations.h" +#include "ProfilesOperations.h" +#include "ServiceDescription.h" +#include "SettingsOperations.h" +#include "SystemOperations.h" +#include "TextureOperations.h" +#include "VideoLibrary.h" +#include "XBMCOperations.h" +#include "utils/JSONVariantParser.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +using namespace JSONRPC; + +std::map<std::string, CVariant> CJSONServiceDescription::m_notifications = std::map<std::string, CVariant>(); +CJSONServiceDescription::CJsonRpcMethodMap CJSONServiceDescription::m_actionMap; +std::map<std::string, JSONSchemaTypeDefinitionPtr> CJSONServiceDescription::m_types = std::map<std::string, JSONSchemaTypeDefinitionPtr>(); +CJSONServiceDescription::IncompleteSchemaDefinitionMap CJSONServiceDescription::m_incompleteDefinitions = CJSONServiceDescription::IncompleteSchemaDefinitionMap(); + +// clang-format off + +JsonRpcMethodMap CJSONServiceDescription::m_methodMaps[] = { +// JSON-RPC + { "JSONRPC.Introspect", CJSONRPC::Introspect }, + { "JSONRPC.Version", CJSONRPC::Version }, + { "JSONRPC.Permission", CJSONRPC::Permission }, + { "JSONRPC.Ping", CJSONRPC::Ping }, + { "JSONRPC.GetConfiguration", CJSONRPC::GetConfiguration }, + { "JSONRPC.SetConfiguration", CJSONRPC::SetConfiguration }, + { "JSONRPC.NotifyAll", CJSONRPC::NotifyAll }, + +// Player + { "Player.GetActivePlayers", CPlayerOperations::GetActivePlayers }, + { "Player.GetPlayers", CPlayerOperations::GetPlayers }, + { "Player.GetProperties", CPlayerOperations::GetProperties }, + { "Player.GetItem", CPlayerOperations::GetItem }, + + { "Player.PlayPause", CPlayerOperations::PlayPause }, + { "Player.Stop", CPlayerOperations::Stop }, + { "Player.GetAudioDelay", CPlayerOperations::GetAudioDelay }, + { "Player.SetAudioDelay", CPlayerOperations::SetAudioDelay }, + { "Player.SetSpeed", CPlayerOperations::SetSpeed }, + { "Player.Seek", CPlayerOperations::Seek }, + { "Player.Move", CPlayerOperations::Move }, + { "Player.Zoom", CPlayerOperations::Zoom }, + { "Player.SetViewMode", CPlayerOperations::SetViewMode }, + { "Player.GetViewMode", CPlayerOperations::GetViewMode }, + { "Player.Rotate", CPlayerOperations::Rotate }, + + { "Player.Open", CPlayerOperations::Open }, + { "Player.GoTo", CPlayerOperations::GoTo }, + { "Player.SetShuffle", CPlayerOperations::SetShuffle }, + { "Player.SetRepeat", CPlayerOperations::SetRepeat }, + { "Player.SetPartymode", CPlayerOperations::SetPartymode }, + + { "Player.SetAudioStream", CPlayerOperations::SetAudioStream }, + { "Player.AddSubtitle", CPlayerOperations::AddSubtitle }, + { "Player.SetSubtitle", CPlayerOperations::SetSubtitle }, + { "Player.SetVideoStream", CPlayerOperations::SetVideoStream }, + +// Playlist + { "Playlist.GetPlaylists", CPlaylistOperations::GetPlaylists }, + { "Playlist.GetProperties", CPlaylistOperations::GetProperties }, + { "Playlist.GetItems", CPlaylistOperations::GetItems }, + { "Playlist.Add", CPlaylistOperations::Add }, + { "Playlist.Insert", CPlaylistOperations::Insert }, + { "Playlist.Clear", CPlaylistOperations::Clear }, + { "Playlist.Remove", CPlaylistOperations::Remove }, + { "Playlist.Swap", CPlaylistOperations::Swap }, + +// Files + { "Files.GetSources", CFileOperations::GetRootDirectory }, + { "Files.GetDirectory", CFileOperations::GetDirectory }, + { "Files.GetFileDetails", CFileOperations::GetFileDetails }, + { "Files.SetFileDetails", CFileOperations::SetFileDetails }, + { "Files.PrepareDownload", CFileOperations::PrepareDownload }, + { "Files.Download", CFileOperations::Download }, + +// Music Library + { "AudioLibrary.GetProperties", CAudioLibrary::GetProperties }, + { "AudioLibrary.GetArtists", CAudioLibrary::GetArtists }, + { "AudioLibrary.GetArtistDetails", CAudioLibrary::GetArtistDetails }, + { "AudioLibrary.GetAlbums", CAudioLibrary::GetAlbums }, + { "AudioLibrary.GetAlbumDetails", CAudioLibrary::GetAlbumDetails }, + { "AudioLibrary.GetSongs", CAudioLibrary::GetSongs }, + { "AudioLibrary.GetSongDetails", CAudioLibrary::GetSongDetails }, + { "AudioLibrary.GetRecentlyAddedAlbums", CAudioLibrary::GetRecentlyAddedAlbums }, + { "AudioLibrary.GetRecentlyAddedSongs", CAudioLibrary::GetRecentlyAddedSongs }, + { "AudioLibrary.GetRecentlyPlayedAlbums", CAudioLibrary::GetRecentlyPlayedAlbums }, + { "AudioLibrary.GetRecentlyPlayedSongs", CAudioLibrary::GetRecentlyPlayedSongs }, + { "AudioLibrary.GetGenres", CAudioLibrary::GetGenres }, + { "AudioLibrary.GetRoles", CAudioLibrary::GetRoles }, + { "AudioLibrary.GetSources", CAudioLibrary::GetSources }, + { "AudioLibrary.GetAvailableArtTypes", CAudioLibrary::GetAvailableArtTypes }, + { "AudioLibrary.GetAvailableArt", CAudioLibrary::GetAvailableArt }, + { "AudioLibrary.SetArtistDetails", CAudioLibrary::SetArtistDetails }, + { "AudioLibrary.SetAlbumDetails", CAudioLibrary::SetAlbumDetails }, + { "AudioLibrary.SetSongDetails", CAudioLibrary::SetSongDetails }, + { "AudioLibrary.Scan", CAudioLibrary::Scan }, + { "AudioLibrary.Export", CAudioLibrary::Export }, + { "AudioLibrary.Clean", CAudioLibrary::Clean }, + +// Video Library + { "VideoLibrary.GetGenres", CVideoLibrary::GetGenres }, + { "VideoLibrary.GetTags", CVideoLibrary::GetTags }, + { "VideoLibrary.GetAvailableArtTypes", CVideoLibrary::GetAvailableArtTypes }, + { "VideoLibrary.GetAvailableArt", CVideoLibrary::GetAvailableArt }, + { "VideoLibrary.GetMovies", CVideoLibrary::GetMovies }, + { "VideoLibrary.GetMovieDetails", CVideoLibrary::GetMovieDetails }, + { "VideoLibrary.GetMovieSets", CVideoLibrary::GetMovieSets }, + { "VideoLibrary.GetMovieSetDetails", CVideoLibrary::GetMovieSetDetails }, + { "VideoLibrary.GetTVShows", CVideoLibrary::GetTVShows }, + { "VideoLibrary.GetTVShowDetails", CVideoLibrary::GetTVShowDetails }, + { "VideoLibrary.GetSeasons", CVideoLibrary::GetSeasons }, + { "VideoLibrary.GetSeasonDetails", CVideoLibrary::GetSeasonDetails }, + { "VideoLibrary.GetEpisodes", CVideoLibrary::GetEpisodes }, + { "VideoLibrary.GetEpisodeDetails", CVideoLibrary::GetEpisodeDetails }, + { "VideoLibrary.GetMusicVideos", CVideoLibrary::GetMusicVideos }, + { "VideoLibrary.GetMusicVideoDetails", CVideoLibrary::GetMusicVideoDetails }, + { "VideoLibrary.GetRecentlyAddedMovies", CVideoLibrary::GetRecentlyAddedMovies }, + { "VideoLibrary.GetRecentlyAddedEpisodes", CVideoLibrary::GetRecentlyAddedEpisodes }, + { "VideoLibrary.GetRecentlyAddedMusicVideos", CVideoLibrary::GetRecentlyAddedMusicVideos }, + { "VideoLibrary.GetInProgressTVShows", CVideoLibrary::GetInProgressTVShows }, + { "VideoLibrary.SetMovieDetails", CVideoLibrary::SetMovieDetails }, + { "VideoLibrary.SetMovieSetDetails", CVideoLibrary::SetMovieSetDetails }, + { "VideoLibrary.SetTVShowDetails", CVideoLibrary::SetTVShowDetails }, + { "VideoLibrary.SetSeasonDetails", CVideoLibrary::SetSeasonDetails }, + { "VideoLibrary.SetEpisodeDetails", CVideoLibrary::SetEpisodeDetails }, + { "VideoLibrary.SetMusicVideoDetails", CVideoLibrary::SetMusicVideoDetails }, + { "VideoLibrary.RefreshMovie", CVideoLibrary::RefreshMovie }, + { "VideoLibrary.RefreshTVShow", CVideoLibrary::RefreshTVShow }, + { "VideoLibrary.RefreshEpisode", CVideoLibrary::RefreshEpisode }, + { "VideoLibrary.RefreshMusicVideo", CVideoLibrary::RefreshMusicVideo }, + { "VideoLibrary.RemoveMovie", CVideoLibrary::RemoveMovie }, + { "VideoLibrary.RemoveTVShow", CVideoLibrary::RemoveTVShow }, + { "VideoLibrary.RemoveEpisode", CVideoLibrary::RemoveEpisode }, + { "VideoLibrary.RemoveMusicVideo", CVideoLibrary::RemoveMusicVideo }, + { "VideoLibrary.Scan", CVideoLibrary::Scan }, + { "VideoLibrary.Export", CVideoLibrary::Export }, + { "VideoLibrary.Clean", CVideoLibrary::Clean }, + +// Addon operations + { "Addons.GetAddons", CAddonsOperations::GetAddons }, + { "Addons.GetAddonDetails", CAddonsOperations::GetAddonDetails }, + { "Addons.SetAddonEnabled", CAddonsOperations::SetAddonEnabled }, + { "Addons.ExecuteAddon", CAddonsOperations::ExecuteAddon }, + +// GUI operations + { "GUI.GetProperties", CGUIOperations::GetProperties }, + { "GUI.ActivateWindow", CGUIOperations::ActivateWindow }, + { "GUI.ShowNotification", CGUIOperations::ShowNotification }, + { "GUI.SetFullscreen", CGUIOperations::SetFullscreen }, + { "GUI.SetStereoscopicMode", CGUIOperations::SetStereoscopicMode }, + { "GUI.GetStereoscopicModes", CGUIOperations::GetStereoscopicModes }, + +// PVR operations + { "PVR.GetProperties", CPVROperations::GetProperties }, + { "PVR.GetChannelGroups", CPVROperations::GetChannelGroups }, + { "PVR.GetChannelGroupDetails", CPVROperations::GetChannelGroupDetails }, + { "PVR.GetChannels", CPVROperations::GetChannels }, + { "PVR.GetChannelDetails", CPVROperations::GetChannelDetails }, + { "PVR.GetClients", CPVROperations::GetClients }, + { "PVR.GetBroadcasts", CPVROperations::GetBroadcasts }, + { "PVR.GetBroadcastDetails", CPVROperations::GetBroadcastDetails }, + { "PVR.GetBroadcastIsPlayable", CPVROperations::GetBroadcastIsPlayable }, + { "PVR.GetTimers", CPVROperations::GetTimers }, + { "PVR.GetTimerDetails", CPVROperations::GetTimerDetails }, + { "PVR.GetRecordings", CPVROperations::GetRecordings }, + { "PVR.GetRecordingDetails", CPVROperations::GetRecordingDetails }, + { "PVR.AddTimer", CPVROperations::AddTimer }, + { "PVR.DeleteTimer", CPVROperations::DeleteTimer }, + { "PVR.ToggleTimer", CPVROperations::ToggleTimer }, + { "PVR.Record", CPVROperations::Record }, + { "PVR.Scan", CPVROperations::Scan }, + +// Profiles operations + { "Profiles.GetProfiles", CProfilesOperations::GetProfiles}, + { "Profiles.GetCurrentProfile", CProfilesOperations::GetCurrentProfile}, + { "Profiles.LoadProfile", CProfilesOperations::LoadProfile}, + +// System operations + { "System.GetProperties", CSystemOperations::GetProperties }, + { "System.EjectOpticalDrive", CSystemOperations::EjectOpticalDrive }, + { "System.Shutdown", CSystemOperations::Shutdown }, + { "System.Suspend", CSystemOperations::Suspend }, + { "System.Hibernate", CSystemOperations::Hibernate }, + { "System.Reboot", CSystemOperations::Reboot }, + +// Input operations + { "Input.SendText", CInputOperations::SendText }, + { "Input.ExecuteAction", CInputOperations::ExecuteAction }, + { "Input.ButtonEvent", CInputOperations::ButtonEvent }, + { "Input.Left", CInputOperations::Left }, + { "Input.Right", CInputOperations::Right }, + { "Input.Down", CInputOperations::Down }, + { "Input.Up", CInputOperations::Up }, + { "Input.Select", CInputOperations::Select }, + { "Input.Back", CInputOperations::Back }, + { "Input.ContextMenu", CInputOperations::ContextMenu }, + { "Input.Info", CInputOperations::Info }, + { "Input.Home", CInputOperations::Home }, + { "Input.ShowCodec", CInputOperations::ShowCodec }, + { "Input.ShowOSD", CInputOperations::ShowOSD }, + { "Input.ShowPlayerProcessInfo", CInputOperations::ShowPlayerProcessInfo }, + +// Application operations + { "Application.GetProperties", CApplicationOperations::GetProperties }, + { "Application.SetVolume", CApplicationOperations::SetVolume }, + { "Application.SetMute", CApplicationOperations::SetMute }, + { "Application.Quit", CApplicationOperations::Quit }, + +// Favourites operations + { "Favourites.GetFavourites", CFavouritesOperations::GetFavourites }, + { "Favourites.AddFavourite", CFavouritesOperations::AddFavourite }, + +// Textures operations + { "Textures.GetTextures", CTextureOperations::GetTextures }, + { "Textures.RemoveTexture", CTextureOperations::RemoveTexture }, + +// Settings operations + { "Settings.GetSections", CSettingsOperations::GetSections }, + { "Settings.GetCategories", CSettingsOperations::GetCategories }, + { "Settings.GetSettings", CSettingsOperations::GetSettings }, + { "Settings.GetSettingValue", CSettingsOperations::GetSettingValue }, + { "Settings.SetSettingValue", CSettingsOperations::SetSettingValue }, + { "Settings.ResetSettingValue", CSettingsOperations::ResetSettingValue }, + { "Settings.GetSkinSettings", CSettingsOperations::GetSkinSettings }, + { "Settings.GetSkinSettingValue", CSettingsOperations::GetSkinSettingValue }, + { "Settings.SetSkinSettingValue", CSettingsOperations::SetSkinSettingValue }, + +// XBMC operations + { "XBMC.GetInfoLabels", CXBMCOperations::GetInfoLabels }, + { "XBMC.GetInfoBooleans", CXBMCOperations::GetInfoBooleans } +}; + +// clang-format on + +JSONSchemaTypeDefinition::JSONSchemaTypeDefinition() + : missingReference(), + name(), + ID(), + referencedType(nullptr), + extends(), + description(), + unionTypes(), + defaultValue(), + minimum(-std::numeric_limits<double>::max()), + maximum(std::numeric_limits<double>::max()), + enums(), + items(), + additionalItems(), + properties(), + additionalProperties(nullptr) +{ } + +bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* = false */) +{ + bool hasReference = false; + + // Check if the type of the parameter defines a json reference + // to a type defined somewhere else + if (value.isMember("$ref") && value["$ref"].isString()) + { + // Get the name of the referenced type + std::string refType = value["$ref"].asString(); + // Check if the referenced type exists + JSONSchemaTypeDefinitionPtr referencedTypeDef = CJSONServiceDescription::GetType(refType); + if (refType.length() <= 0 || referencedTypeDef.get() == NULL) + { + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} references an unknown type {}", name, + refType); + missingReference = refType; + return false; + } + + std::string typeName = name; + *this = *referencedTypeDef; + if (!typeName.empty()) + name = typeName; + referencedType = referencedTypeDef; + hasReference = true; + } + else if (value.isMember("id") && value["id"].isString()) + ID = GetString(value["id"], ""); + + // Check if the "required" field has been defined + optional = value.isMember("required") && value["required"].isBoolean() ? !value["required"].asBoolean() : true; + + // Get the "description" + if (!hasReference || (value.isMember("description") && value["description"].isString())) + description = GetString(value["description"], ""); + + if (hasReference) + { + // If there is a specific default value, read it + if (value.isMember("default") && IsType(value["default"], type)) + { + bool ok = false; + if (enums.size() <= 0) + ok = true; + // If the type has an enum definition we must make + // sure that the default value is a valid enum value + else + { + for (const auto& itr : enums) + { + if (value["default"] == itr) + { + ok = true; + break; + } + } + } + + if (ok) + defaultValue = value["default"]; + } + + return true; + } + + // Check whether this type extends an existing type + if (value.isMember("extends")) + { + if (value["extends"].isString()) + { + std::string extendsName = GetString(value["extends"], ""); + if (!extendsName.empty()) + { + JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); + if (extendedTypeDef.get() == NULL) + { + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, + extendsName); + missingReference = extendsName; + return false; + } + + type = extendedTypeDef->type; + extends.push_back(extendedTypeDef); + } + } + else if (value["extends"].isArray()) + { + JSONSchemaType extendedType = AnyValue; + for (unsigned int extendsIndex = 0; extendsIndex < value["extends"].size(); extendsIndex++) + { + std::string extendsName = GetString(value["extends"][extendsIndex], ""); + if (!extendsName.empty()) + { + JSONSchemaTypeDefinitionPtr extendedTypeDef = CJSONServiceDescription::GetType(extendsName); + if (extendedTypeDef.get() == NULL) + { + extends.clear(); + CLog::Log(LOGDEBUG, "JSONRPC: JSON schema type {} extends an unknown type {}", name, + extendsName); + missingReference = extendsName; + return false; + } + + if (extendsIndex == 0) + extendedType = extendedTypeDef->type; + else if (extendedType != extendedTypeDef->type) + { + extends.clear(); + CLog::Log(LOGDEBUG, + "JSONRPC: JSON schema type {} extends multiple JSON schema types of " + "mismatching types", + name); + return false; + } + + extends.push_back(extendedTypeDef); + } + } + + type = extendedType; + } + } + + // Only read the "type" attribute if it's + // not an extending type + if (extends.size() <= 0) + { + // Get the defined type of the parameter + if (!CJSONServiceDescription::parseJSONSchemaType(value["type"], unionTypes, type, missingReference)) + return false; + } + + if (HasType(type, ObjectValue)) + { + // If the type definition is of type "object" + // and has a "properties" definition we need + // to handle these as well + if (value.isMember("properties") && value["properties"].isObject()) + { + // Get all child elements of the "properties" + // object and loop through them + for (CVariant::const_iterator_map itr = value["properties"].begin_map(); itr != value["properties"].end_map(); ++itr) + { + // Create a new type definition, store the name + // of the current property into it, parse it + // recursively and add its default value + // to the current type's default value + JSONSchemaTypeDefinitionPtr propertyType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + propertyType->name = itr->first; + if (!propertyType->Parse(itr->second)) + { + missingReference = propertyType->missingReference; + return false; + } + defaultValue[itr->first] = propertyType->defaultValue; + properties.add(propertyType); + } + } + + hasAdditionalProperties = true; + additionalProperties = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (value.isMember("additionalProperties")) + { + if (value["additionalProperties"].isBoolean()) + { + hasAdditionalProperties = value["additionalProperties"].asBoolean(); + if (!hasAdditionalProperties) + { + additionalProperties.reset(); + } + } + else if (value["additionalProperties"].isObject() && !value["additionalProperties"].isNull()) + { + if (!additionalProperties->Parse(value["additionalProperties"])) + { + missingReference = additionalProperties->missingReference; + hasAdditionalProperties = false; + additionalProperties.reset(); + + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties schema definition in type {}", + name); + return false; + } + } + else + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additionalProperties definition in type {}", name); + return false; + } + } + } + + // If the defined parameter is an array + // we need to check for detailed definitions + // of the array items + if (HasType(type, ArrayValue)) + { + // Check for "uniqueItems" field + if (value.isMember("uniqueItems") && value["uniqueItems"].isBoolean()) + uniqueItems = value["uniqueItems"].asBoolean(); + else + uniqueItems = false; + + // Check for "additionalItems" field + if (value.isMember("additionalItems")) + { + // If it is an object, there is only one schema for it + if (value["additionalItems"].isObject()) + { + JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (additionalItem->Parse(value["additionalItems"])) + additionalItems.push_back(additionalItem); + else + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value for type {}", name); + missingReference = additionalItem->missingReference; + return false; + } + } + // If it is an array there may be multiple schema definitions + else if (value["additionalItems"].isArray()) + { + for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++) + { + JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + + if (additionalItem->Parse(value["additionalItems"][itemIndex])) + additionalItems.push_back(additionalItem); + else + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" value (item {}) for type {}", + itemIndex, name); + missingReference = additionalItem->missingReference; + return false; + } + } + } + // If it is not a (array of) schema and not a bool (default value is false) + // it has an invalid value + else if (!value["additionalItems"].isBoolean()) + { + CLog::Log(LOGDEBUG, "Invalid \"additionalItems\" definition for type {}", name); + return false; + } + } + + // If the "items" field is a single object + // we can parse that directly + if (value.isMember("items")) + { + if (value["items"].isObject()) + { + JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!item->Parse(value["items"])) + { + CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type {}", name); + missingReference = item->missingReference; + return false; + } + items.push_back(item); + } + // Otherwise if it is an array we need to + // parse all elements and store them + else if (value["items"].isArray()) + { + for (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); ++itemItr) + { + JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!item->Parse(*itemItr)) + { + CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type {}", name); + missingReference = item->missingReference; + return false; + } + items.push_back(item); + } + } + } + + minItems = (unsigned int)value["minItems"].asUnsignedInteger(0); + maxItems = (unsigned int)value["maxItems"].asUnsignedInteger(0); + } + + if (HasType(type, NumberValue) || HasType(type, IntegerValue)) + { + if ((type & NumberValue) == NumberValue) + { + minimum = value["minimum"].asDouble(-std::numeric_limits<double>::max()); + maximum = value["maximum"].asDouble(std::numeric_limits<double>::max()); + } + else if ((type & IntegerValue) == IntegerValue) + { + minimum = (double)value["minimum"].asInteger(std::numeric_limits<int>::min()); + maximum = (double)value["maximum"].asInteger(std::numeric_limits<int>::max()); + } + + exclusiveMinimum = value["exclusiveMinimum"].asBoolean(false); + exclusiveMaximum = value["exclusiveMaximum"].asBoolean(false); + divisibleBy = (unsigned int)value["divisibleBy"].asUnsignedInteger(0); + } + + if (HasType(type, StringValue)) + { + minLength = (int)value["minLength"].asInteger(-1); + maxLength = (int)value["maxLength"].asInteger(-1); + } + + // If the type definition is neither an + // "object" nor an "array" we can check + // for an "enum" definition + if (value.isMember("enum") && value["enum"].isArray()) + { + // Loop through all elements in the "enum" array + for (CVariant::const_iterator_array enumItr = value["enum"].begin_array(); enumItr != value["enum"].end_array(); ++enumItr) + { + // Check for duplicates and eliminate them + bool approved = true; + for (unsigned int approvedIndex = 0; approvedIndex < enums.size(); approvedIndex++) + { + if (*enumItr == enums.at(approvedIndex)) + { + approved = false; + break; + } + } + + // Only add the current item to the enum value + // list if it is not duplicate + if (approved) + enums.push_back(*enumItr); + } + } + + if (type != ObjectValue) + { + // If there is a definition for a default value and its type + // matches the type of the parameter we can parse it + bool ok = false; + if (value.isMember("default") && IsType(value["default"], type)) + { + if (enums.size() <= 0) + ok = true; + // If the type has an enum definition we must make + // sure that the default value is a valid enum value + else + { + for (std::vector<CVariant>::const_iterator itr = enums.begin(); itr != enums.end(); ++itr) + { + if (value["default"] == *itr) + { + ok = true; + break; + } + } + } + } + + if (ok) + defaultValue = value["default"]; + else + { + // If the type of the default value definition does not + // match the type of the parameter we have to log this + if (value.isMember("default") && !IsType(value["default"], type)) + CLog::Log(LOGDEBUG, "JSONRPC: Parameter {} has an invalid default value", name); + + // If the type contains an "enum" we need to get the + // default value from the first enum value + if (enums.size() > 0) + defaultValue = enums.at(0); + // otherwise set a default value instead + else + SetDefaultValue(defaultValue, type); + } + } + + return true; +} + +JSONRPC_STATUS JSONSchemaTypeDefinition::Check(const CVariant& value, + CVariant& outputValue, + CVariant& errorData) const +{ + if (!name.empty()) + errorData["name"] = name; + SchemaValueTypeToJson(type, errorData["type"]); + std::string errorMessage; + + // Let's check the type of the provided parameter + if (!IsType(value, type)) + { + errorMessage = StringUtils::Format("Invalid type {} received", ValueTypeToString(value.type())); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + else if (value.isNull() && !HasType(type, NullValue)) + { + errorData["message"] = "Received value is null"; + return InvalidParams; + } + + // Let's check if we have to handle a union type + if (unionTypes.size() > 0) + { + bool ok = false; + for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) + { + CVariant dummyError; + CVariant testOutput = outputValue; + if (unionTypes.at(unionIndex)->Check(value, testOutput, dummyError) == OK) + { + ok = true; + outputValue = testOutput; + break; + } + } + + if (!ok) + { + errorData["message"] = "Received value does not match any of the union type definitions"; + return InvalidParams; + } + } + + // First we need to check if this type extends another + // type and if so we need to check against the extended + // type first + if (extends.size() > 0) + { + for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) + { + JSONRPC_STATUS status = extends.at(extendsIndex)->Check(value, outputValue, errorData); + + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not match extended type {} of type {}", + extends.at(extendsIndex)->ID, name); + errorMessage = StringUtils::Format("value does not match extended type {}", + extends.at(extendsIndex)->ID); + errorData["message"] = errorMessage.c_str(); + return status; + } + } + } + + // If it is an array we need to + // - check the type of every element ("items") + // - check if they need to be unique ("uniqueItems") + if (HasType(type, ArrayValue) && value.isArray()) + { + outputValue = CVariant(CVariant::VariantTypeArray); + // Check the number of items against minItems and maxItems + if ((minItems > 0 && value.size() < minItems) || (maxItems > 0 && value.size() > maxItems)) + { + CLog::Log( + LOGDEBUG, + "JSONRPC: Number of array elements does not match minItems and/or maxItems in type {}", + name); + if (minItems > 0 && maxItems > 0) + errorMessage = StringUtils::Format("Between {} and {} array items expected but {} received", + minItems, maxItems, value.size()); + else if (minItems > 0) + errorMessage = StringUtils::Format("At least {} array items expected but only {} received", + minItems, value.size()); + else + errorMessage = StringUtils::Format("Only {} array items expected but {} received", maxItems, + value.size()); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + if (items.size() == 0) + outputValue = value; + else if (items.size() == 1) + { + JSONSchemaTypeDefinitionPtr itemType = items.at(0); + + // Loop through all array elements + for (unsigned int arrayIndex = 0; arrayIndex < value.size(); arrayIndex++) + { + CVariant temp; + JSONRPC_STATUS status = itemType->Check(value[arrayIndex], temp, errorData["property"]); + outputValue.push_back(temp); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Array element at index {} does not match in type {}", + arrayIndex, name); + errorMessage = + StringUtils::Format("array element at index {} does not match", arrayIndex); + errorData["message"] = errorMessage.c_str(); + return status; + } + } + } + // We have more than one element in "items" + // so we have tuple typing, which means that + // every element in the value array must match + // with the type at the same position in the + // "items" array + else + { + // If the number of elements in the value array + // does not match the number of elements in the + // "items" array and additional items are not + // allowed there is no need to check every element + if (value.size() < items.size() || (value.size() != items.size() && additionalItems.size() == 0)) + { + CLog::Log(LOGDEBUG, "JSONRPC: One of the array elements does not match in type {}", name); + errorMessage = StringUtils::Format("{0} array elements expected but {1} received", items.size(), value.size()); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + // Loop through all array elements until there + // are either no more schemas in the "items" + // array or no more elements in the value's array + unsigned int arrayIndex; + for (arrayIndex = 0; arrayIndex < std::min(items.size(), (size_t)value.size()); arrayIndex++) + { + JSONRPC_STATUS status = items.at(arrayIndex)->Check(value[arrayIndex], outputValue[arrayIndex], errorData["property"]); + if (status != OK) + { + CLog::Log( + LOGDEBUG, + "JSONRPC: Array element at index {} does not match with items schema in type {}", + arrayIndex, name); + return status; + } + } + + if (additionalItems.size() > 0) + { + // Loop through the rest of the elements + // in the array and check them against the + // "additionalItems" + for (; arrayIndex < value.size(); arrayIndex++) + { + bool ok = false; + for (unsigned int additionalIndex = 0; additionalIndex < additionalItems.size(); additionalIndex++) + { + CVariant dummyError; + if (additionalItems.at(additionalIndex)->Check(value[arrayIndex], outputValue[arrayIndex], dummyError) == OK) + { + ok = true; + break; + } + } + + if (!ok) + { + CLog::Log(LOGDEBUG, + "JSONRPC: Array contains non-conforming additional items in type {}", name); + errorMessage = StringUtils::Format( + "Array element at index {} does not match the \"additionalItems\" schema", + arrayIndex); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + } + } + + // If every array element is unique we need to check each one + if (uniqueItems) + { + for (unsigned int checkingIndex = 0; checkingIndex < outputValue.size(); checkingIndex++) + { + for (unsigned int checkedIndex = checkingIndex + 1; checkedIndex < outputValue.size(); checkedIndex++) + { + // If two elements are the same they are not unique + if (outputValue[checkingIndex] == outputValue[checkedIndex]) + { + CLog::Log(LOGDEBUG, "JSONRPC: Not unique array element at index {} and {} in type {}", + checkingIndex, checkedIndex, name); + errorMessage = StringUtils::Format( + "Array element at index {} is not unique (same as array element at index {})", + checkingIndex, checkedIndex); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + } + } + + return OK; + } + + // If it is an object we need to check every element + // against the defined "properties" + if (HasType(type, ObjectValue) && value.isObject()) + { + unsigned int handled = 0; + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; + for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) + { + if (value.isMember(propertiesIterator->second->name)) + { + JSONRPC_STATUS status = propertiesIterator->second->Check(value[propertiesIterator->second->name], outputValue[propertiesIterator->second->name], errorData["property"]); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid property \"{}\" in type {}", + propertiesIterator->second->name, name); + return status; + } + handled++; + } + else if (propertiesIterator->second->optional) + outputValue[propertiesIterator->second->name] = propertiesIterator->second->defaultValue; + else + { + errorData["property"]["name"] = propertiesIterator->second->name.c_str(); + errorData["property"]["type"] = SchemaValueTypeToString(propertiesIterator->second->type); + errorData["message"] = "Missing property"; + return InvalidParams; + } + } + + // Additional properties are not allowed + if (handled < value.size()) + { + // If additional properties are allowed we need to check if + // they match the defined schema + if (hasAdditionalProperties && additionalProperties != NULL) + { + CVariant::const_iterator_map iter; + CVariant::const_iterator_map iterEnd = value.end_map(); + for (iter = value.begin_map(); iter != iterEnd; ++iter) + { + if (properties.find(iter->first) != properties.end()) + continue; + + // If the additional property is of type "any" + // we can simply copy its value to the output + // object + if (additionalProperties->type == AnyValue) + { + outputValue[iter->first] = value[iter->first]; + continue; + } + + JSONRPC_STATUS status = additionalProperties->Check(value[iter->first], outputValue[iter->first], errorData["property"]); + if (status != OK) + { + CLog::Log(LOGDEBUG, "JSONRPC: Invalid additional property \"{}\" in type {}", + iter->first, name); + return status; + } + } + } + // If we still have unchecked properties but additional + // properties are not allowed, we have invalid parameters + else if (!hasAdditionalProperties || additionalProperties == NULL) + { + errorData["message"] = "Unexpected additional properties received"; + errorData.erase("property"); + return InvalidParams; + } + } + + return OK; + } + + // It's neither an array nor an object + + // If it can only take certain values ("enum") + // we need to check against those + if (enums.size() > 0) + { + bool valid = false; + for (const auto& enumItr : enums) + { + if (enumItr == value) + { + valid = true; + break; + } + } + + if (!valid) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not match any of the enum values in type {}", name); + errorData["message"] = "Received value does not match any of the defined enum values"; + return InvalidParams; + } + } + + // If we have a number or an integer type, we need + // to check the minimum and maximum values + if ((HasType(type, NumberValue) && value.isDouble()) || (HasType(type, IntegerValue) && value.isInteger())) + { + double numberValue; + if (value.isDouble()) + numberValue = value.asDouble(); + else + numberValue = (double)value.asInteger(); + // Check minimum + if ((exclusiveMinimum && numberValue <= minimum) || (!exclusiveMinimum && numberValue < minimum) || + // Check maximum + (exclusiveMaximum && numberValue >= maximum) || (!exclusiveMaximum && numberValue > maximum)) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not lay between minimum and maximum in type {}", + name); + if (value.isDouble()) + errorMessage = + StringUtils::Format("Value between {:f} ({}) and {:f} ({}) expected but {:f} received", + minimum, exclusiveMinimum ? "exclusive" : "inclusive", maximum, + exclusiveMaximum ? "exclusive" : "inclusive", numberValue); + else + errorMessage = StringUtils::Format( + "Value between {} ({}) and {} ({}) expected but {} received", (int)minimum, + exclusiveMinimum ? "exclusive" : "inclusive", (int)maximum, + exclusiveMaximum ? "exclusive" : "inclusive", (int)numberValue); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + // Check divisibleBy + if ((HasType(type, IntegerValue) && divisibleBy > 0 && ((int)numberValue % divisibleBy) != 0)) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet divisibleBy requirements in type {}", name); + errorMessage = StringUtils::Format("Value should be divisible by {} but {} received", + divisibleBy, (int)numberValue); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + + // If we have a string, we need to check the length + if (HasType(type, StringValue) && value.isString()) + { + int size = static_cast<int>(value.asString().size()); + if (size < minLength) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet minLength requirements in type {}", name); + errorMessage = StringUtils::Format( + "Value should have a minimum length of {} but has a length of {}", minLength, size); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + + if (maxLength >= 0 && size > maxLength) + { + CLog::Log(LOGDEBUG, "JSONRPC: Value does not meet maxLength requirements in type {}", name); + errorMessage = StringUtils::Format( + "Value should have a maximum length of {} but has a length of {}", maxLength, size); + errorData["message"] = errorMessage.c_str(); + return InvalidParams; + } + } + + // Otherwise it can have any value + outputValue = value; + return OK; +} + +void JSONSchemaTypeDefinition::Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const +{ + bool typeReference = false; + + // Printing general fields + if (isParameter) + output["name"] = name; + + if (isGlobal) + output["id"] = ID; + else if (!ID.empty()) + { + output["$ref"] = ID; + typeReference = true; + } + + if (printDescriptions && !description.empty()) + output["description"] = description; + + if (isParameter || printDefault) + { + if (!optional) + output["required"] = true; + if (optional && type != ObjectValue && type != ArrayValue) + output["default"] = defaultValue; + } + + if (!typeReference) + { + if (extends.size() == 1) + { + output["extends"] = extends.at(0)->ID; + } + else if (extends.size() > 1) + { + output["extends"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int extendsIndex = 0; extendsIndex < extends.size(); extendsIndex++) + output["extends"].append(extends.at(extendsIndex)->ID); + } + else if (unionTypes.size() > 0) + { + output["type"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int unionIndex = 0; unionIndex < unionTypes.size(); unionIndex++) + { + CVariant unionOutput = CVariant(CVariant::VariantTypeObject); + unionTypes.at(unionIndex)->Print(false, false, false, printDescriptions, unionOutput); + output["type"].append(unionOutput); + } + } + else + CJSONUtils::SchemaValueTypeToJson(type, output["type"]); + + // Printing enum field + if (enums.size() > 0) + { + output["enums"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int enumIndex = 0; enumIndex < enums.size(); enumIndex++) + output["enums"].append(enums.at(enumIndex)); + } + + // Printing integer/number fields + if (CJSONUtils::HasType(type, IntegerValue) || CJSONUtils::HasType(type, NumberValue)) + { + if (CJSONUtils::HasType(type, NumberValue)) + { + if (minimum > -std::numeric_limits<double>::max()) + output["minimum"] = minimum; + if (maximum < std::numeric_limits<double>::max()) + output["maximum"] = maximum; + } + else + { + if (minimum > std::numeric_limits<int>::min()) + output["minimum"] = (int)minimum; + if (maximum < std::numeric_limits<int>::max()) + output["maximum"] = (int)maximum; + } + + if (exclusiveMinimum) + output["exclusiveMinimum"] = true; + if (exclusiveMaximum) + output["exclusiveMaximum"] = true; + if (divisibleBy > 0) + output["divisibleBy"] = divisibleBy; + } + if (CJSONUtils::HasType(type, StringValue)) + { + if (minLength >= 0) + output["minLength"] = minLength; + if (maxLength >= 0) + output["maxLength"] = maxLength; + } + + // Print array fields + if (CJSONUtils::HasType(type, ArrayValue)) + { + if (items.size() == 1) + { + items.at(0)->Print(false, false, false, printDescriptions, output["items"]); + } + else if (items.size() > 1) + { + output["items"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int itemIndex = 0; itemIndex < items.size(); itemIndex++) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + items.at(itemIndex)->Print(false, false, false, printDescriptions, item); + output["items"].append(item); + } + } + + if (minItems > 0) + output["minItems"] = minItems; + if (maxItems > 0) + output["maxItems"] = maxItems; + + if (additionalItems.size() == 1) + { + additionalItems.at(0)->Print(false, false, false, printDescriptions, output["additionalItems"]); + } + else if (additionalItems.size() > 1) + { + output["additionalItems"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int addItemIndex = 0; addItemIndex < additionalItems.size(); addItemIndex++) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + additionalItems.at(addItemIndex)->Print(false, false, false, printDescriptions, item); + output["additionalItems"].append(item); + } + } + + if (uniqueItems) + output["uniqueItems"] = true; + } + + // Print object fields + if (CJSONUtils::HasType(type, ObjectValue)) + { + if (properties.size() > 0) + { + output["properties"] = CVariant(CVariant::VariantTypeObject); + + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesEnd = properties.end(); + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator propertiesIterator; + for (propertiesIterator = properties.begin(); propertiesIterator != propertiesEnd; ++propertiesIterator) + { + propertiesIterator->second->Print(false, false, true, printDescriptions, output["properties"][propertiesIterator->first]); + } + } + + if (!hasAdditionalProperties) + output["additionalProperties"] = false; + else if (additionalProperties != NULL && additionalProperties->type != AnyValue) + additionalProperties->Print(false, false, true, printDescriptions, output["additionalProperties"]); + } + } +} + +void JSONSchemaTypeDefinition::ResolveReference() +{ + // Check and set the reference type before recursing + // to guard against cycles + if (referencedTypeSet) + return; + + referencedTypeSet = true; + + // Take care of all nested types + for (const auto& it : extends) + it->ResolveReference(); + for (const auto& it : unionTypes) + it->ResolveReference(); + for (const auto& it : items) + it->ResolveReference(); + for (const auto& it : additionalItems) + it->ResolveReference(); + for (const auto& it : properties) + it.second->ResolveReference(); + + if (additionalProperties) + additionalProperties->ResolveReference(); + + if (referencedType == nullptr) + return; + + std::string origName = name; + std::string origDescription = description; + bool origOptional = optional; + CVariant origDefaultValue = defaultValue; + JSONSchemaTypeDefinitionPtr referencedTypeDef = referencedType; + + // set all the values from the given type definition + *this = *referencedType; + + // restore the original values + if (!origName.empty()) + name = origName; + + if (!origDescription.empty()) + description = origDescription; + + if (!origOptional) + optional = origOptional; + + if (!origDefaultValue.isNull()) + defaultValue = origDefaultValue; + + if (referencedTypeDef.get() != NULL) + referencedType = referencedTypeDef; + + // This will have been overwritten by the copy of the reference + // type so we need to set it again + referencedTypeSet = true; +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::CJsonSchemaPropertiesMap() : + m_propertiesmap(std::map<std::string, JSONSchemaTypeDefinitionPtr>()) +{ +} + +void JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::add( + const JSONSchemaTypeDefinitionPtr& property) +{ + std::string name = property->name; + StringUtils::ToLower(name); + m_propertiesmap[name] = property; +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::begin() const +{ + return m_propertiesmap.begin(); +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::find(const std::string& key) const +{ + return m_propertiesmap.find(key); +} + +JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::end() const +{ + return m_propertiesmap.end(); +} + +unsigned int JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::size() const +{ + return static_cast<unsigned int>(m_propertiesmap.size()); +} + +JsonRpcMethod::JsonRpcMethod() + : missingReference(), + name(), + method(NULL), + description(), + parameters(), + returns(new JSONSchemaTypeDefinition()) +{ } + +bool JsonRpcMethod::Parse(const CVariant &value) +{ + // Parse XBMC specific information about the method + if (value.isMember("transport") && value["transport"].isArray()) + { + int transport = 0; + for (unsigned int index = 0; index < value["transport"].size(); index++) + transport |= StringToTransportLayer(value["transport"][index].asString()); + + transportneed = (TransportLayerCapability)transport; + } + else + transportneed = StringToTransportLayer(value.isMember("transport") ? value["transport"].asString() : ""); + + if (value.isMember("permission") && value["permission"].isArray()) + { + int permissions = 0; + for (unsigned int index = 0; index < value["permission"].size(); index++) + permissions |= StringToPermission(value["permission"][index].asString()); + + permission = (OperationPermission)permissions; + } + else + permission = StringToPermission(value.isMember("permission") ? value["permission"].asString() : ""); + + description = GetString(value["description"], ""); + + // Check whether there are parameters defined + if (value.isMember("params") && value["params"].isArray()) + { + // Loop through all defined parameters + for (unsigned int paramIndex = 0; paramIndex < value["params"].size(); paramIndex++) + { + CVariant parameter = value["params"][paramIndex]; + // If the parameter definition does not contain a valid "name" or + // "type" element we will ignore it + if (!parameter.isMember("name") || !parameter["name"].isString() || + (!parameter.isMember("type") && !parameter.isMember("$ref") && !parameter.isMember("extends")) || + (parameter.isMember("type") && !parameter["type"].isString() && !parameter["type"].isArray()) || + (parameter.isMember("$ref") && !parameter["$ref"].isString()) || + (parameter.isMember("extends") && !parameter["extends"].isString() && !parameter["extends"].isArray())) + { + CLog::Log(LOGDEBUG, "JSONRPC: Method {} has a badly defined parameter", name); + return false; + } + + // Parse the parameter and add it to the list + // of defined parameters + JSONSchemaTypeDefinitionPtr param = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + if (!parseParameter(parameter, param)) + { + missingReference = param->missingReference; + return false; + } + parameters.push_back(param); + } + } + + // Parse the return value of the method + if (!parseReturn(value)) + { + missingReference = returns->missingReference; + return false; + } + + return true; +} + +JSONRPC_STATUS JsonRpcMethod::Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const +{ + if (transport != NULL && (transport->GetCapabilities() & transportneed) == transportneed) + { + if (client != NULL && (client->GetPermissionFlags() & permission) == permission && (!notification || (permission & OPERATION_PERMISSION_NOTIFICATION) == permission)) + { + methodCall = method; + + // Count the number of actually handled (present) + // parameters + unsigned int handled = 0; + CVariant errorData = CVariant(CVariant::VariantTypeObject); + errorData["method"] = name; + + // Loop through all the parameters to check + for (unsigned int i = 0; i < parameters.size(); i++) + { + // Evaluate the current parameter + JSONRPC_STATUS status = checkParameter(requestParameters, parameters.at(i), i, outputParameters, handled, errorData); + if (status != OK) + { + // Return the error data object in the outputParameters reference + outputParameters = errorData; + return status; + } + } + + // Check if there were unnecessary parameters + if (handled < requestParameters.size()) + { + errorData["message"] = "Too many parameters"; + outputParameters = errorData; + return InvalidParams; + } + + return OK; + } + else + return BadPermission; + } + + return MethodNotFound; +} + +bool JsonRpcMethod::parseParameter(const CVariant& value, + const JSONSchemaTypeDefinitionPtr& parameter) +{ + parameter->name = GetString(value["name"], ""); + + // Parse the type and default value of the parameter + return parameter->Parse(value, true); +} + +bool JsonRpcMethod::parseReturn(const CVariant &value) +{ + // Only parse the "returns" definition if there is one + if (!value.isMember("returns")) + { + returns->type = NullValue; + return true; + } + + // If the type of the return value is defined as a simple string we can parse it directly + if (value["returns"].isString()) + return CJSONServiceDescription::parseJSONSchemaType(value["returns"], returns->unionTypes, returns->type, missingReference); + + // otherwise we have to parse the whole type definition + if (!returns->Parse(value["returns"])) + { + missingReference = returns->missingReference; + return false; + } + + return true; +} + +JSONRPC_STATUS JsonRpcMethod::checkParameter(const CVariant& requestParameters, + const JSONSchemaTypeDefinitionPtr& type, + unsigned int position, + CVariant& outputParameters, + unsigned int& handled, + CVariant& errorData) +{ + // Let's check if the parameter has been provided + if (ParameterExists(requestParameters, type->name, position)) + { + // Get the parameter + CVariant parameterValue = GetParameter(requestParameters, type->name, position); + + // Evaluate the type of the parameter + JSONRPC_STATUS status = type->Check(parameterValue, outputParameters[type->name], errorData["stack"]); + if (status != OK) + return status; + + // The parameter was present and valid + handled++; + } + // If the parameter has not been provided but is optional + // we can use its default value + else if (type->optional) + outputParameters[type->name] = type->defaultValue; + // The parameter is required but has not been provided => invalid + else + { + errorData["stack"]["name"] = type->name; + SchemaValueTypeToJson(type->type, errorData["stack"]["type"]); + errorData["stack"]["message"] = "Missing parameter"; + return InvalidParams; + } + + return OK; +} + +void CJSONServiceDescription::ResolveReferences() +{ + for (const auto& it : m_types) + it.second->ResolveReference(); +} + +void CJSONServiceDescription::Cleanup() +{ + // reset all of the static data + m_notifications.clear(); + m_actionMap.clear(); + m_types.clear(); + m_incompleteDefinitions.clear(); +} + +bool CJSONServiceDescription::prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name) +{ + if (description.empty()) + { + CLog::Log(LOGERROR, "JSONRPC: Missing JSON Schema definition for \"{}\"", name); + return false; + } + + if (description.at(0) != '{') + description = StringUtils::Format("{{{:s}}}", description); + + // Make sure the method description actually exists and represents an object + if (!CJSONVariantParser::Parse(description, descriptionObject) || !descriptionObject.isObject()) + { + CLog::Log(LOGERROR, "JSONRPC: Unable to parse JSON Schema definition for \"{}\"", name); + return false; + } + + CVariant::const_iterator_map member = descriptionObject.begin_map(); + if (member != descriptionObject.end_map()) + name = member->first; + + if (name.empty() || + (!descriptionObject[name].isMember("type") && !descriptionObject[name].isMember("$ref") && !descriptionObject[name].isMember("extends"))) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for \"{}\"", name); + return false; + } + + return true; +} + +bool CJSONServiceDescription::addMethod(const std::string &jsonMethod, MethodCall method) +{ + CVariant descriptionObject; + std::string methodName; + + std::string modJsonMethod = jsonMethod; + // Make sure the method description actually exists and represents an object + if (!prepareDescription(modJsonMethod, descriptionObject, methodName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for method \"{}\"", methodName); + return false; + } + + if (m_actionMap.find(methodName) != m_actionMap.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a method with the name \"{}\"", methodName); + return false; + } + + std::string type = GetString(descriptionObject[methodName]["type"], ""); + if (type.compare("method") != 0) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for method \"{}\"", methodName); + return false; + } + + if (method == NULL) + { + unsigned int size = sizeof(m_methodMaps) / sizeof(JsonRpcMethodMap); + for (unsigned int index = 0; index < size; index++) + { + if (methodName.compare(m_methodMaps[index].name) == 0) + { + method = m_methodMaps[index].method; + break; + } + } + + if (method == NULL) + { + CLog::Log(LOGERROR, "JSONRPC: Missing implementation for method \"{}\"", methodName); + return false; + } + } + + // Parse the details of the method + JsonRpcMethod newMethod; + newMethod.name = methodName; + newMethod.method = method; + + if (!newMethod.Parse(descriptionObject[newMethod.name])) + { + CLog::Log(LOGERROR, "JSONRPC: Could not parse method \"{}\"", methodName); + if (!newMethod.missingReference.empty()) + { + IncompleteSchemaDefinition incomplete; + incomplete.Schema = modJsonMethod; + incomplete.Type = SchemaDefinitionMethod; + incomplete.Method = method; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(newMethod.missingReference); + if (iter == m_incompleteDefinitions.end()) + m_incompleteDefinitions[newMethod.missingReference] = std::vector<IncompleteSchemaDefinition>(); + + CLog::Log( + LOGINFO, + "JSONRPC: Adding method \"{}\" to list of incomplete definitions (waiting for \"{}\")", + methodName, newMethod.missingReference); + m_incompleteDefinitions[newMethod.missingReference].push_back(incomplete); + } + + return false; + } + + m_actionMap.add(newMethod); + + return true; +} + +bool CJSONServiceDescription::AddType(const std::string &jsonType) +{ + CVariant descriptionObject; + std::string typeName; + + std::string modJsonType = jsonType; + if (!prepareDescription(modJsonType, descriptionObject, typeName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for type \"{}\"", typeName); + return false; + } + + if (m_types.find(typeName) != m_types.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a type with the name \"{}\"", typeName); + return false; + } + + // Make sure the "id" attribute is correctly populated + descriptionObject[typeName]["id"] = typeName; + + JSONSchemaTypeDefinitionPtr globalType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + globalType->name = typeName; + globalType->ID = typeName; + CJSONServiceDescription::addReferenceTypeDefinition(globalType); + + if (!globalType->Parse(descriptionObject[typeName])) + { + CLog::Log(LOGWARNING, "JSONRPC: Could not parse type \"{}\"", typeName); + CJSONServiceDescription::removeReferenceTypeDefinition(typeName); + if (!globalType->missingReference.empty()) + { + IncompleteSchemaDefinition incomplete; + incomplete.Schema = modJsonType; + incomplete.Type = SchemaDefinitionType; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(globalType->missingReference); + if (iter == m_incompleteDefinitions.end()) + m_incompleteDefinitions[globalType->missingReference] = std::vector<IncompleteSchemaDefinition>(); + + CLog::Log( + LOGINFO, + "JSONRPC: Adding type \"{}\" to list of incomplete definitions (waiting for \"{}\")", + typeName, globalType->missingReference); + m_incompleteDefinitions[globalType->missingReference].push_back(incomplete); + } + + globalType.reset(); + + return false; + } + + return true; +} + +bool CJSONServiceDescription::AddMethod(const std::string &jsonMethod, MethodCall method) +{ + if (method == NULL) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSONRPC method implementation"); + return false; + } + + return addMethod(jsonMethod, method); +} + +bool CJSONServiceDescription::AddBuiltinMethod(const std::string &jsonMethod) +{ + return addMethod(jsonMethod, NULL); +} + +bool CJSONServiceDescription::AddNotification(const std::string &jsonNotification) +{ + CVariant descriptionObject; + std::string notificationName; + + std::string modJsonNotification = jsonNotification; + // Make sure the notification description actually exists and represents an object + if (!prepareDescription(modJsonNotification, descriptionObject, notificationName)) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON Schema definition for notification \"{}\"", + notificationName); + return false; + } + + if (m_notifications.find(notificationName) != m_notifications.end()) + { + CLog::Log(LOGERROR, "JSONRPC: There already is a notification with the name \"{}\"", + notificationName); + return false; + } + + std::string type = GetString(descriptionObject[notificationName]["type"], ""); + if (type.compare("notification") != 0) + { + CLog::Log(LOGERROR, "JSONRPC: Invalid JSON type for notification \"{}\"", notificationName); + return false; + } + + m_notifications[notificationName] = descriptionObject; + + return true; +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type /* = CVariant::VariantTypeNull */, const CVariant &defaultValue /* = CVariant::ConstNullVariant */) +{ + if (name.empty() || m_types.find(name) != m_types.end() || + values.size() == 0) + return false; + + JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + definition->ID = name; + + std::vector<CVariant::VariantType> types; + bool autoType = false; + if (type == CVariant::VariantTypeNull) + autoType = true; + else + types.push_back(type); + + for (unsigned int index = 0; index < values.size(); index++) + { + if (autoType) + types.push_back(values[index].type()); + else if (type != CVariant::VariantTypeConstNull && type != values[index].type()) + return false; + } + definition->enums.insert(definition->enums.begin(), values.begin(), values.end()); + + int schemaType = (int)AnyValue; + for (unsigned int index = 0; index < types.size(); index++) + { + JSONSchemaType currentType; + switch (type) + { + case CVariant::VariantTypeString: + currentType = StringValue; + break; + case CVariant::VariantTypeDouble: + currentType = NumberValue; + break; + case CVariant::VariantTypeInteger: + case CVariant::VariantTypeUnsignedInteger: + currentType = IntegerValue; + break; + case CVariant::VariantTypeBoolean: + currentType = BooleanValue; + break; + case CVariant::VariantTypeArray: + currentType = ArrayValue; + break; + case CVariant::VariantTypeObject: + currentType = ObjectValue; + break; + case CVariant::VariantTypeConstNull: + currentType = AnyValue; + break; + default: + case CVariant::VariantTypeNull: + return false; + } + + if (index == 0) + schemaType = currentType; + else + schemaType |= (int)currentType; + } + definition->type = (JSONSchemaType)schemaType; + + if (defaultValue.type() == CVariant::VariantTypeConstNull) + definition->defaultValue = definition->enums.at(0); + else + definition->defaultValue = defaultValue; + + addReferenceTypeDefinition(definition); + + return true; +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<std::string> &values) +{ + std::vector<CVariant> enums; + enums.reserve(values.size()); + for (const auto& it : values) + enums.emplace_back(it); + + return AddEnum(name, enums, CVariant::VariantTypeString); +} + +bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector<int> &values) +{ + std::vector<CVariant> enums; + enums.reserve(values.size()); + for (const auto& it : values) + enums.emplace_back(it); + + return AddEnum(name, enums, CVariant::VariantTypeInteger); +} + +const char* CJSONServiceDescription::GetVersion() +{ + return JSONRPC_SERVICE_VERSION; +} + +JSONRPC_STATUS CJSONServiceDescription::Print(CVariant &result, ITransportLayer *transport, IClient *client, + bool printDescriptions /* = true */, bool printMetadata /* = false */, bool filterByTransport /* = true */, + const std::string &filterByName /* = "" */, const std::string &filterByType /* = "" */, bool printReferences /* = true */) +{ + std::map<std::string, JSONSchemaTypeDefinitionPtr> types; + CJsonRpcMethodMap methods; + std::map<std::string, CVariant> notifications; + + int clientPermissions = client->GetPermissionFlags(); + int transportCapabilities = transport->GetCapabilities(); + + if (filterByName.size() > 0) + { + std::string name = filterByName; + + if (filterByType == "method") + { + StringUtils::ToLower(name); + + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator = m_actionMap.find(name); + if (methodIterator != m_actionMap.end() && + (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) + methods.add(methodIterator->second); + else + return InvalidParams; + } + else if (filterByType == "namespace") + { + // append a . delimiter to make sure we check for a namespace + StringUtils::ToLower(name); + name.append("."); + + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = m_actionMap.end(); + for (methodIterator = m_actionMap.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + // Check if the given name is at the very beginning of the method name + if (methodIterator->first.find(name) == 0 && + (clientPermissions & methodIterator->second.permission) == methodIterator->second.permission && ((transportCapabilities & methodIterator->second.transportneed) == methodIterator->second.transportneed || !filterByTransport)) + methods.add(methodIterator->second); + } + + if (methods.begin() == methods.end()) + return InvalidParams; + } + else if (filterByType == "type") + { + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(name); + if (typeIterator != m_types.end()) + types[typeIterator->first] = typeIterator->second; + else + return InvalidParams; + } + else if (filterByType == "notification") + { + std::map<std::string, CVariant>::const_iterator notificationIterator = m_notifications.find(name); + if (notificationIterator != m_notifications.end()) + notifications[notificationIterator->first] = notificationIterator->second; + else + return InvalidParams; + } + else + return InvalidParams; + + // If we need to print all referenced types we have to go through all parameters etc + if (printReferences) + { + std::vector<std::string> referencedTypes; + + // Loop through all printed types to get all referenced types + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator; + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end(); + for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) + getReferencedTypes(typeIterator->second, referencedTypes); + + // Loop through all printed method's parameters and return value to get all referenced types + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); + for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + for (unsigned int index = 0; index < methodIterator->second.parameters.size(); index++) + getReferencedTypes(methodIterator->second.parameters.at(index), referencedTypes); + + getReferencedTypes(methodIterator->second.returns, referencedTypes); + } + + for (unsigned int index = 0; index < referencedTypes.size(); index++) + { + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator = m_types.find(referencedTypes.at(index)); + if (typeIterator != m_types.end()) + types[typeIterator->first] = typeIterator->second; + } + } + } + else + { + types = m_types; + methods = m_actionMap; + notifications = m_notifications; + } + + // Print the header + result["id"] = JSONRPC_SERVICE_ID; + result["version"] = JSONRPC_SERVICE_VERSION; + result["description"] = JSONRPC_SERVICE_DESCRIPTION; + + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIterator; + std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator typeIteratorEnd = types.end(); + for (typeIterator = types.begin(); typeIterator != typeIteratorEnd; ++typeIterator) + { + CVariant currentType = CVariant(CVariant::VariantTypeObject); + typeIterator->second->Print(false, true, true, printDescriptions, currentType); + + result["types"][typeIterator->first] = currentType; + } + + // Iterate through all json rpc methods + CJsonRpcMethodMap::JsonRpcMethodIterator methodIterator; + CJsonRpcMethodMap::JsonRpcMethodIterator methodIteratorEnd = methods.end(); + for (methodIterator = methods.begin(); methodIterator != methodIteratorEnd; methodIterator++) + { + if ((clientPermissions & methodIterator->second.permission) != methodIterator->second.permission || ((transportCapabilities & methodIterator->second.transportneed) != methodIterator->second.transportneed && filterByTransport)) + continue; + + CVariant currentMethod = CVariant(CVariant::VariantTypeObject); + + currentMethod["type"] = "method"; + if (printDescriptions && !methodIterator->second.description.empty()) + currentMethod["description"] = methodIterator->second.description; + if (printMetadata) + { + CVariant permissions(CVariant::VariantTypeArray); + for (int i = ReadData; i <= OPERATION_PERMISSION_ALL; i *= 2) + { + if ((methodIterator->second.permission & i) == i) + permissions.push_back(PermissionToString((OperationPermission)i)); + } + + if (permissions.size() == 1) + currentMethod["permission"] = permissions[0]; + else + currentMethod["permission"] = permissions; + } + + currentMethod["params"] = CVariant(CVariant::VariantTypeArray); + for (unsigned int paramIndex = 0; paramIndex < methodIterator->second.parameters.size(); paramIndex++) + { + CVariant param = CVariant(CVariant::VariantTypeObject); + methodIterator->second.parameters.at(paramIndex)->Print(true, false, true, printDescriptions, param); + currentMethod["params"].append(param); + } + + methodIterator->second.returns->Print(false, false, false, printDescriptions, currentMethod["returns"]); + + result["methods"][methodIterator->second.name] = currentMethod; + } + + // Print notification description + std::map<std::string, CVariant>::const_iterator notificationIterator; + std::map<std::string, CVariant>::const_iterator notificationIteratorEnd = notifications.end(); + for (notificationIterator = notifications.begin(); notificationIterator != notificationIteratorEnd; ++notificationIterator) + result["notifications"][notificationIterator->first] = notificationIterator->second[notificationIterator->first]; + + return OK; +} + +JSONRPC_STATUS CJSONServiceDescription::CheckCall(const char* const method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) +{ + CJsonRpcMethodMap::JsonRpcMethodIterator iter = m_actionMap.find(method); + if (iter != m_actionMap.end()) + return iter->second.Check(requestParameters, transport, client, notification, methodCall, outputParameters); + + return MethodNotFound; +} + +JSONSchemaTypeDefinitionPtr CJSONServiceDescription::GetType(const std::string &identification) +{ + std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator iter = m_types.find(identification); + if (iter == m_types.end()) + return JSONSchemaTypeDefinitionPtr(); + + return iter->second; +} + +bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinitionPtr>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference) +{ + missingReference.clear(); + schemaType = AnyValue; + + if (value.isArray()) + { + int parsedType = 0; + // If the defined type is an array, we have + // to handle a union type + for (unsigned int typeIndex = 0; typeIndex < value.size(); typeIndex++) + { + JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + // If the type is a string try to parse it + if (value[typeIndex].isString()) + definition->type = StringToSchemaValueType(value[typeIndex].asString()); + else if (value[typeIndex].isObject()) + { + if (!definition->Parse(value[typeIndex])) + { + missingReference = definition->missingReference; + CLog::Log(LOGERROR, "JSONRPC: Invalid type schema in union type definition"); + return false; + } + } + else + { + CLog::Log(LOGWARNING, "JSONRPC: Invalid type in union type definition"); + return false; + } + + definition->optional = false; + typeDefinitions.push_back(definition); + parsedType |= definition->type; + } + + // If the type has not been set yet set it to "any" + if (parsedType != 0) + schemaType = (JSONSchemaType)parsedType; + + return true; + } + + if (value.isString()) + { + schemaType = StringToSchemaValueType(value.asString()); + return true; + } + + return false; +} + +void CJSONServiceDescription::addReferenceTypeDefinition( + const JSONSchemaTypeDefinitionPtr& typeDefinition) +{ + // If the given json value is no object or does not contain an "id" field + // of type string it is no valid type definition + if (typeDefinition->ID.empty()) + return; + + // If the id has already been defined we ignore the type definition + if (m_types.find(typeDefinition->ID) != m_types.end()) + return; + + // Add the type to the list of type definitions + m_types[typeDefinition->ID] = typeDefinition; + + IncompleteSchemaDefinitionMap::iterator iter = m_incompleteDefinitions.find(typeDefinition->ID); + if (iter == m_incompleteDefinitions.end()) + return; + + CLog::Log(LOGINFO, "JSONRPC: Resolving incomplete types/methods referencing {}", + typeDefinition->ID); + for (unsigned int index = 0; index < iter->second.size(); index++) + { + if (iter->second[index].Type == SchemaDefinitionType) + AddType(iter->second[index].Schema); + else + AddMethod(iter->second[index].Schema, iter->second[index].Method); + } + + m_incompleteDefinitions.erase(typeDefinition->ID); +} + +void CJSONServiceDescription::removeReferenceTypeDefinition(const std::string &typeID) +{ + if (typeID.empty()) + return; + + std::map<std::string, JSONSchemaTypeDefinitionPtr>::iterator type = m_types.find(typeID); + if (type != m_types.end()) + m_types.erase(type); +} + +void CJSONServiceDescription::getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type, + std::vector<std::string>& referencedTypes) +{ + // If the current type is a referenceable object, we can add it to the list + if (type->ID.size() > 0) + { + for (unsigned int index = 0; index < referencedTypes.size(); index++) + { + // The referenceable object has already been added to the list so we can just skip it + if (type->ID == referencedTypes.at(index)) + return; + } + + referencedTypes.push_back(type->ID); + } + + // If the current type is an object we need to check its properties + if (HasType(type->type, ObjectValue)) + { + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iter; + JSONSchemaTypeDefinition::CJsonSchemaPropertiesMap::JSONSchemaPropertiesIterator iterEnd = type->properties.end(); + for (iter = type->properties.begin(); iter != iterEnd; ++iter) + getReferencedTypes(iter->second, referencedTypes); + } + // If the current type is an array we need to check its items + if (HasType(type->type, ArrayValue)) + { + unsigned int index; + for (index = 0; index < type->items.size(); index++) + getReferencedTypes(type->items.at(index), referencedTypes); + + for (index = 0; index < type->additionalItems.size(); index++) + getReferencedTypes(type->additionalItems.at(index), referencedTypes); + } + + // If the current type extends others type we need to check those types + for (unsigned int index = 0; index < type->extends.size(); index++) + getReferencedTypes(type->extends.at(index), referencedTypes); + + // If the current type is a union type we need to check those types + for (unsigned int index = 0; index < type->unionTypes.size(); index++) + getReferencedTypes(type->unionTypes.at(index), referencedTypes); +} + +CJSONServiceDescription::CJsonRpcMethodMap::CJsonRpcMethodMap(): + m_actionmap(std::map<std::string, JsonRpcMethod>()) +{ +} + +void CJSONServiceDescription::CJsonRpcMethodMap::clear() +{ + m_actionmap.clear(); +} + +void CJSONServiceDescription::CJsonRpcMethodMap::add(const JsonRpcMethod &method) +{ + std::string name = method.name; + StringUtils::ToLower(name); + m_actionmap[name] = method; +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::begin() const +{ + return m_actionmap.begin(); +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::find(const std::string& key) const +{ + return m_actionmap.find(key); +} + +CJSONServiceDescription::CJsonRpcMethodMap::JsonRpcMethodIterator CJSONServiceDescription::CJsonRpcMethodMap::end() const +{ + return m_actionmap.end(); +} diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.h b/xbmc/interfaces/json-rpc/JSONServiceDescription.h new file mode 100644 index 0000000..ee48920 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.h @@ -0,0 +1,442 @@ +/* + * 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 "JSONUtils.h" +#include "utils/Variant.h" + +#include <limits> +#include <memory> +#include <string> +#include <vector> + +namespace JSONRPC +{ + class JSONSchemaTypeDefinition; + typedef std::shared_ptr<JSONSchemaTypeDefinition> JSONSchemaTypeDefinitionPtr; + + /*! + \ingroup jsonrpc + \brief Class for a parameter of a + json rpc method. + + Represents a parameter of a defined + json rpc method and is used to verify + and extract the value of the parameter + in a method call. + */ + class JSONSchemaTypeDefinition : protected CJSONUtils + { + public: + JSONSchemaTypeDefinition(); + + bool Parse(const CVariant &value, bool isParameter = false); + JSONRPC_STATUS Check(const CVariant& value, CVariant& outputValue, CVariant& errorData) const; + void Print(bool isParameter, bool isGlobal, bool printDefault, bool printDescriptions, CVariant &output) const; + void ResolveReference(); + + std::string missingReference; + + /*! + \brief Name of the parameter (for + by-name calls) + */ + std::string name; + + /*! + \brief Id of the type (for + referenced types) + Renamed from "id" because of possible + issues with Objective-C. + */ + std::string ID; + + /*! + \brief Referenced object + */ + JSONSchemaTypeDefinitionPtr referencedType; + + /*! + \brief Whether the type has been set + based on the referenced type + */ + bool referencedTypeSet = false; + + /*! + \brief Array of reference types + which are extended by this type. + */ + std::vector<JSONSchemaTypeDefinitionPtr> extends; + + /*! + \brief Description of the parameter + */ + std::string description; + + /*! + \brief JSON schema type of the parameter's value + */ + JSONSchemaType type = AnyValue; + + /*! + \brief JSON schema type definitions in case + of a union type + */ + std::vector<JSONSchemaTypeDefinitionPtr> unionTypes; + + /*! + \brief Whether or not the parameter is + optional + */ + bool optional = true; + + /*! + \brief Default value of the parameter + (only needed when it is optional) + */ + CVariant defaultValue; + + /*! + \brief Minimum value for Integer + or Number types + */ + double minimum; + + /*! + \brief Maximum value for Integer or Number types + */ + double maximum; + + /*! + \brief Whether to exclude the defined Minimum + value from the valid range or not + */ + bool exclusiveMinimum = false; + + /*! + \brief Whether to exclude the defined Maximum + value from the valid range or not + */ + bool exclusiveMaximum = false; + + /*! + \brief Integer by which the value (of type + Integer) must be divisible without rest + */ + unsigned int divisibleBy = 0; + + /*! + \brief Minimum length for String types + */ + int minLength = -1; + + /*! + \brief Maximum length for String types + */ + int maxLength = -1; + + /*! + \brief (Optional) List of allowed values + for the type + */ + std::vector<CVariant> enums; + + /*! + \brief List of possible values in an array + */ + std::vector<JSONSchemaTypeDefinitionPtr> items; + + /*! + \brief Minimum amount of items in the array + */ + unsigned int minItems = 0; + + /*! + \brief Maximum amount of items in the array + */ + unsigned int maxItems = 0; + + /*! + \brief Whether every value in the array + must be unique or not + */ + bool uniqueItems = false; + + /*! + \brief List of json schema definitions for + additional items in an array with tuple + typing (defined schemas in "items") + */ + std::vector<JSONSchemaTypeDefinitionPtr> additionalItems; + + /*! + \brief Maps a properties name to its + json schema type definition + */ + class CJsonSchemaPropertiesMap + { + public: + CJsonSchemaPropertiesMap(); + + void add(const JSONSchemaTypeDefinitionPtr& property); + + typedef std::map<std::string, JSONSchemaTypeDefinitionPtr>::const_iterator JSONSchemaPropertiesIterator; + JSONSchemaPropertiesIterator begin() const; + JSONSchemaPropertiesIterator find(const std::string& key) const; + JSONSchemaPropertiesIterator end() const; + unsigned int size() const; + private: + std::map<std::string, JSONSchemaTypeDefinitionPtr> m_propertiesmap; + }; + + /*! + \brief List of properties of the parameter (only needed when the + parameter is an object) + */ + CJsonSchemaPropertiesMap properties; + + /*! + \brief Whether the type can have additional properties + or not + */ + bool hasAdditionalProperties = false; + + /*! + \brief Type definition for additional properties + */ + JSONSchemaTypeDefinitionPtr additionalProperties; + }; + + /*! + \ingroup jsonrpc + \brief Structure for a published json + rpc method. + + Represents a published json rpc method + and is used to verify an incoming json + rpc request against a defined method. + */ + class JsonRpcMethod : protected CJSONUtils + { + public: + JsonRpcMethod(); + + bool Parse(const CVariant &value); + JSONRPC_STATUS Check(const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters) const; + + std::string missingReference; + + /*! + \brief Name of the represented method + */ + std::string name; + /*! + \brief Pointer tot he implementation + of the represented method + */ + MethodCall method; + /*! + \brief Definition of the type of + request/response + */ + TransportLayerCapability transportneed = Response; + /*! + \brief Definition of the permissions needed + to execute the method + */ + OperationPermission permission = ReadData; + /*! + \brief Description of the method + */ + std::string description; + /*! + \brief List of accepted parameters + */ + std::vector<JSONSchemaTypeDefinitionPtr> parameters; + /*! + \brief Definition of the return value + */ + JSONSchemaTypeDefinitionPtr returns; + + private: + bool parseParameter(const CVariant& value, const JSONSchemaTypeDefinitionPtr& parameter); + bool parseReturn(const CVariant &value); + static JSONRPC_STATUS checkParameter(const CVariant& requestParameters, + const JSONSchemaTypeDefinitionPtr& type, + unsigned int position, + CVariant& outputParameters, + unsigned int& handled, + CVariant& errorData); + }; + + /*! + \ingroup jsonrpc + \brief Structure mapping a json rpc method + definition to an actual method implementation. + */ + typedef struct + { + /*! + \brief Name of the json rpc method. + */ + std::string name; + /*! + \brief Pointer to the actual + implementation of the json rpc + method. + */ + MethodCall method; + } JsonRpcMethodMap; + + /*! + \ingroup jsonrpc + \brief Helper class for json schema service descriptor based + service descriptions for the json rpc API + + Provides static functions to parse a complete json schema + service descriptor of a published service containing json rpc + methods, print the json schema service descriptor representation + into a string (mainly for output purposes) and evaluate and verify + parameters provided in a call to one of the publish json rpc methods + against a parameter definition parsed from a json schema service + descriptor. + */ + class CJSONServiceDescription : public CJSONUtils + { + friend class JSONSchemaTypeDefinition; + friend class JsonRpcMethod; + public: + /*! + \brief Parses the given json schema description and evaluates + and stores the defined type + \param jsonType json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddType(const std::string &jsonType); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined method + \param jsonMethod json schema description to parse + \param method pointer to the implementation + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddMethod(const std::string &jsonMethod, MethodCall method); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined builtin method + \param jsonMethod json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddBuiltinMethod(const std::string &jsonMethod); + + /*! + \brief Parses the given json schema description and evaluates + and stores the defined notification + \param jsonNotification json schema description to parse + \return True if the json schema description has been parsed successfully otherwise false + */ + static bool AddNotification(const std::string &jsonNotification); + + static bool AddEnum(const std::string &name, const std::vector<CVariant> &values, CVariant::VariantType type = CVariant::VariantTypeNull, const CVariant &defaultValue = CVariant::ConstNullVariant); + static bool AddEnum(const std::string &name, const std::vector<std::string> &values); + static bool AddEnum(const std::string &name, const std::vector<int> &values); + + /*! + \brief Gets the version of the json + schema description + \return Version of the json schema description + */ + static const char* GetVersion(); + + /*! + \brief Prints the json schema description into the given result object + \param result Object into which the json schema description is printed + \param transport Transport layer capabilities + \param client Client requesting a print + \param printDescriptions Whether to print descriptions or not + \param printMetadata Whether to print XBMC specific data or not + \param filterByTransport Whether to filter by transport or not + */ + static JSONRPC_STATUS Print(CVariant &result, ITransportLayer *transport, IClient *client, bool printDescriptions = true, bool printMetadata = false, bool filterByTransport = true, const std::string &filterByName = "", const std::string &filterByType = "", bool printReferences = true); + + /*! + \brief Checks the given parameters from the request against the + json schema description for the given method + \param method Called method + \param requestParameters Parameters from the request + \param client Client who sent the request + \param notification Whether the request was sent as a notification or not + \param methodCall Object which will contain the actual C/C++ method to be called + \param outputParameters Cleaned up parameter list + \return OK if the validation of the request succeeded otherwise an appropriate error code + + Checks if the given method is a valid json rpc method, if the client has the permission + to call this method, if the method can be called as a notification or not, assigns the + actual C/C++ implementation of the method to the "methodCall" parameter and checks the + given parameters from the request against the json schema description for the given method. + */ + static JSONRPC_STATUS CheckCall(const char* method, const CVariant &requestParameters, ITransportLayer *transport, IClient *client, bool notification, MethodCall &methodCall, CVariant &outputParameters); + + static JSONSchemaTypeDefinitionPtr GetType(const std::string &identification); + + static void ResolveReferences(); + static void Cleanup(); + + private: + static bool prepareDescription(std::string &description, CVariant &descriptionObject, std::string &name); + static bool addMethod(const std::string &jsonMethod, MethodCall method); + static void parseHeader(const CVariant &descriptionObject); + static bool parseJSONSchemaType(const CVariant &value, std::vector<JSONSchemaTypeDefinitionPtr>& typeDefinitions, JSONSchemaType &schemaType, std::string &missingReference); + static void addReferenceTypeDefinition(const JSONSchemaTypeDefinitionPtr& typeDefinition); + static void removeReferenceTypeDefinition(const std::string &typeID); + + static void getReferencedTypes(const JSONSchemaTypeDefinitionPtr& type, + std::vector<std::string>& referencedTypes); + + class CJsonRpcMethodMap + { + public: + CJsonRpcMethodMap(); + + void add(const JsonRpcMethod &method); + + typedef std::map<std::string, JsonRpcMethod>::const_iterator JsonRpcMethodIterator; + JsonRpcMethodIterator begin() const; + JsonRpcMethodIterator find(const std::string& key) const; + JsonRpcMethodIterator end() const; + + void clear(); + private: + std::map<std::string, JsonRpcMethod> m_actionmap; + }; + + static CJsonRpcMethodMap m_actionMap; + static std::map<std::string, JSONSchemaTypeDefinitionPtr> m_types; + static std::map<std::string, CVariant> m_notifications; + static JsonRpcMethodMap m_methodMaps[]; + + typedef enum SchemaDefinition + { + SchemaDefinitionType, + SchemaDefinitionMethod + } SchemaDefinition; + + typedef struct IncompleteSchemaDefinition + { + std::string Schema; + SchemaDefinition Type; + MethodCall Method; + } IncompleteSchemaDefinition; + + typedef std::map<std::string, std::vector<IncompleteSchemaDefinition> > IncompleteSchemaDefinitionMap; + static IncompleteSchemaDefinitionMap m_incompleteDefinitions; + }; +} diff --git a/xbmc/interfaces/json-rpc/JSONUtils.cpp b/xbmc/interfaces/json-rpc/JSONUtils.cpp new file mode 100644 index 0000000..a8d60d1 --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONUtils.cpp @@ -0,0 +1,38 @@ +/* + * 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 "JSONUtils.h" + +#include "XBDateTime.h" + +namespace JSONRPC +{ + +void CJSONUtils::SetFromDBDate(const CVariant& jsonDate, CDateTime& date) +{ + if (!jsonDate.isString()) + return; + + if (jsonDate.empty()) + date.Reset(); + else + date.SetFromDBDate(jsonDate.asString()); +} + +void CJSONUtils::SetFromDBDateTime(const CVariant& jsonDate, CDateTime& date) +{ + if (!jsonDate.isString()) + return; + + if (jsonDate.empty()) + date.Reset(); + else + date.SetFromDBDateTime(jsonDate.asString()); +} + +} // namespace JSONRPC diff --git a/xbmc/interfaces/json-rpc/JSONUtils.h b/xbmc/interfaces/json-rpc/JSONUtils.h new file mode 100644 index 0000000..150e23b --- /dev/null +++ b/xbmc/interfaces/json-rpc/JSONUtils.h @@ -0,0 +1,490 @@ +/* + * 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 "JSONRPCUtils.h" +#include "playlists/SmartPlayList.h" +#include "utils/JSONVariantParser.h" +#include "utils/JSONVariantWriter.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#include <stdlib.h> +#include <string.h> +#include <vector> + +class CDateTime; + +namespace JSONRPC +{ + /*! + \brief Possible value types of a parameter or return type + */ + enum JSONSchemaType + { + NullValue = 0x01, + StringValue = 0x02, + NumberValue = 0x04, + IntegerValue = 0x08, + BooleanValue = 0x10, + ArrayValue = 0x20, + ObjectValue = 0x40, + AnyValue = 0x80 + }; + + /*! + \ingroup jsonrpc + \brief Helper class containing utility methods to handle + json rpc method calls.*/ + class CJSONUtils + { + public: + static void MillisecondsToTimeObject(int time, CVariant &result) + { + int ms = time % 1000; + result["milliseconds"] = ms; + time = (time - ms) / 1000; + + int s = time % 60; + result["seconds"] = s; + time = (time - s) / 60; + + int m = time % 60; + result["minutes"] = m; + time = (time -m) / 60; + + result["hours"] = time; + } + + protected: + static void HandleLimits(const CVariant ¶meterObject, CVariant &result, int size, int &start, int &end) + { + if (size < 0) + size = 0; + + start = (int)parameterObject["limits"]["start"].asInteger(); + end = (int)parameterObject["limits"]["end"].asInteger(); + end = (end <= 0 || end > size) ? size : end; + start = start > end ? end : start; + + result["limits"]["start"] = start; + result["limits"]["end"] = end; + result["limits"]["total"] = size; + } + + static bool ParseSorting(const CVariant ¶meterObject, SortBy &sortBy, SortOrder &sortOrder, SortAttribute &sortAttributes) + { + std::string method = parameterObject["sort"]["method"].asString(); + std::string order = parameterObject["sort"]["order"].asString(); + StringUtils::ToLower(method); + StringUtils::ToLower(order); + + // parse the sort attributes + sortAttributes = SortAttributeNone; + if (parameterObject["sort"]["ignorearticle"].asBoolean()) + sortAttributes = static_cast<SortAttribute>(sortAttributes | SortAttributeIgnoreArticle); + if (parameterObject["sort"]["useartistsortname"].asBoolean()) + sortAttributes = static_cast<SortAttribute>(sortAttributes | SortAttributeUseArtistSortName); + + // parse the sort order + sortOrder = SortUtils::SortOrderFromString(order); + if (sortOrder == SortOrderNone) + return false; + + // parse the sort method + sortBy = SortUtils::SortMethodFromString(method); + + return true; + } + + static void ParseLimits(const CVariant ¶meterObject, int &limitStart, int &limitEnd) + { + limitStart = (int)parameterObject["limits"]["start"].asInteger(); + limitEnd = (int)parameterObject["limits"]["end"].asInteger(); + } + + /*! + \brief Checks if the given object contains a parameter + \param parameterObject Object to check for a parameter + \param key Possible name of the parameter + \param position Possible position of the parameter + \return True if the parameter is available otherwise false + + Checks the given object for a parameter with the given key (if + the given object is not an array) or for a parameter at the + given position (if the given object is an array). + */ + static inline bool ParameterExists(const CVariant& parameterObject, + const std::string& key, + unsigned int position) + { + return IsValueMember(parameterObject, key) || + (parameterObject.isArray() && parameterObject.size() > position); + } + + /*! + \brief Checks if the given object contains a value + with the given key + \param value Value to check for the member + \param key Key of the member to check for + \return True if the given object contains a member with + the given key otherwise false + */ + static inline bool IsValueMember(const CVariant& value, const std::string& key) + { + return value.isMember(key); + } + + /*! + \brief Returns the json value of a parameter + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \return Json value of the parameter with the given name or at the + given position + + Returns the value of the parameter with the given key (if + the given object is not an array) or of the parameter at the + given position (if the given object is an array). + */ + static inline CVariant GetParameter(const CVariant& parameterObject, + const std::string& key, + unsigned int position) + { + return IsValueMember(parameterObject, key) ? parameterObject[key] : parameterObject[position]; + } + + /*! + \brief Returns the json value of a parameter or the given + default value + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \param fallback Default value of the parameter + \return Json value of the parameter with the given name or at the + given position or the default value if the parameter does not exist + + Returns the value of the parameter with the given key (if + the given object is not an array) or of the parameter at the + given position (if the given object is an array). If the + parameter does not exist the given default value is returned. + */ + static inline CVariant GetParameter(const CVariant& parameterObject, + const std::string& key, + unsigned int position, + const CVariant& fallback) + { + return IsValueMember(parameterObject, key) + ? parameterObject[key] + : ((parameterObject.isArray() && parameterObject.size() > position) + ? parameterObject[position] + : fallback); + } + + /*! + \brief Returns the given json value as a string + \param value Json value to convert to a string + \param defaultValue Default string value + \return String value of the given json value or the default value + if the given json value is no string + */ + static inline std::string GetString(const CVariant &value, const char* defaultValue) + { + std::string str = defaultValue; + if (value.isString()) + { + str = value.asString(); + } + + return str; + } + + /*! + \brief Returns a TransportLayerCapability value of the + given string representation + \param transport String representation of the TransportLayerCapability + \return TransportLayerCapability value of the given string representation + */ + static inline TransportLayerCapability StringToTransportLayer(const std::string& transport) + { + if (transport.compare("Announcing") == 0) + return Announcing; + if (transport.compare("FileDownloadDirect") == 0) + return FileDownloadDirect; + if (transport.compare("FileDownloadRedirect") == 0) + return FileDownloadRedirect; + + return Response; + } + + /*! + \brief Returns a JSONSchemaType value for the given + string representation + \param valueType String representation of the JSONSchemaType + \return JSONSchemaType value of the given string representation + */ + static inline JSONSchemaType StringToSchemaValueType(const std::string& valueType) + { + if (valueType.compare("null") == 0) + return NullValue; + if (valueType.compare("string") == 0) + return StringValue; + if (valueType.compare("number") == 0) + return NumberValue; + if (valueType.compare("integer") == 0) + return IntegerValue; + if (valueType.compare("boolean") == 0) + return BooleanValue; + if (valueType.compare("array") == 0) + return ArrayValue; + if (valueType.compare("object") == 0) + return ObjectValue; + + return AnyValue; + } + + /*! + \brief Returns a string representation for the + given JSONSchemaType + \param valueType Specific JSONSchemaType + \return String representation of the given JSONSchemaType + */ + static inline std::string SchemaValueTypeToString(JSONSchemaType valueType) + { + std::vector<JSONSchemaType> types = std::vector<JSONSchemaType>(); + for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2) + { + if (HasType(valueType, (JSONSchemaType)value)) + types.push_back((JSONSchemaType)value); + } + + std::string strType; + if (types.size() > 1) + strType.append("["); + + for (unsigned int index = 0; index < types.size(); index++) + { + if (index > 0) + strType.append(", "); + + switch (types.at(index)) + { + case StringValue: + strType.append("string"); + break; + case NumberValue: + strType.append("number"); + break; + case IntegerValue: + strType.append("integer"); + break; + case BooleanValue: + strType.append("boolean"); + break; + case ArrayValue: + strType.append("array"); + break; + case ObjectValue: + strType.append("object"); + break; + case AnyValue: + strType.append("any"); + break; + case NullValue: + strType.append("null"); + break; + default: + strType.append("unknown"); + } + } + + if (types.size() > 1) + strType.append("]"); + + return strType; + } + + /*! + \brief Converts the given json schema type into + a json object + \param valueTye json schema type(s) + \param jsonObject json object into which the json schema type(s) are stored + */ + static inline void SchemaValueTypeToJson(JSONSchemaType valueType, CVariant &jsonObject) + { + jsonObject = CVariant(CVariant::VariantTypeArray); + for (unsigned int value = 0x01; value <= (unsigned int)AnyValue; value *= 2) + { + if (HasType(valueType, (JSONSchemaType)value)) + jsonObject.append(SchemaValueTypeToString((JSONSchemaType)value)); + } + + if (jsonObject.size() == 1) + { + CVariant jsonType = jsonObject[0]; + jsonObject = jsonType; + } + } + + static inline const char *ValueTypeToString(CVariant::VariantType valueType) + { + switch (valueType) + { + case CVariant::VariantTypeString: + return "string"; + case CVariant::VariantTypeDouble: + return "number"; + case CVariant::VariantTypeInteger: + case CVariant::VariantTypeUnsignedInteger: + return "integer"; + case CVariant::VariantTypeBoolean: + return "boolean"; + case CVariant::VariantTypeArray: + return "array"; + case CVariant::VariantTypeObject: + return "object"; + case CVariant::VariantTypeNull: + case CVariant::VariantTypeConstNull: + return "null"; + default: + return "unknown"; + } + } + + /*! + \brief Checks if the parameter with the given name or at + the given position is of a certain type + \param parameterObject Object containing all provided parameters + \param key Possible name of the parameter + \param position Possible position of the parameter + \param valueType Expected type of the parameter + \return True if the specific parameter is of the given type otherwise false + */ + static inline bool IsParameterType(const CVariant ¶meterObject, const char *key, unsigned int position, JSONSchemaType valueType) + { + if ((valueType & AnyValue) == AnyValue) + return true; + + CVariant parameter; + if (IsValueMember(parameterObject, key)) + parameter = parameterObject[key]; + else if(parameterObject.isArray() && parameterObject.size() > position) + parameter = parameterObject[position]; + + return IsType(parameter, valueType); + } + + /*! + \brief Checks if the given json value is of the given type + \param value Json value to check + \param valueType Expected type of the json value + \return True if the given json value is of the given type otherwise false + */ + static inline bool IsType(const CVariant &value, JSONSchemaType valueType) + { + if (HasType(valueType, AnyValue)) + return true; + if (HasType(valueType, StringValue) && value.isString()) + return true; + if (HasType(valueType, NumberValue) && (value.isInteger() || value.isUnsignedInteger() || value.isDouble())) + return true; + if (HasType(valueType, IntegerValue) && (value.isInteger() || value.isUnsignedInteger())) + return true; + if (HasType(valueType, BooleanValue) && value.isBoolean()) + return true; + if (HasType(valueType, ArrayValue) && value.isArray()) + return true; + if (HasType(valueType, ObjectValue) && value.isObject()) + return true; + + return value.isNull(); + } + + /*! + \brief Sets the value of the given json value to the + default value of the given type + \param value Json value to be set + \param valueType Type of the default value + */ + static inline void SetDefaultValue(CVariant &value, JSONSchemaType valueType) + { + switch (valueType) + { + case StringValue: + value = CVariant(""); + break; + case NumberValue: + value = CVariant(CVariant::VariantTypeDouble); + break; + case IntegerValue: + value = CVariant(CVariant::VariantTypeInteger); + break; + case BooleanValue: + value = CVariant(CVariant::VariantTypeBoolean); + break; + case ArrayValue: + value = CVariant(CVariant::VariantTypeArray); + break; + case ObjectValue: + value = CVariant(CVariant::VariantTypeObject); + break; + default: + value = CVariant(CVariant::VariantTypeNull); + } + } + + static inline bool HasType(JSONSchemaType typeObject, JSONSchemaType type) { return (typeObject & type) == type; } + + static inline bool ParameterNotNull(const CVariant& parameterObject, const std::string& key) + { + return parameterObject.isMember(key) && !parameterObject[key].isNull(); + } + + /*! + \brief Copies the values from the jsonStringArray to the stringArray. + stringArray is cleared. + \param jsonStringArray JSON object representing a string array + \param stringArray String array where the values are copied into (cleared) + */ + static void CopyStringArray(const CVariant &jsonStringArray, std::vector<std::string> &stringArray) + { + if (!jsonStringArray.isArray()) + return; + + stringArray.clear(); + for (CVariant::const_iterator_array it = jsonStringArray.begin_array(); it != jsonStringArray.end_array(); ++it) + stringArray.push_back(it->asString()); + } + + static void SetFromDBDate(const CVariant& jsonDate, CDateTime& date); + + static void SetFromDBDateTime(const CVariant& jsonDate, CDateTime& date); + + static bool GetXspFiltering(const std::string &type, const CVariant &filter, std::string &xsp) + { + if (type.empty() || !filter.isObject()) + return false; + + CVariant xspObj(CVariant::VariantTypeObject); + xspObj["type"] = type; + + if (filter.isMember("field")) + { + xspObj["rules"]["and"] = CVariant(CVariant::VariantTypeArray); + xspObj["rules"]["and"].push_back(filter); + } + else + xspObj["rules"] = filter; + + CSmartPlaylist playlist; + return playlist.Load(xspObj) && playlist.SaveAsJson(xsp, false); + } + }; +} diff --git a/xbmc/interfaces/json-rpc/PVROperations.cpp b/xbmc/interfaces/json-rpc/PVROperations.cpp new file mode 100644 index 0000000..3611ad6 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PVROperations.cpp @@ -0,0 +1,543 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PVROperations.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsTimers.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimers.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using namespace PVR; +using namespace KODI::MESSAGING; + +JSONRPC_STATUS CPVROperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelGroups(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + CPVRChannelGroups *channelGroups = channelGroupContainer->Get(parameterObject["channeltype"].asString().compare("radio") == 0); + if (channelGroups == NULL) + return FailedToExecute; + + int start, end; + + std::vector<std::shared_ptr<CPVRChannelGroup>> groupList = channelGroups->GetMembers(true); + HandleLimits(parameterObject, result, groupList.size(), start, end); + for (int index = start; index < end; index++) + FillChannelGroupDetails(groupList.at(index), parameterObject, result["channelgroups"], true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelGroupDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroup> channelGroup; + CVariant id = parameterObject["channelgroupid"]; + if (id.isInteger()) + channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + else if (id.isString()) + channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); + + if (channelGroup == NULL) + return InvalidParams; + + FillChannelGroupDetails(channelGroup, parameterObject, result["channelgroupdetails"], false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroup> channelGroup; + CVariant id = parameterObject["channelgroupid"]; + if (id.isInteger()) + channelGroup = channelGroupContainer->GetByIdFromAll((int)id.asInteger()); + else if (id.isString()) + channelGroup = channelGroupContainer->GetGroupAll(id.asString() == "allradio"); + + if (channelGroup == NULL) + return InvalidParams; + + CFileItemList channels; + const auto groupMembers = channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE); + for (const auto& groupMember : groupMembers) + { + channels.Add(std::make_shared<CFileItem>(groupMember)); + } + + HandleFileItemList("channelid", false, "channels", channels, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetChannelDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById( + static_cast<int>(parameterObject["channelid"].asInteger())); + if (channel == NULL) + return InvalidParams; + + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(channel); + if (!groupMember) + return InvalidParams; + + HandleFileItem("channelid", false, "channeldetails", std::make_shared<CFileItem>(groupMember), + parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetClients(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + int start, end; + + auto clientInfos = CServiceBroker::GetPVRManager().Clients()->GetEnabledClientInfos(); + HandleLimits(parameterObject, result, clientInfos.size(), start, end); + for (int index = start; index < end; index++) + result["clients"].append(clientInfos[index]); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcasts(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById((int)parameterObject["channelid"].asInteger()); + if (channel == NULL) + return InvalidParams; + + std::shared_ptr<CPVREpg> channelEpg = channel->GetEPG(); + if (!channelEpg) + return InternalError; + + CFileItemList programFull; + + const std::vector<std::shared_ptr<CPVREpgInfoTag>> tags = channelEpg->GetTags(); + for (const auto& tag : tags) + { + programFull.Add(std::make_shared<CFileItem>(tag)); + } + + HandleFileItemList("broadcastid", false, "broadcasts", programFull, parameterObject, result, programFull.Size(), true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcastDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + HandleFileItem("broadcastid", false, "broadcastdetails", CFileItemPtr(new CFileItem(epgTag)), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetBroadcastIsPlayable(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + result = epgTag->IsPlayable(); + + return OK; +} + +JSONRPC_STATUS CPVROperations::Record(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRChannel> pChannel; + CVariant channel = parameterObject["channel"]; + if (channel.isString() && channel.asString() == "current") + { + pChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); + if (!pChannel) + return InternalError; + } + else if (channel.isInteger()) + { + std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + pChannel = channelGroupContainer->GetChannelById((int)channel.asInteger()); + } + else + return InvalidParams; + + if (pChannel == NULL) + return InvalidParams; + else if (!pChannel->CanRecord()) + return FailedToExecute; + + CVariant record = parameterObject["record"]; + bool bIsRecording = CServiceBroker::GetPVRManager().Timers()->IsRecordingOnChannel(*pChannel); + bool toggle = true; + if (record.isBoolean() && record.asBoolean() == bIsRecording) + toggle = false; + + if (toggle) + { + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().SetRecordingOnChannel( + pChannel, !bIsRecording)) + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPVROperations::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + if (parameterObject.isMember("clientid")) + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().StartChannelScan( + parameterObject["clientid"].asInteger())) + return ACK; + } + else + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().StartChannelScan()) + return ACK; + } + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::GetPropertyValue(const std::string &property, CVariant &result) +{ + bool started = CServiceBroker::GetPVRManager().IsStarted(); + + if (property == "available") + result = started; + else if (property == "recording") + { + if (started) + result = CServiceBroker::GetPVRManager().PlaybackState()->IsRecording(); + else + result = false; + } + else if (property == "scanning") + { + if (started) + result = CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().IsRunningChannelScan(); + else + result = false; + } + else + return InvalidParams; + + return OK; +} + +void CPVROperations::FillChannelGroupDetails(const std::shared_ptr<CPVRChannelGroup> &channelGroup, const CVariant ¶meterObject, CVariant &result, bool append /* = false */) +{ + if (channelGroup == NULL) + return; + + CVariant object(CVariant::VariantTypeObject); + object["channelgroupid"] = channelGroup->GroupID(); + object["channeltype"] = channelGroup->IsRadio() ? "radio" : "tv"; + object["label"] = channelGroup->GroupName(); + + if (append) + result.append(object); + else + { + CFileItemList channels; + const auto groupMembers = channelGroup->GetMembers(CPVRChannelGroup::Include::ONLY_VISIBLE); + for (const auto& groupMember : groupMembers) + { + channels.Add(std::make_shared<CFileItem>(groupMember)); + } + + object["channels"] = CVariant(CVariant::VariantTypeArray); + HandleFileItemList("channelid", false, "channels", channels, parameterObject["channels"], object, false); + + result = object; + } +} + +JSONRPC_STATUS CPVROperations::GetTimers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + CFileItemList timerList; + const std::vector<std::shared_ptr<CPVRTimerInfoTag>> tags = timers->GetAll(); + for (const auto& timer : tags) + { + timerList.Add(std::make_shared<CFileItem>(timer)); + } + + HandleFileItemList("timerid", false, "timers", timerList, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + std::shared_ptr<CPVRTimerInfoTag> timer = timers->GetById((int)parameterObject["timerid"].asInteger()); + if (!timer) + return InvalidParams; + + HandleFileItem("timerid", false, "timerdetails", CFileItemPtr(new CFileItem(timer)), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +JSONRPC_STATUS CPVROperations::AddTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + if (CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag)) + return InvalidParams; + + const std::shared_ptr<CPVRTimerInfoTag> newTimer = + CPVRTimerInfoTag::CreateFromEpg(epgTag, parameterObject["timerrule"].asBoolean(false), + parameterObject["reminder"].asBoolean(false)); + if (newTimer) + { + if (CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(newTimer)) + return ACK; + } + return FailedToExecute; +} + + +JSONRPC_STATUS CPVROperations::DeleteTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRTimers> timers = CServiceBroker::GetPVRManager().Timers(); + if (!timers) + return FailedToExecute; + + std::shared_ptr<CPVRTimerInfoTag> timer = timers->GetById(parameterObject["timerid"].asInteger()); + if (!timer) + return InvalidParams; + + if (timers->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK) + return ACK; + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::ToggleTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + parameterObject["broadcastid"].asInteger()); + + if (!epgTag) + return InvalidParams; + + bool timerrule = parameterObject["timerrule"].asBoolean(false); + bool sentOkay = false; + std::shared_ptr<CPVRTimerInfoTag> timer = CServiceBroker::GetPVRManager().Timers()->GetTimerForEpgTag(epgTag); + if (timer) + { + if (timerrule) + timer = CServiceBroker::GetPVRManager().Timers()->GetTimerRule(timer); + + if (timer) + sentOkay = (CServiceBroker::GetPVRManager().Timers()->DeleteTimer(timer, timer->IsRecording(), false) == TimerOperationResult::OK); + } + else + { + timer = CPVRTimerInfoTag::CreateFromEpg(epgTag, timerrule); + if (!timer) + return InvalidParams; + + sentOkay = CServiceBroker::GetPVRManager().Get<PVR::GUI::Timers>().AddTimer(timer); + } + + if (sentOkay) + return ACK; + + return FailedToExecute; +} + +JSONRPC_STATUS CPVROperations::GetRecordings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRRecordings> recordings = CServiceBroker::GetPVRManager().Recordings(); + if (!recordings) + return FailedToExecute; + + CFileItemList recordingsList; + const std::vector<std::shared_ptr<CPVRRecording>> recs = recordings->GetAll(); + for (const auto& recording : recs) + { + recordingsList.Add(std::make_shared<CFileItem>(recording)); + } + + HandleFileItemList("recordingid", true, "recordings", recordingsList, parameterObject, result, true); + + return OK; +} + +JSONRPC_STATUS CPVROperations::GetRecordingDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (!CServiceBroker::GetPVRManager().IsStarted()) + return FailedToExecute; + + std::shared_ptr<CPVRRecordings> recordings = CServiceBroker::GetPVRManager().Recordings(); + if (!recordings) + return FailedToExecute; + + const std::shared_ptr<CPVRRecording> recording = recordings->GetById(static_cast<int>(parameterObject["recordingid"].asInteger())); + if (!recording) + return InvalidParams; + + HandleFileItem("recordingid", true, "recordingdetails", std::make_shared<CFileItem>(recording), parameterObject, parameterObject["properties"], result, false); + + return OK; +} + +std::shared_ptr<CFileItem> CPVROperations::GetRecordingFileItem(int recordingId) +{ + if (CServiceBroker::GetPVRManager().IsStarted()) + { + const std::shared_ptr<PVR::CPVRRecordings> recordings = + CServiceBroker::GetPVRManager().Recordings(); + + if (recordings) + { + const std::shared_ptr<PVR::CPVRRecording> recording = recordings->GetById(recordingId); + if (recording) + return std::make_shared<CFileItem>(recording); + } + } + + return {}; +} diff --git a/xbmc/interfaces/json-rpc/PVROperations.h b/xbmc/interfaces/json-rpc/PVROperations.h new file mode 100644 index 0000000..08aa703 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PVROperations.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "pvr/channels/PVRChannelGroup.h" + +#include <memory> + +class CVariant; + +namespace JSONRPC +{ + class CPVROperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelGroups(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelGroupDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetChannelDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetClients(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetBroadcasts(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetBroadcastDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetBroadcastIsPlayable(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetTimers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTimerDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecordings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecordingDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS AddTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS DeleteTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ToggleTimer(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Record(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static std::shared_ptr<CFileItem> GetRecordingFileItem(int recordingId); + + private: + static JSONRPC_STATUS GetPropertyValue(const std::string &property, CVariant &result); + static void FillChannelGroupDetails(const std::shared_ptr<PVR::CPVRChannelGroup> &channelGroup, const CVariant ¶meterObject, CVariant &result, bool append = false); + }; +} diff --git a/xbmc/interfaces/json-rpc/PlayerOperations.cpp b/xbmc/interfaces/json-rpc/PlayerOperations.cpp new file mode 100644 index 0000000..9912a72 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlayerOperations.cpp @@ -0,0 +1,2158 @@ +/* + * 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 "PlayerOperations.h" + +#include "AudioLibrary.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUIUserMessages.h" +#include "PartyModeManager.h" +#include "PlayListPlayer.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "VideoLibrary.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "application/ApplicationPowerHandling.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/ApplicationMessenger.h" +#include "music/MusicDatabase.h" +#include "pictures/GUIWindowSlideShow.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsPlayback.h" +#include "pvr/recordings/PVRRecordings.h" +#include "settings/AdvancedSettings.h" +#include "settings/DisplaySettings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/MathUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" + +#include <map> +#include <tuple> + +using namespace JSONRPC; +using namespace PVR; + +namespace +{ + +void AppendAudioStreamFlagsAsBooleans(CVariant& list, StreamFlags flags) +{ + list["isdefault"] = ((flags & StreamFlags::FLAG_DEFAULT) != 0); + list["isoriginal"] = ((flags & StreamFlags::FLAG_ORIGINAL) != 0); + list["isimpaired"] = ((flags & StreamFlags::FLAG_VISUAL_IMPAIRED) != 0); +} + +void AppendSubtitleStreamFlagsAsBooleans(CVariant& list, StreamFlags flags) +{ + list["isdefault"] = ((flags & StreamFlags::FLAG_DEFAULT) != 0); + list["isforced"] = ((flags & StreamFlags::FLAG_FORCED) != 0); + list["isimpaired"] = ((flags & StreamFlags::FLAG_HEARING_IMPAIRED) != 0); +} + +} // namespace + +JSONRPC_STATUS CPlayerOperations::GetActivePlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int activePlayers = GetActivePlayers(); + result = CVariant(CVariant::VariantTypeArray); + + std::string strPlayerType = "internal"; + if (activePlayers & External) + strPlayerType = "external"; + else if (activePlayers & Remote) + strPlayerType = "remote"; + + if (activePlayers & Video) + { + CVariant video = CVariant(CVariant::VariantTypeObject); + video["playerid"] = GetPlaylist(Video); + video["type"] = "video"; + video["playertype"] = strPlayerType; + result.append(video); + } + if (activePlayers & Audio) + { + CVariant audio = CVariant(CVariant::VariantTypeObject); + audio["playerid"] = GetPlaylist(Audio); + audio["type"] = "audio"; + audio["playertype"] = strPlayerType; + result.append(audio); + } + if (activePlayers & Picture) + { + CVariant picture = CVariant(CVariant::VariantTypeObject); + picture["playerid"] = GetPlaylist(Picture); + picture["type"] = "picture"; + picture["playertype"] = "internal"; + result.append(picture); + } + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetPlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + std::string media = parameterObject["media"].asString(); + result = CVariant(CVariant::VariantTypeArray); + std::vector<std::string> players; + + if (media == "all") + { + playerCoreFactory.GetPlayers(players); + } + else + { + bool video = false; + if (media == "video") + video = true; + playerCoreFactory.GetPlayers(players, true, video); + } + + for (const auto& playername : players) + { + CVariant player(CVariant::VariantTypeObject); + player["name"] = playername; + + player["playsvideo"] = playerCoreFactory.PlaysVideo(playername); + player["playsaudio"] = playerCoreFactory.PlaysAudio(playername); + player["type"] = playerCoreFactory.GetPlayerType(playername); + + result.push_back(player); + } + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(player, propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CPlayerOperations::GetItem(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + CFileItemPtr fileItem; + + switch (player) + { + case Video: + case Audio: + { + fileItem = std::make_shared<CFileItem>(g_application.CurrentFileItem()); + if (IsPVRChannel()) + break; + + if (player == Video) + { + if (!CVideoLibrary::FillFileItem(fileItem->GetPath(), fileItem, parameterObject)) + { + // Fallback to item details held by GUI but ensure path unchanged + //! @todo remove this once there is no route to playback that updates + // GUI item without also updating app item e.g. start playback of a + // non-library item via JSON + const CVideoInfoTag *currentVideoTag = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentMovieTag(); + if (currentVideoTag != NULL) + { + std::string originalLabel = fileItem->GetLabel(); + std::string originalPath = fileItem->GetPath(); + fileItem->SetFromVideoInfoTag(*currentVideoTag); + if (fileItem->GetLabel().empty()) + fileItem->SetLabel(originalLabel); + fileItem->SetPath(originalPath); // Ensure path unchanged + } + } + + bool additionalInfo = false; + for (CVariant::const_iterator_array itr = parameterObject["properties"].begin_array(); + itr != parameterObject["properties"].end_array(); ++itr) + { + std::string fieldValue = itr->asString(); + if (fieldValue == "cast" || fieldValue == "set" || fieldValue == "setid" || fieldValue == "showlink" || fieldValue == "resume" || + (fieldValue == "streamdetails" && !fileItem->GetVideoInfoTag()->m_streamDetails.HasItems())) + additionalInfo = true; + } + + CVideoDatabase videodatabase; + if ((additionalInfo) && + videodatabase.Open()) + { + switch (fileItem->GetVideoContentType()) + { + case VideoDbContentType::MOVIES: + videodatabase.GetMovieInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::MUSICVIDEOS: + videodatabase.GetMusicVideoInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::EPISODES: + videodatabase.GetEpisodeInfo("", *(fileItem->GetVideoInfoTag()), + fileItem->GetVideoInfoTag()->m_iDbId); + break; + + case VideoDbContentType::TVSHOWS: + case VideoDbContentType::MOVIE_SETS: + default: + break; + } + + videodatabase.Close(); + } + } + else // Audio + { + if (!CAudioLibrary::FillFileItem(fileItem->GetPath(), fileItem, parameterObject)) + { + // Fallback to item details held by GUI but ensure path unchanged + //! @todo remove this once there is no route to playback that updates + // GUI item without also updating app item e.g. start playback of a + // non-library item via JSON + const MUSIC_INFO::CMusicInfoTag* currentMusicTag = + CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag(); + if (currentMusicTag != NULL) + { + std::string originalLabel = fileItem->GetLabel(); + std::string originalPath = fileItem->GetPath(); + fileItem->SetFromMusicInfoTag(*currentMusicTag); + if (fileItem->GetLabel().empty()) + fileItem->SetLabel(originalLabel); + fileItem->SetPath(originalPath); // Ensure path unchanged + } + } + + if (fileItem->IsMusicDb()) + { + CMusicDatabase musicdb; + CFileItemList items; + items.Add(fileItem); + CAudioLibrary::GetAdditionalSongDetails(parameterObject, items, musicdb); + } + } + break; + } + + case Picture: + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + + CFileItemList slides; + slideshow->GetSlideShowContents(slides); + fileItem = slides[slideshow->CurrentSlide() - 1]; + break; + } + + case None: + default: + return FailedToExecute; + } + + HandleFileItem("id", !IsPVRChannel(), "item", fileItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CPlayerOperations::PlayPause(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CGUIWindowSlideShow *slideshow = NULL; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->CanPause()) + return FailedToExecute; + + if (parameterObject["play"].isString()) + CBuiltins::GetInstance().Execute("playercontrol(play)"); + else + { + if (parameterObject["play"].asBoolean()) + { + if (appPlayer->IsPausedPlayback()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + else if (appPlayer->GetPlaySpeed() != 1) + appPlayer->SetPlaySpeed(1); + } + else if (!appPlayer->IsPausedPlayback()) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + } + result["speed"] = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + return OK; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying() && + (parameterObject["play"].isString() || + (parameterObject["play"].isBoolean() && parameterObject["play"].asBoolean() == slideshow->IsPaused()))) + SendSlideshowAction(ACTION_PAUSE); + + if (slideshow && slideshow->IsPlaying() && !slideshow->IsPaused()) + result["speed"] = slideshow->GetDirection(); + else + result["speed"] = 0; + return OK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Stop(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_MEDIA_STOP, static_cast<int>(parameterObject["playerid"].asInteger())); + return ACK; + + case Picture: + SendSlideshowAction(ACTION_STOP); + return ACK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::GetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result["offset"] = appPlayer->GetVideoSettings().m_AudioDelay; + return OK; +} + +JSONRPC_STATUS CPlayerOperations::SetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + float videoAudioDelayRange = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoAudioDelayRange; + + if (parameterObject["offset"].isDouble()) + { + float offset = static_cast<float>(parameterObject["offset"].asDouble()); + offset = MathUtils::RoundF(offset, AUDIO_DELAY_STEP); + if (offset > videoAudioDelayRange) + offset = videoAudioDelayRange; + else if (offset < -videoAudioDelayRange) + offset = -videoAudioDelayRange; + + appPlayer->SetAVDelay(offset); + } + else if (parameterObject["offset"].isString()) + { + CVideoSettings vs = appPlayer->GetVideoSettings(); + if (parameterObject["offset"].asString().compare("increment") == 0) + { + vs.m_AudioDelay += AUDIO_DELAY_STEP; + if (vs.m_AudioDelay > videoAudioDelayRange) + vs.m_AudioDelay = videoAudioDelayRange; + appPlayer->SetAVDelay(vs.m_AudioDelay); + } + else + { + vs.m_AudioDelay -= AUDIO_DELAY_STEP; + if (vs.m_AudioDelay < -videoAudioDelayRange) + vs.m_AudioDelay = -videoAudioDelayRange; + appPlayer->SetAVDelay(vs.m_AudioDelay); + } + } + else + return InvalidParams; + + result["offset"] = appPlayer->GetVideoSettings().m_AudioDelay; + return OK; + } + case Audio: + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::SetSpeed(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (parameterObject["speed"].isInteger()) + { + int speed = (int)parameterObject["speed"].asInteger(); + if (speed != 0) + { + // If the player is paused we first need to unpause + if (appPlayer->IsPausedPlayback()) + appPlayer->Pause(); + appPlayer->SetPlaySpeed(speed); + } + else + appPlayer->Pause(); + } + else if (parameterObject["speed"].isString()) + { + if (parameterObject["speed"].asString().compare("increment") == 0) + CBuiltins::GetInstance().Execute("playercontrol(forward)"); + else + CBuiltins::GetInstance().Execute("playercontrol(rewind)"); + } + else + return InvalidParams; + + result["speed"] = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + return OK; + } + + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Seek(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (!appPlayer->CanSeek()) + return FailedToExecute; + + const CVariant& value = parameterObject["value"]; + if (value.isMember("percentage")) + g_application.SeekPercentage(value["percentage"].asFloat()); + else if (value.isMember("step")) + { + std::string step = value["step"].asString(); + if (step == "smallforward") + CBuiltins::GetInstance().Execute("playercontrol(smallskipforward)"); + else if (step == "smallbackward") + CBuiltins::GetInstance().Execute("playercontrol(smallskipbackward)"); + else if (step == "bigforward") + CBuiltins::GetInstance().Execute("playercontrol(bigskipforward)"); + else if (step == "bigbackward") + CBuiltins::GetInstance().Execute("playercontrol(bigskipbackward)"); + else + return InvalidParams; + } + else if (value.isMember("seconds")) + appPlayer->GetSeekHandler().SeekSeconds(static_cast<int>(value["seconds"].asInteger())); + else if (value.isMember("time")) + g_application.SeekTime(ParseTimeInSeconds(value["time"])); + else + return InvalidParams; + + GetPropertyValue(player, "percentage", result["percentage"]); + GetPropertyValue(player, "time", result["time"]); + GetPropertyValue(player, "totaltime", result["totaltime"]); + return OK; + } + + case Picture: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Move(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string direction = parameterObject["direction"].asString(); + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (direction == "left") + SendSlideshowAction(ACTION_MOVE_LEFT); + else if (direction == "right") + SendSlideshowAction(ACTION_MOVE_RIGHT); + else if (direction == "up") + SendSlideshowAction(ACTION_MOVE_UP); + else if (direction == "down") + SendSlideshowAction(ACTION_MOVE_DOWN); + else + return InvalidParams; + + return ACK; + + case Video: + case Audio: + if (direction == "left" || direction == "up") + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PREV_ITEM))); + else if (direction == "right" || direction == "down") + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_NEXT_ITEM))); + else + return InvalidParams; + + return ACK; + + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Zoom(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant zoom = parameterObject["zoom"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (zoom.isInteger()) + SendSlideshowAction(ACTION_ZOOM_LEVEL_NORMAL + ((int)zoom.asInteger() - 1)); + else if (zoom.isString()) + { + std::string strZoom = zoom.asString(); + if (strZoom == "in") + SendSlideshowAction(ACTION_ZOOM_IN); + else if (strZoom == "out") + SendSlideshowAction(ACTION_ZOOM_OUT); + else + return InvalidParams; + } + else + return InvalidParams; + + return ACK; + + case Video: + case Audio: + case None: + default: + return FailedToExecute; + } +} + +// Matching pairs of values from JSON type "Player.ViewMode" and C++ enum ViewMode +// Additions to enum ViewMode need to be added here and in the JSON type +std::map<std::string, ViewMode> viewModes = +{ + {"normal", ViewModeNormal}, + {"zoom", ViewModeZoom}, + {"stretch4x3", ViewModeStretch4x3}, + {"widezoom", ViewModeWideZoom, }, + {"stretch16x9", ViewModeStretch16x9}, + {"original", ViewModeOriginal}, + {"stretch16x9nonlin", ViewModeStretch16x9Nonlin}, + {"zoom120width", ViewModeZoom120Width}, + {"zoom110width", ViewModeZoom110Width} +}; + +std::string GetStringFromViewMode(ViewMode viewMode) +{ + std::string result = "custom"; + + auto it = find_if(viewModes.begin(), viewModes.end(), [viewMode](const std::pair<std::string, ViewMode> & p) + { + return p.second == viewMode; + }); + + if (it != viewModes.end()) + { + std::pair<std::string, ViewMode> value = *it; + result = value.first; + } + + return result; +} + +void GetNewValueForViewModeParameter(const CVariant ¶meter, float stepSize, float minValue, float maxValue, float &result) +{ + if (parameter.isDouble()) + { + result = parameter.asDouble(); + } + else if (parameter.isString()) + { + if (parameter == "decrease") + { + stepSize *= -1; + } + + result += stepSize; + } + + result = std::max(minValue, std::min(result, maxValue)); +} + +JSONRPC_STATUS CPlayerOperations::SetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + JSONRPC_STATUS jsonStatus = InvalidParams; + // init with current values from settings + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + CVideoSettings vs = appPlayer->GetVideoSettings(); + ViewMode mode = ViewModeNormal; + + CVariant viewMode = parameterObject["viewmode"]; + if (viewMode.isString()) + { + std::string modestr = viewMode.asString(); + if (viewModes.find(modestr) != viewModes.end()) + { + mode = viewModes[modestr]; + jsonStatus = ACK; + } + } + else if (viewMode.isObject()) + { + mode = ViewModeCustom; + CVariant zoom = viewMode["zoom"]; + CVariant pixelRatio = viewMode["pixelratio"]; + CVariant verticalShift = viewMode["verticalshift"]; + CVariant stretch = viewMode["nonlinearstretch"]; + + if (!zoom.isNull()) + { + GetNewValueForViewModeParameter(zoom, 0.01f, 0.5f, 2.f, vs.m_CustomZoomAmount); + jsonStatus = ACK; + } + + if (!pixelRatio.isNull()) + { + GetNewValueForViewModeParameter(pixelRatio, 0.01f, 0.5f, 2.f, vs.m_CustomPixelRatio); + jsonStatus = ACK; + } + + if (!verticalShift.isNull()) + { + GetNewValueForViewModeParameter(verticalShift, -0.01f, -2.f, 2.f, vs.m_CustomVerticalShift); + jsonStatus = ACK; + } + + if (stretch.isBoolean()) + { + vs.m_CustomNonLinStretch = stretch.asBoolean(); + jsonStatus = ACK; + } + } + + if (jsonStatus == ACK) + { + appPlayer->SetRenderViewMode(static_cast<int>(mode), vs.m_CustomZoomAmount, + vs.m_CustomPixelRatio, vs.m_CustomVerticalShift, + vs.m_CustomNonLinStretch); + } + + return jsonStatus; +} + +JSONRPC_STATUS CPlayerOperations::GetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int mode = appPlayer->GetVideoSettings().m_ViewMode; + + result["viewmode"] = GetStringFromViewMode(static_cast<ViewMode>(mode)); + + result["zoom"] = CDisplaySettings::GetInstance().GetZoomAmount(); + result["pixelratio"] = CDisplaySettings::GetInstance().GetPixelRatio(); + result["verticalshift"] = CDisplaySettings::GetInstance().GetVerticalShift(); + result["nonlinearstretch"] = CDisplaySettings::GetInstance().IsNonLinearStretched(); + return OK; +} + +JSONRPC_STATUS CPlayerOperations::Rotate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Picture: + if (parameterObject["value"].asString().compare("clockwise") == 0) + SendSlideshowAction(ACTION_ROTATE_PICTURE_CW); + else + SendSlideshowAction(ACTION_ROTATE_PICTURE_CCW); + return ACK; + + case Video: + case Audio: + case None: + default: + return FailedToExecute; + } +} + +JSONRPC_STATUS CPlayerOperations::Open(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant options = parameterObject["options"]; + CVariant optionShuffled = options["shuffled"]; + CVariant optionRepeat = options["repeat"]; + CVariant optionResume = options["resume"]; + CVariant optionPlayer = options["playername"]; + + if (parameterObject["item"].isMember("playlistid")) + { + PLAYLIST::Id playlistid = parameterObject["item"]["playlistid"].asInteger(); + + if (playlistid == PLAYLIST::TYPE_MUSIC || playlistid == PLAYLIST::TYPE_VIDEO) + { + // Apply the "shuffled" option if available + if (optionShuffled.isBoolean()) + CServiceBroker::GetPlaylistPlayer().SetShuffle(playlistid, optionShuffled.asBoolean(), false); + // Apply the "repeat" option if available + if (!optionRepeat.isNull()) + CServiceBroker::GetPlaylistPlayer().SetRepeat(playlistid, ParseRepeatState(optionRepeat), + false); + } + + int playlistStartPosition = (int)parameterObject["item"]["position"].asInteger(); + + switch (playlistid) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, playlistid, + playlistStartPosition); + break; + + case PLAYLIST::TYPE_PICTURE: + { + std::string firstPicturePath; + if (playlistStartPosition > 0) + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow != NULL) + { + CFileItemList list; + slideshow->GetSlideShowContents(list); + if (playlistStartPosition < list.Size()) + firstPicturePath = list.Get(playlistStartPosition)->GetPath(); + } + } + + return StartSlideshow("", false, optionShuffled.isBoolean() && optionShuffled.asBoolean(), firstPicturePath); + break; + } + } + + return ACK; + } + else if (parameterObject["item"].isMember("path")) + { + bool random = (optionShuffled.isBoolean() && optionShuffled.asBoolean()) || + (!optionShuffled.isBoolean() && parameterObject["item"]["random"].asBoolean()); + return StartSlideshow(parameterObject["item"]["path"].asString(), parameterObject["item"]["recursive"].asBoolean(), random); + } + else if (parameterObject["item"].isObject() && parameterObject["item"].isMember("partymode")) + { + if (g_partyModeManager.IsEnabled()) + g_partyModeManager.Disable(); + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "playercontrol(partymode(" + parameterObject["item"]["partymode"].asString() + "))"); + return ACK; + } + else if (parameterObject["item"].isMember("broadcastid")) + { + const std::shared_ptr<CPVREpgInfoTag> epgTag = + CServiceBroker::GetPVRManager().EpgContainer().GetTagByDatabaseId( + static_cast<unsigned int>(parameterObject["item"]["broadcastid"].asInteger())); + + if (!epgTag || !epgTag->IsPlayable()) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayEpgTag(CFileItem(epgTag))) + return FailedToExecute; + + return ACK; + } + else if (parameterObject["item"].isMember("channelid")) + { + const std::shared_ptr<CPVRChannelGroupsContainer> channelGroupContainer = CServiceBroker::GetPVRManager().ChannelGroups(); + if (!channelGroupContainer) + return FailedToExecute; + + const std::shared_ptr<CPVRChannel> channel = channelGroupContainer->GetChannelById(static_cast<int>(parameterObject["item"]["channelid"].asInteger())); + if (!channel) + return InvalidParams; + + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(channel); + if (!groupMember) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia( + CFileItem(groupMember))) + return FailedToExecute; + + return ACK; + } + else if (parameterObject["item"].isMember("recordingid")) + { + const std::shared_ptr<CPVRRecordings> recordingsContainer = CServiceBroker::GetPVRManager().Recordings(); + if (!recordingsContainer) + return FailedToExecute; + + const std::shared_ptr<CPVRRecording> recording = recordingsContainer->GetById(static_cast<int>(parameterObject["item"]["recordingid"].asInteger())); + if (!recording) + return InvalidParams; + + if (!CServiceBroker::GetPVRManager().Get<PVR::GUI::Playback>().PlayMedia(CFileItem(recording))) + return FailedToExecute; + + return ACK; + } + else + { + CFileItemList list; + if (FillFileItemList(parameterObject["item"], list) && list.Size() > 0) + { + bool slideshow = true; + for (int index = 0; index < list.Size(); index++) + { + if (!list[index]->IsPicture()) + { + slideshow = false; + break; + } + } + + if (slideshow) + { + CGUIWindowSlideShow *slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + + SendSlideshowAction(ACTION_STOP); + slideshow->Reset(); + for (int index = 0; index < list.Size(); index++) + slideshow->Add(list[index].get()); + + return StartSlideshow("", false, optionShuffled.isBoolean() && optionShuffled.asBoolean()); + } + else + { + std::string playername; + // Handle the "playerid" option + if (!optionPlayer.isNull()) + { + if (optionPlayer.isString()) + { + playername = optionPlayer.asString(); + + if (playername != "default") + { + const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + + // check if the there's actually a player with the given name + if (playerCoreFactory.GetPlayerType(playername).empty()) + return InvalidParams; + + // check if the player can handle at least the first item in the list + std::vector<std::string> possiblePlayers; + playerCoreFactory.GetPlayers(*list.Get(0).get(), possiblePlayers); + + bool match = false; + for (const auto& entry : possiblePlayers) + { + if (StringUtils::EqualsNoCase(entry, playername)) + { + match = true; + break; + } + } + if (!match) + return InvalidParams; + } + } + else + return InvalidParams; + } + + // Handle "shuffled" option + if (optionShuffled.isBoolean()) + list.SetProperty("shuffled", optionShuffled); + // Handle "repeat" option + if (!optionRepeat.isNull()) + list.SetProperty("repeat", static_cast<int>(ParseRepeatState(optionRepeat))); + // Handle "resume" option + if (list.Size() == 1) + { + if (optionResume.isBoolean() && optionResume.asBoolean()) + list[0]->SetStartOffset(STARTOFFSET_RESUME); + else if (optionResume.isDouble()) + list[0]->SetProperty("StartPercent", optionResume); + else if (optionResume.isObject()) + list[0]->SetStartOffset( + CUtil::ConvertSecsToMilliSecs(ParseTimeInSeconds(optionResume))); + } + + auto l = new CFileItemList(); //don't delete + l->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1, static_cast<void*>(l), + playername); + } + + return ACK; + } + else + return InvalidParams; + } + + return InvalidParams; +} + +JSONRPC_STATUS CPlayerOperations::GoTo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant to = parameterObject["to"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + if (to.isString()) + { + std::string strTo = to.asString(); + int actionID; + if (strTo == "previous") + actionID = ACTION_PREV_ITEM; + else if (strTo == "next") + actionID = ACTION_NEXT_ITEM; + else + return InvalidParams; + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>(new CAction(actionID))); + } + else if (to.isInteger()) + { + if (IsPVRChannel()) + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast<void*>( + new CAction(ACTION_CHANNEL_SWITCH, static_cast<float>(to.asInteger())))); + else + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY, + static_cast<int>(to.asInteger())); + } + else + return InvalidParams; + break; + + case Picture: + if (to.isString()) + { + std::string strTo = to.asString(); + int actionID; + if (strTo == "previous") + actionID = ACTION_PREV_PICTURE; + else if (strTo == "next") + actionID = ACTION_NEXT_PICTURE; + else + return InvalidParams; + + SendSlideshowAction(actionID); + } + else + return FailedToExecute; + break; + + case None: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetShuffle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CGUIWindowSlideShow *slideshow = NULL; + CVariant shuffle = parameterObject["shuffle"]; + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + PLAYLIST::Id playlistid = GetPlaylist(GetPlayer(parameterObject["playerid"])); + if (CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistid)) + { + if ((shuffle.isBoolean() && !shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_SHUFFLE, playlistid, 0); + } + } + else + { + if ((shuffle.isBoolean() && shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_SHUFFLE, playlistid, 1); + } + } + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow == NULL) + return FailedToExecute; + if (slideshow->IsShuffled()) + { + if ((shuffle.isBoolean() && !shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + return FailedToExecute; + } + else + { + if ((shuffle.isBoolean() && shuffle.asBoolean()) || + (shuffle.isString() && shuffle.asString() == "toggle")) + slideshow->Shuffle(); + } + break; + + default: + return FailedToExecute; + } + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetRepeat(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + PLAYLIST::RepeatState repeat = PLAYLIST::RepeatState::NONE; + PLAYLIST::Id playlistid = GetPlaylist(GetPlayer(parameterObject["playerid"])); + if (parameterObject["repeat"].asString() == "cycle") + { + PLAYLIST::RepeatState repeatPrev = + CServiceBroker::GetPlaylistPlayer().GetRepeat(playlistid); + if (repeatPrev == PLAYLIST::RepeatState::NONE) + repeat = PLAYLIST::RepeatState::ALL; + else if (repeatPrev == PLAYLIST::RepeatState::ALL) + repeat = PLAYLIST::RepeatState::ONE; + else + repeat = PLAYLIST::RepeatState::NONE; + } + else + repeat = ParseRepeatState(parameterObject["repeat"]); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_REPEAT, playlistid, + static_cast<int>(repeat)); + break; + } + + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetPartymode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PlayerType player = GetPlayer(parameterObject["playerid"]); + switch (player) + { + case Video: + case Audio: + { + if (IsPVRChannel()) + return FailedToExecute; + + bool change = false; + PartyModeContext context = PARTYMODECONTEXT_UNKNOWN; + std::string strContext; + if (player == Video) + { + context = PARTYMODECONTEXT_VIDEO; + strContext = "video"; + } + else if (player == Audio) + { + context = PARTYMODECONTEXT_MUSIC; + strContext = "music"; + } + + bool toggle = parameterObject["partymode"].isString(); + if (g_partyModeManager.IsEnabled()) + { + if (g_partyModeManager.GetType() != context) + return InvalidParams; + + if (toggle || parameterObject["partymode"].asBoolean() == false) + change = true; + } + else + { + if (toggle || parameterObject["partymode"].asBoolean() == true) + change = true; + } + + if (change) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "playercontrol(partymode(" + strContext + "))"); + break; + } + + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetAudioStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + int index = -1; + if (parameterObject["stream"].isString()) + { + std::string action = parameterObject["stream"].asString(); + if (action.compare("previous") == 0) + { + index = appPlayer->GetAudioStream() - 1; + if (index < 0) + index = appPlayer->GetAudioStreamCount() - 1; + } + else if (action.compare("next") == 0) + { + index = appPlayer->GetAudioStream() + 1; + if (index >= appPlayer->GetAudioStreamCount()) + index = 0; + } + else + return InvalidParams; + } + else if (parameterObject["stream"].isInteger()) + index = (int)parameterObject["stream"].asInteger(); + + if (index < 0 || appPlayer->GetAudioStreamCount() <= index) + return InvalidParams; + + appPlayer->SetAudioStream(index); + } + else + return FailedToExecute; + break; + } + + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::AddSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (GetPlayer(parameterObject["playerid"]) != Video) + return FailedToExecute; + + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (!appPlayer->HasPlayer()) + return FailedToExecute; + + if (!parameterObject["subtitle"].isString()) + return FailedToExecute; + + std::string sub = parameterObject["subtitle"].asString(); + appPlayer->AddSubtitle(sub); + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + int index = -1; + if (parameterObject["subtitle"].isString()) + { + std::string action = parameterObject["subtitle"].asString(); + if (action.compare("previous") == 0) + { + index = appPlayer->GetSubtitle() - 1; + if (index < 0) + index = appPlayer->GetSubtitleCount() - 1; + } + else if (action.compare("next") == 0) + { + index = appPlayer->GetSubtitle() + 1; + if (index >= appPlayer->GetSubtitleCount()) + index = 0; + } + else if (action.compare("off") == 0) + { + appPlayer->SetSubtitleVisible(false); + return ACK; + } + else if (action.compare("on") == 0) + { + appPlayer->SetSubtitleVisible(true); + return ACK; + } + else + return InvalidParams; + } + else if (parameterObject["subtitle"].isInteger()) + index = (int)parameterObject["subtitle"].asInteger(); + + if (index < 0 || appPlayer->GetSubtitleCount() <= index) + return InvalidParams; + + appPlayer->SetSubtitle(index); + + // Check if we need to enable subtitles to be displayed + if (parameterObject["enable"].asBoolean() && !appPlayer->GetSubtitleVisible()) + appPlayer->SetSubtitleVisible(true); + } + else + return FailedToExecute; + break; + } + + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +JSONRPC_STATUS CPlayerOperations::SetVideoStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + switch (GetPlayer(parameterObject["playerid"])) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int streamCount = appPlayer->GetVideoStreamCount(); + if (streamCount > 0) + { + int index = appPlayer->GetVideoStream(); + if (parameterObject["stream"].isString()) + { + std::string action = parameterObject["stream"].asString(); + if (action.compare("previous") == 0) + { + index--; + if (index < 0) + index = streamCount - 1; + } + else if (action.compare("next") == 0) + { + index++; + if (index >= streamCount) + index = 0; + } + else + return InvalidParams; + } + else if (parameterObject["stream"].isInteger()) + index = (int)parameterObject["stream"].asInteger(); + + if (index < 0 || streamCount <= index) + return InvalidParams; + + appPlayer->SetVideoStream(index); + } + else + return FailedToExecute; + break; + } + case Audio: + case Picture: + default: + return FailedToExecute; + } + + return ACK; +} + +int CPlayerOperations::GetActivePlayers() +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + int activePlayers = 0; + if (appPlayer->IsPlayingVideo() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording()) + activePlayers |= Video; + if (appPlayer->IsPlayingAudio() || + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio()) + activePlayers |= Audio; + if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_SLIDESHOW)) + activePlayers |= Picture; + if (appPlayer->IsExternalPlaying()) + activePlayers |= External; + if (appPlayer->IsRemotePlaying()) + activePlayers |= Remote; + + return activePlayers; +} + +PlayerType CPlayerOperations::GetPlayer(const CVariant &player) +{ + PLAYLIST::Id playerPlaylistId = player.asInteger(); + PlayerType playerID; + + switch (playerPlaylistId) + { + case PLAYLIST::TYPE_VIDEO: + playerID = Video; + break; + + case PLAYLIST::TYPE_MUSIC: + playerID = Audio; + break; + + case PLAYLIST::TYPE_PICTURE: + playerID = Picture; + break; + + default: + playerID = None; + break; + } + + if (GetPlaylist(playerID) == playerPlaylistId) + return playerID; + else + return None; +} + +PLAYLIST::Id CPlayerOperations::GetPlaylist(PlayerType player) +{ + PLAYLIST::Id playlistId = CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(); + if (playlistId == PLAYLIST::TYPE_NONE) // No active playlist, try guessing + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + playlistId = appPlayer->GetPreferredPlaylist(); + } + + switch (player) + { + case Video: + return playlistId == PLAYLIST::TYPE_NONE ? PLAYLIST::TYPE_VIDEO : playlistId; + + case Audio: + return playlistId == PLAYLIST::TYPE_NONE ? PLAYLIST::TYPE_MUSIC : playlistId; + + case Picture: + return PLAYLIST::TYPE_PICTURE; + + default: + return playlistId; + } +} + +JSONRPC_STATUS CPlayerOperations::StartSlideshow(const std::string& path, bool recursive, bool random, const std::string &firstPicturePath /* = "" */) +{ + int flags = 0; + if (recursive) + flags |= 1; + if (random) + flags |= 2; + else + flags |= 4; + + std::vector<std::string> params; + params.push_back(path); + if (!firstPicturePath.empty()) + params.push_back(firstPicturePath); + + // Reset screensaver when started from JSON only to avoid potential conflict with slideshow screensavers + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + appPower->ResetScreenSaver(); + appPower->WakeUpScreenSaverAndDPMS(); + CGUIMessage msg(GUI_MSG_START_SLIDESHOW, 0, 0, flags); + msg.SetStringParams(params); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, WINDOW_SLIDESHOW); + + return ACK; +} + +void CPlayerOperations::SendSlideshowAction(int actionID) +{ + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1, + static_cast<void*>(new CAction(actionID))); +} + +JSONRPC_STATUS CPlayerOperations::GetPropertyValue(PlayerType player, const std::string &property, CVariant &result) +{ + if (player == None) + return FailedToExecute; + + PLAYLIST::Id playlistId = GetPlaylist(player); + + if (property == "type") + { + switch (player) + { + case Video: + result = "video"; + break; + + case Audio: + result = "audio"; + break; + + case Picture: + result = "picture"; + break; + + default: + return FailedToExecute; + } + } + else if (property == "partymode") + { + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = false; + break; + } + + result = g_partyModeManager.IsEnabled(); + break; + + case Picture: + result = false; + break; + + default: + return FailedToExecute; + } + } + else if (property == "speed") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->IsPausedPlayback() ? 0 : (int)lrint(appPlayer->GetPlaySpeed()); + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying() && !slideshow->IsPaused()) + result = slideshow->GetDirection(); + else + result = 0; + break; + + default: + return FailedToExecute; + } + } + else if (property == "time") + { + switch (player) + { + case Video: + case Audio: + { + int ms = 0; + if (!IsPVRChannel()) + ms = (int)(g_application.GetTime() * 1000.0); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + ms = epg->Progress() * 1000; + } + + MillisecondsToTimeObject(ms, result); + break; + } + + case Picture: + MillisecondsToTimeObject(0, result); + break; + + default: + return FailedToExecute; + } + } + else if (property == "percentage") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + { + if (!IsPVRChannel()) + result = g_application.GetPercentage(); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + result = epg->ProgressPercentage(); + else + result = 0; + } + break; + } + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->NumSlides() > 0) + result = (double)slideshow->CurrentSlide() / slideshow->NumSlides(); + else + result = 0.0; + break; + + default: + return FailedToExecute; + } + } + else if (property == "cachepercentage") + { + switch (player) + { + case Video: + case Audio: + { + result = g_application.GetCachePercentage(); + break; + } + + case Picture: + { + result = 0.0; + break; + } + + default: + return FailedToExecute; + } + } + else if (property == "totaltime") + { + switch (player) + { + case Video: + case Audio: + { + int ms = 0; + if (!IsPVRChannel()) + ms = (int)(g_application.GetTotalTime() * 1000.0); + else + { + std::shared_ptr<CPVREpgInfoTag> epg(GetCurrentEpg()); + if (epg) + ms = epg->GetDuration() * 1000; + } + + MillisecondsToTimeObject(ms, result); + break; + } + + case Picture: + MillisecondsToTimeObject(0, result); + break; + + default: + return FailedToExecute; + } + } + else if (property == "playlistid") + { + result = playlistId; + } + else if (property == "position") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: /* Return the position of current item if there is an active playlist */ + if (!IsPVRChannel() && + CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == playlistId) + { + result = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + } + else + result = -1; + break; + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying()) + result = slideshow->CurrentSlide() - 1; + else + result = -1; + break; + + default: + result = -1; + break; + } + } + else if (property == "repeat") + { + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = "off"; + break; + } + + switch (CServiceBroker::GetPlaylistPlayer().GetRepeat(playlistId)) + { + case PLAYLIST::RepeatState::ONE: + result = "one"; + break; + case PLAYLIST::RepeatState::ALL: + result = "all"; + break; + default: + result = "off"; + break; + } + break; + + case Picture: + default: + result = "off"; + break; + } + } + else if (property == "shuffled") + { + CGUIWindowSlideShow *slideshow = NULL; + switch (player) + { + case Video: + case Audio: + if (IsPVRChannel()) + { + result = false; + break; + } + + result = CServiceBroker::GetPlaylistPlayer().IsShuffled(playlistId); + break; + + case Picture: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow && slideshow->IsPlaying()) + result = slideshow->IsShuffled(); + else + result = -1; + break; + + default: + result = -1; + break; + } + } + else if (property == "canseek") + { + switch (player) + { + case Video: + case Audio: + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->CanSeek(); + break; + } + + case Picture: + default: + result = false; + break; + } + } + else if (property == "canchangespeed") + { + switch (player) + { + case Video: + case Audio: + result = !IsPVRChannel(); + break; + + case Picture: + default: + result = false; + break; + } + } + else if (property == "canmove") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canzoom") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canrotate") + { + switch (player) + { + case Picture: + result = true; + break; + + case Video: + case Audio: + default: + result = false; + break; + } + } + else if (property == "canshuffle") + { + switch (player) + { + case Video: + case Audio: + case Picture: + result = !IsPVRChannel(); + break; + + default: + result = false; + break; + } + } + else if (property == "canrepeat") + { + switch (player) + { + case Video: + case Audio: + result = !IsPVRChannel(); + break; + + case Picture: + default: + result = false; + break; + } + } + else if (property == "currentaudiostream") + { + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + result = CVariant(CVariant::VariantTypeObject); + int index = appPlayer->GetAudioStream(); + if (index >= 0) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + result["codec"] = info.codecName; + result["bitrate"] = info.bitrate; + result["channels"] = info.channels; + result["samplerate"] = info.samplerate; + AppendAudioStreamFlagsAsBooleans(result, info.flags); + } + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "audiostreams") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + case Audio: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + for (int index = 0; index < appPlayer->GetAudioStreamCount(); index++) + { + AudioStreamInfo info; + appPlayer->GetAudioStreamInfo(index, info); + + CVariant audioStream(CVariant::VariantTypeObject); + audioStream["index"] = index; + audioStream["name"] = info.name; + audioStream["language"] = info.language; + audioStream["codec"] = info.codecName; + audioStream["bitrate"] = info.bitrate; + audioStream["channels"] = info.channels; + audioStream["samplerate"] = info.samplerate; + AppendAudioStreamFlagsAsBooleans(audioStream, info.flags); + + result.append(audioStream); + } + } + break; + } + + case Picture: + default: + break; + } + } + else if (property == "currentvideostream") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int index = appPlayer->GetVideoStream(); + if (index >= 0) + { + result = CVariant(CVariant::VariantTypeObject); + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + result["codec"] = info.codecName; + result["width"] = info.width; + result["height"] = info.height; + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + case Audio: + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "videostreams") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + int streamCount = appPlayer->GetVideoStreamCount(); + if (streamCount >= 0) + { + for (int index = 0; index < streamCount; ++index) + { + VideoStreamInfo info; + appPlayer->GetVideoStreamInfo(index, info); + + CVariant videoStream(CVariant::VariantTypeObject); + videoStream["index"] = index; + videoStream["name"] = info.name; + videoStream["language"] = info.language; + videoStream["codec"] = info.codecName; + videoStream["width"] = info.width; + videoStream["height"] = info.height; + + result.append(videoStream); + } + } + break; + } + case Audio: + case Picture: + default: + break; + } + } + else if (property == "subtitleenabled") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + result = appPlayer->GetSubtitleVisible(); + break; + } + + case Audio: + case Picture: + default: + result = false; + break; + } + } + else if (property == "currentsubtitle") + { + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + result = CVariant(CVariant::VariantTypeObject); + int index = appPlayer->GetSubtitle(); + if (index >= 0) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(index, info); + + result["index"] = index; + result["name"] = info.name; + result["language"] = info.language; + AppendSubtitleStreamFlagsAsBooleans(result, info.flags); + } + } + else + result = CVariant(CVariant::VariantTypeNull); + break; + } + + case Audio: + case Picture: + default: + result = CVariant(CVariant::VariantTypeNull); + break; + } + } + else if (property == "subtitles") + { + result = CVariant(CVariant::VariantTypeArray); + switch (player) + { + case Video: + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + if (appPlayer->HasPlayer()) + { + for (int index = 0; index < appPlayer->GetSubtitleCount(); index++) + { + SubtitleStreamInfo info; + appPlayer->GetSubtitleStreamInfo(index, info); + + CVariant subtitle(CVariant::VariantTypeObject); + subtitle["index"] = index; + subtitle["name"] = info.name; + subtitle["language"] = info.language; + AppendSubtitleStreamFlagsAsBooleans(subtitle, info.flags); + + result.append(subtitle); + } + } + break; + } + + case Audio: + case Picture: + default: + break; + } + } + else if (property == "live") + result = IsPVRChannel(); + else + return InvalidParams; + + return OK; +} + +PLAYLIST::RepeatState CPlayerOperations::ParseRepeatState(const CVariant& repeat) +{ + PLAYLIST::RepeatState state = PLAYLIST::RepeatState::NONE; + std::string strState = repeat.asString(); + + if (strState.compare("one") == 0) + state = PLAYLIST::RepeatState::ONE; + else if (strState.compare("all") == 0) + state = PLAYLIST::RepeatState::ALL; + + return state; +} + +double CPlayerOperations::ParseTimeInSeconds(const CVariant &time) +{ + double seconds = 0.0; + if (time.isMember("hours")) + seconds += time["hours"].asInteger() * 60 * 60; + if (time.isMember("minutes")) + seconds += time["minutes"].asInteger() * 60; + if (time.isMember("seconds")) + seconds += time["seconds"].asInteger(); + if (time.isMember("milliseconds")) + seconds += time["milliseconds"].asDouble() / 1000.0; + + return seconds; +} + +bool CPlayerOperations::IsPVRChannel() +{ + const std::shared_ptr<CPVRPlaybackState> state = CServiceBroker::GetPVRManager().PlaybackState(); + return state->IsPlayingTV() || state->IsPlayingRadio(); +} + +std::shared_ptr<CPVREpgInfoTag> CPlayerOperations::GetCurrentEpg() +{ + const std::shared_ptr<CPVRPlaybackState> state = CServiceBroker::GetPVRManager().PlaybackState(); + if (!state->IsPlayingTV() && !state->IsPlayingRadio()) + return {}; + + const std::shared_ptr<CPVRChannel> currentChannel = state->GetPlayingChannel(); + if (!currentChannel) + return {}; + + return currentChannel->GetEPGNow(); +} diff --git a/xbmc/interfaces/json-rpc/PlayerOperations.h b/xbmc/interfaces/json-rpc/PlayerOperations.h new file mode 100644 index 0000000..b681bec --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlayerOperations.h @@ -0,0 +1,96 @@ +/* + * 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 "FileItemHandler.h" +#include "JSONRPC.h" + +#include <string> + +class CVariant; + +namespace PVR +{ +class CPVRChannelGroup; +class CPVREpgInfoTag; +} + +namespace PLAYLIST +{ +using Id = int; +enum class RepeatState; +} // namespace PLAYLIST + +namespace JSONRPC +{ + enum PlayerType + { + None = 0, + Video = 0x1, + Audio = 0x2, + Picture = 0x4, + External = 0x8, + Remote = 0x10 + }; + + static const int PlayerImplicit = (Video | Audio | Picture); + + class CPlayerOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetActivePlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetPlayers(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetItem(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS PlayPause(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Stop(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetAudioDelay(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetSpeed(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Seek(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Move(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Zoom(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetViewMode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Rotate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Open(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GoTo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetShuffle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetRepeat(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetPartymode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS SetAudioStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS AddSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSubtitle(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetVideoStream(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static int GetActivePlayers(); + static PlayerType GetPlayer(const CVariant &player); + static PLAYLIST::Id GetPlaylist(PlayerType player); + static JSONRPC_STATUS StartSlideshow(const std::string& path, bool recursive, bool random, const std::string &firstPicturePath = ""); + static void SendSlideshowAction(int actionID); + static JSONRPC_STATUS GetPropertyValue(PlayerType player, const std::string &property, CVariant &result); + + static PLAYLIST::RepeatState ParseRepeatState(const CVariant& repeat); + static double ParseTimeInSeconds(const CVariant &time); + static bool IsPVRChannel(); + static std::shared_ptr<PVR::CPVREpgInfoTag> GetCurrentEpg(); + }; +} diff --git a/xbmc/interfaces/json-rpc/PlaylistOperations.cpp b/xbmc/interfaces/json-rpc/PlaylistOperations.cpp new file mode 100644 index 0000000..b98540a --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlaylistOperations.cpp @@ -0,0 +1,323 @@ +/* + * 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 "PlaylistOperations.h" + +#include "FileItem.h" +#include "GUIUserMessages.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "input/Key.h" +#include "messaging/ApplicationMessenger.h" +#include "pictures/GUIWindowSlideShow.h" +#include "pictures/PictureInfoTag.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CPlaylistOperations::GetPlaylists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + result = CVariant(CVariant::VariantTypeArray); + CVariant playlist = CVariant(CVariant::VariantTypeObject); + + playlist["playlistid"] = PLAYLIST::TYPE_MUSIC; + playlist["type"] = "audio"; + result.append(playlist); + + playlist["playlistid"] = PLAYLIST::TYPE_VIDEO; + playlist["type"] = "video"; + result.append(playlist); + + playlist["playlistid"] = PLAYLIST::TYPE_PICTURE; + playlist["type"] = "picture"; + result.append(playlist); + + return OK; +} + +JSONRPC_STATUS CPlaylistOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(playlistId, propertyName, property)) != OK) + return ret; + + result[propertyName] = property; + } + + return OK; +} + +JSONRPC_STATUS CPlaylistOperations::GetItems(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList list; + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + case PLAYLIST::TYPE_MUSIC: + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_GET_ITEMS, playlistId, -1, + static_cast<void*>(&list)); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow) + slideshow->GetSlideShowContents(list); + break; + } + + HandleFileItemList("id", true, "items", list, parameterObject, result); + + return OK; +} + +bool CPlaylistOperations::CheckMediaParameter(PLAYLIST::Id playlistId, const CVariant& itemObject) +{ + if (itemObject.isMember("media") && itemObject["media"].asString().compare("files") != 0) + { + if (playlistId == PLAYLIST::TYPE_VIDEO && itemObject["media"].asString().compare("video") != 0) + return false; + if (playlistId == PLAYLIST::TYPE_MUSIC && itemObject["media"].asString().compare("music") != 0) + return false; + if (playlistId == PLAYLIST::TYPE_PICTURE && + itemObject["media"].asString().compare("video") != 0 && + itemObject["media"].asString().compare("pictures") != 0) + return false; + } + return true; +} + +JSONRPC_STATUS CPlaylistOperations::Add(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + + CGUIWindowSlideShow *slideshow = NULL; + if (playlistId == PLAYLIST::TYPE_PICTURE) + { + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow == NULL) + return FailedToExecute; + } + + CFileItemList list; + if (!HandleItemsParameter(playlistId, parameterObject["item"], list)) + return InvalidParams; + + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + case PLAYLIST::TYPE_MUSIC: + { + auto tmpList = new CFileItemList(); + tmpList->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_ADD, playlistId, -1, + static_cast<void*>(tmpList)); + break; + } + case PLAYLIST::TYPE_PICTURE: + for (int index = 0; index < list.Size(); index++) + { + CPictureInfoTag picture = CPictureInfoTag(); + if (!picture.Load(list[index]->GetPath())) + continue; + + *list[index]->GetPictureInfoTag() = picture; + slideshow->Add(list[index].get()); + } + break; + + default: + return InvalidParams; + } + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Insert(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + CFileItemList list; + if (!HandleItemsParameter(playlistId, parameterObject["item"], list)) + return InvalidParams; + + auto tmpList = new CFileItemList(); + tmpList->Copy(list); + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_PLAYLISTPLAYER_INSERT, playlistId, + static_cast<int>(parameterObject["position"].asInteger()), static_cast<void*>(tmpList)); + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Remove(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + int position = (int)parameterObject["position"].asInteger(); + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == playlistId && + CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == position) + return InvalidParams; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_REMOVE, playlistId, position); + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Clear(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_CLEAR, playlistId); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (!slideshow) + return FailedToExecute; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_SLIDESHOW, -1, + static_cast<void*>(new CAction(ACTION_STOP))); + slideshow->Reset(); + break; + } + + return ACK; +} + +JSONRPC_STATUS CPlaylistOperations::Swap(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + PLAYLIST::Id playlistId = GetPlaylist(parameterObject["playlistid"]); + if (playlistId == PLAYLIST::TYPE_PICTURE) + return FailedToExecute; + + auto tmpVec = new std::vector<int>(); + tmpVec->push_back(static_cast<int>(parameterObject["position1"].asInteger())); + tmpVec->push_back(static_cast<int>(parameterObject["position2"].asInteger())); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_PLAYLISTPLAYER_SWAP, playlistId, -1, + static_cast<void*>(tmpVec)); + + return ACK; +} + +PLAYLIST::Id CPlaylistOperations::GetPlaylist(const CVariant& playlist) +{ + PLAYLIST::Id playlistId = playlist.asInteger(PLAYLIST::TYPE_NONE); + if (playlistId != PLAYLIST::TYPE_NONE) + return playlistId; + + return PLAYLIST::TYPE_NONE; +} + +JSONRPC_STATUS CPlaylistOperations::GetPropertyValue(PLAYLIST::Id playlistId, + const std::string& property, + CVariant& result) +{ + if (property == "type") + { + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + result = "audio"; + break; + + case PLAYLIST::TYPE_VIDEO: + result = "video"; + break; + + case PLAYLIST::TYPE_PICTURE: + result = "pictures"; + break; + + default: + result = "unknown"; + break; + } + } + else if (property == "size") + { + CFileItemList list; + CGUIWindowSlideShow *slideshow = NULL; + switch (playlistId) + { + case PLAYLIST::TYPE_MUSIC: + case PLAYLIST::TYPE_VIDEO: + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_GET_ITEMS, playlistId, -1, + static_cast<void*>(&list)); + result = list.Size(); + break; + + case PLAYLIST::TYPE_PICTURE: + slideshow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIWindowSlideShow>(WINDOW_SLIDESHOW); + if (slideshow) + result = slideshow->NumSlides(); + else + result = 0; + break; + + default: + result = 0; + break; + } + } + else + return InvalidParams; + + return OK; +} + +bool CPlaylistOperations::HandleItemsParameter(PLAYLIST::Id playlistId, + const CVariant& itemParam, + CFileItemList& items) +{ + std::vector<CVariant> vecItems; + if (itemParam.isArray()) + vecItems.assign(itemParam.begin_array(), itemParam.end_array()); + else + vecItems.push_back(itemParam); + + bool success = false; + for (auto& itemIt : vecItems) + { + if (!CheckMediaParameter(playlistId, itemIt)) + continue; + + switch (playlistId) + { + case PLAYLIST::TYPE_VIDEO: + itemIt["media"] = "video"; + break; + case PLAYLIST::TYPE_MUSIC: + itemIt["media"] = "music"; + break; + case PLAYLIST::TYPE_PICTURE: + itemIt["media"] = "pictures"; + break; + } + + success |= FillFileItemList(itemIt, items); + } + + return success; +} diff --git a/xbmc/interfaces/json-rpc/PlaylistOperations.h b/xbmc/interfaces/json-rpc/PlaylistOperations.h new file mode 100644 index 0000000..a6a6f83 --- /dev/null +++ b/xbmc/interfaces/json-rpc/PlaylistOperations.h @@ -0,0 +1,46 @@ +/* + * 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 "FileItemHandler.h" +#include "JSONRPC.h" + +class CFileItemList; +class CVariant; + +namespace PLAYLIST +{ +using Id = int; +} // namespace PLAYLIST + +namespace JSONRPC +{ + class CPlaylistOperations : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetPlaylists(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetItems(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Add(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Remove(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Insert(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clear(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Swap(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static PLAYLIST::Id GetPlaylist(const CVariant& playlist); + static JSONRPC_STATUS GetPropertyValue(PLAYLIST::Id playlistId, + const std::string& property, + CVariant& result); + static bool CheckMediaParameter(PLAYLIST::Id playlistId, const CVariant& itemObject); + static bool HandleItemsParameter(PLAYLIST::Id playlistId, + const CVariant& itemParam, + CFileItemList& items); + }; +} diff --git a/xbmc/interfaces/json-rpc/ProfilesOperations.cpp b/xbmc/interfaces/json-rpc/ProfilesOperations.cpp new file mode 100644 index 0000000..adfb010 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ProfilesOperations.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ProfilesOperations.h" + +#include "FileItem.h" +#include "GUIPassword.h" +#include "ServiceBroker.h" +#include "guilib/LocalizeStrings.h" +#include "messaging/ApplicationMessenger.h" +#include "profiles/ProfileManager.h" +#include "settings/SettingsComponent.h" +#include "utils/Digest.h" +#include "utils/Variant.h" + +using namespace JSONRPC; +using KODI::UTILITY::CDigest; + +JSONRPC_STATUS CProfilesOperations::GetProfiles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + CFileItemList listItems; + + for (unsigned int i = 0; i < profileManager->GetNumberOfProfiles(); ++i) + { + const CProfile *profile = profileManager->GetProfile(i); + CFileItemPtr item(new CFileItem(profile->getName())); + item->SetArt("thumb", profile->getThumb()); + listItems.Add(item); + } + + HandleFileItemList("profileid", false, "profiles", listItems, parameterObject, result); + + for (CVariant::const_iterator_array propertyiter = parameterObject["properties"].begin_array(); propertyiter != parameterObject["properties"].end_array(); ++propertyiter) + { + if (propertyiter->isString() && + propertyiter->asString() == "lockmode") + { + for (CVariant::iterator_array profileiter = result["profiles"].begin_array(); profileiter != result["profiles"].end_array(); ++profileiter) + { + std::string profilename = (*profileiter)["label"].asString(); + int index = profileManager->GetProfileIndex(profilename); + const CProfile *profile = profileManager->GetProfile(index); + LockType locktype = LOCK_MODE_UNKNOWN; + if (index == 0) + locktype = profileManager->GetMasterProfile().getLockMode(); + else + locktype = profile->getLockMode(); + (*profileiter)["lockmode"] = locktype; + } + break; + } + } + return OK; +} + +JSONRPC_STATUS CProfilesOperations::GetCurrentProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + const CProfile& currentProfile = profileManager->GetCurrentProfile(); + CVariant profileVariant = CVariant(CVariant::VariantTypeObject); + profileVariant["label"] = currentProfile.getName(); + for (CVariant::const_iterator_array propertyiter = parameterObject["properties"].begin_array(); propertyiter != parameterObject["properties"].end_array(); ++propertyiter) + { + if (propertyiter->isString()) + { + if (propertyiter->asString() == "lockmode") + profileVariant["lockmode"] = currentProfile.getLockMode(); + else if (propertyiter->asString() == "thumbnail") + profileVariant["thumbnail"] = currentProfile.getThumb(); + } + } + + result = profileVariant; + + return OK; +} + +JSONRPC_STATUS CProfilesOperations::LoadProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + const std::shared_ptr<CProfileManager> profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string profilename = parameterObject["profile"].asString(); + int index = profileManager->GetProfileIndex(profilename); + + if (index < 0) + return InvalidParams; + + // get the profile + const CProfile *profile = profileManager->GetProfile(index); + if (profile == NULL) + return InvalidParams; + + bool bPrompt = parameterObject["prompt"].asBoolean(); + bool bCanceled = false; + bool bLoadProfile = false; + + // if the profile does not require a password or + // the user is prompted and provides the correct password + // we can load the requested profile + if (profile->getLockMode() == LOCK_MODE_EVERYONE || + (bPrompt && g_passwordManager.IsProfileLockUnlocked(index, bCanceled, bPrompt))) + bLoadProfile = true; + else if (!bCanceled) // Password needed and user provided it + { + const CVariant &passwordObject = parameterObject["password"]; + const std::string& strToVerify = profile->getLockCode(); + std::string password = passwordObject["value"].asString(); + + // Create password hash from the provided password if md5 is not used + std::string md5pword2; + std::string encryption = passwordObject["encryption"].asString(); + if (encryption == "none") + md5pword2 = CDigest::Calculate(CDigest::Type::MD5, password); + else if (encryption == "md5") + md5pword2 = password; + + // Verify provided password + if (StringUtils::EqualsNoCase(strToVerify, md5pword2)) + bLoadProfile = true; + } + + if (bLoadProfile) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_LOADPROFILE, index); + return ACK; + } + return InvalidParams; +} diff --git a/xbmc/interfaces/json-rpc/ProfilesOperations.h b/xbmc/interfaces/json-rpc/ProfilesOperations.h new file mode 100644 index 0000000..7f11b83 --- /dev/null +++ b/xbmc/interfaces/json-rpc/ProfilesOperations.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CProfilesOperations : CFileItemHandler + { + public: + static JSONRPC_STATUS GetProfiles(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetCurrentProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS LoadProfile(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/SettingsOperations.cpp b/xbmc/interfaces/json-rpc/SettingsOperations.cpp new file mode 100644 index 0000000..09992f2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SettingsOperations.cpp @@ -0,0 +1,905 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "SettingsOperations.h" + +#include "ServiceBroker.h" +#include "addons/Addon.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonInfo.h" +#include "guilib/LocalizeStrings.h" +#include "settings/SettingAddon.h" +#include "settings/SettingControl.h" +#include "settings/SettingDateTime.h" +#include "settings/SettingPath.h" +#include "settings/SettingUtils.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "settings/SkinSettings.h" +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "settings/lib/SettingSection.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CSettingsOperations::GetSections(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + bool listCategories = !parameterObject["properties"].empty() && parameterObject["properties"][0].asString() == "categories"; + + result["sections"] = CVariant(CVariant::VariantTypeArray); + + // apply the level filter + SettingSectionList allSections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + for (const auto& itSection : allSections) + { + SettingCategoryList categories = itSection->GetCategories(level); + if (categories.empty()) + continue; + + CVariant varSection(CVariant::VariantTypeObject); + if (!SerializeSettingSection(itSection, varSection)) + continue; + + if (listCategories) + { + varSection["categories"] = CVariant(CVariant::VariantTypeArray); + for (const auto& itCategory : categories) + { + CVariant varCategory(CVariant::VariantTypeObject); + if (!SerializeSettingCategory(itCategory, varCategory)) + continue; + + varSection["categories"].push_back(varCategory); + } + } + + result["sections"].push_back(varSection); + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetCategories(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + std::string strSection = parameterObject["section"].asString(); + bool listSettings = !parameterObject["properties"].empty() && parameterObject["properties"][0].asString() == "settings"; + + std::vector<SettingSectionPtr> sections; + if (!strSection.empty()) + { + SettingSectionPtr section = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSection(strSection); + if (section == NULL) + return InvalidParams; + + sections.push_back(section); + } + else + sections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + + result["categories"] = CVariant(CVariant::VariantTypeArray); + + for (const auto& itSection : sections) + { + SettingCategoryList categories = itSection->GetCategories(level); + for (const auto& itCategory : categories) + { + CVariant varCategory(CVariant::VariantTypeObject); + if (!SerializeSettingCategory(itCategory, varCategory)) + continue; + + if (listSettings) + { + varCategory["groups"] = CVariant(CVariant::VariantTypeArray); + + SettingGroupList groups = itCategory->GetGroups(level); + for (const auto& itGroup : groups) + { + CVariant varGroup(CVariant::VariantTypeObject); + if (!SerializeSettingGroup(itGroup, varGroup)) + continue; + + varGroup["settings"] = CVariant(CVariant::VariantTypeArray); + SettingList settings = itGroup->GetSettings(level); + for (const auto& itSetting : settings) + { + if (itSetting->IsVisible()) + { + CVariant varSetting(CVariant::VariantTypeObject); + if (!SerializeSetting(itSetting, varSetting)) + continue; + + varGroup["settings"].push_back(varSetting); + } + } + + varCategory["groups"].push_back(varGroup); + } + } + + result["categories"].push_back(varCategory); + } + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSettings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + SettingLevel level = ParseSettingLevel(parameterObject["level"].asString()); + const CVariant &filter = parameterObject["filter"]; + bool doFilter = filter.isMember("section") && filter.isMember("category"); + std::string strSection, strCategory; + if (doFilter) + { + strSection = filter["section"].asString(); + strCategory = filter["category"].asString(); + } + + std::vector<SettingSectionPtr> sections; + + if (doFilter) + { + SettingSectionPtr section = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSection(strSection); + if (section == NULL) + return InvalidParams; + + sections.push_back(section); + } + else + sections = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSections(); + + result["settings"] = CVariant(CVariant::VariantTypeArray); + + for (const auto& itSection : sections) + { + SettingCategoryList categories = itSection->GetCategories(level); + bool found = !doFilter; + for (const auto& itCategory : categories) + { + if (!doFilter || StringUtils::EqualsNoCase(itCategory->GetId(), strCategory)) + { + SettingGroupList groups = itCategory->GetGroups(level); + for (const auto& itGroup : groups) + { + SettingList settings = itGroup->GetSettings(level); + for (const auto& itSetting : settings) + { + if (itSetting->IsVisible()) + { + CVariant varSetting(CVariant::VariantTypeObject); + if (!SerializeSetting(itSetting, varSetting)) + continue; + + result["settings"].push_back(varSetting); + } + } + } + found = true; + + if (doFilter) + break; + } + } + + if (doFilter && !found) + return InvalidParams; + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + CVariant value; + switch (setting->GetType()) + { + case SettingType::Boolean: + value = std::static_pointer_cast<CSettingBool>(setting)->GetValue(); + break; + + case SettingType::Integer: + value = std::static_pointer_cast<CSettingInt>(setting)->GetValue(); + break; + + case SettingType::Number: + value = std::static_pointer_cast<CSettingNumber>(setting)->GetValue(); + break; + + case SettingType::String: + value = std::static_pointer_cast<CSettingString>(setting)->GetValue(); + break; + + case SettingType::List: + { + SerializeSettingListValues(CServiceBroker::GetSettingsComponent()->GetSettings()->GetList(settingId), value); + break; + } + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + result["value"] = value; + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::SetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + CVariant value = parameterObject["value"]; + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + switch (setting->GetType()) + { + case SettingType::Boolean: + if (!value.isBoolean()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingBool>(setting)->SetValue(value.asBoolean()); + break; + + case SettingType::Integer: + if (!value.isInteger() && !value.isUnsignedInteger()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingInt>(setting)->SetValue((int)value.asInteger()); + break; + + case SettingType::Number: + if (!value.isDouble()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingNumber>(setting)->SetValue(value.asDouble()); + break; + + case SettingType::String: + if (!value.isString()) + return InvalidParams; + + result = std::static_pointer_cast<CSettingString>(setting)->SetValue(value.asString()); + break; + + case SettingType::List: + { + if (!value.isArray()) + return InvalidParams; + + std::vector<CVariant> values; + for (CVariant::const_iterator_array itValue = value.begin_array(); itValue != value.end_array(); ++itValue) + values.push_back(*itValue); + + result = CServiceBroker::GetSettingsComponent()->GetSettings()->SetList(settingId, values); + break; + } + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + return OK; +} + +JSONRPC_STATUS CSettingsOperations::ResetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string settingId = parameterObject["setting"].asString(); + + SettingPtr setting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetSetting(settingId); + if (setting == NULL || + !setting->IsVisible()) + return InvalidParams; + + switch (setting->GetType()) + { + case SettingType::Boolean: + case SettingType::Integer: + case SettingType::Number: + case SettingType::String: + case SettingType::List: + setting->Reset(); + break; + + case SettingType::Unknown: + case SettingType::Action: + default: + return InvalidParams; + } + + return ACK; +} + +SettingLevel CSettingsOperations::ParseSettingLevel(const std::string &strLevel) +{ + if (StringUtils::EqualsNoCase(strLevel, "basic")) + return SettingLevel::Basic; + if (StringUtils::EqualsNoCase(strLevel, "advanced")) + return SettingLevel::Advanced; + if (StringUtils::EqualsNoCase(strLevel, "expert")) + return SettingLevel::Expert; + + return SettingLevel::Standard; +} + +bool CSettingsOperations::SerializeISetting(const std::shared_ptr<const ISetting>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["id"] = setting->GetId(); + + return true; +} + +bool CSettingsOperations::SerializeSettingSection( + const std::shared_ptr<const CSettingSection>& setting, CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + return true; +} + +bool CSettingsOperations::SerializeSettingCategory( + const std::shared_ptr<const CSettingCategory>& setting, CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + return true; +} + +bool CSettingsOperations::SerializeSettingGroup(const std::shared_ptr<const CSettingGroup>& setting, + CVariant& obj) +{ + return SerializeISetting(setting, obj); +} + +bool CSettingsOperations::SerializeSetting(const std::shared_ptr<const CSetting>& setting, + CVariant& obj) +{ + if (!SerializeISetting(setting, obj)) + return false; + + obj["label"] = g_localizeStrings.Get(setting->GetLabel()); + if (setting->GetHelp() >= 0) + obj["help"] = g_localizeStrings.Get(setting->GetHelp()); + + switch (setting->GetLevel()) + { + case SettingLevel::Basic: + obj["level"] = "basic"; + break; + + case SettingLevel::Standard: + obj["level"] = "standard"; + break; + + case SettingLevel::Advanced: + obj["level"] = "advanced"; + break; + + case SettingLevel::Expert: + obj["level"] = "expert"; + break; + + default: + return false; + } + + obj["enabled"] = setting->IsEnabled(); + obj["parent"] = setting->GetParent(); + + obj["control"] = CVariant(CVariant::VariantTypeObject); + if (!SerializeSettingControl(setting->GetControl(), obj["control"])) + return false; + + switch (setting->GetType()) + { + case SettingType::Boolean: + obj["type"] = "boolean"; + if (!SerializeSettingBool(std::static_pointer_cast<const CSettingBool>(setting), obj)) + return false; + break; + + case SettingType::Integer: + obj["type"] = "integer"; + if (!SerializeSettingInt(std::static_pointer_cast<const CSettingInt>(setting), obj)) + return false; + break; + + case SettingType::Number: + obj["type"] = "number"; + if (!SerializeSettingNumber(std::static_pointer_cast<const CSettingNumber>(setting), obj)) + return false; + break; + + case SettingType::String: + obj["type"] = "string"; + if (!SerializeSettingString(std::static_pointer_cast<const CSettingString>(setting), obj)) + return false; + break; + + case SettingType::Action: + obj["type"] = "action"; + if (!SerializeSettingAction(std::static_pointer_cast<const CSettingAction>(setting), obj)) + return false; + break; + + case SettingType::List: + obj["type"] = "list"; + if (!SerializeSettingList(std::static_pointer_cast<const CSettingList>(setting), obj)) + return false; + break; + + default: + return false; + } + + return true; +} + +bool CSettingsOperations::SerializeSettingBool(const std::shared_ptr<const CSettingBool>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["value"] = setting->GetValue(); + obj["default"] = setting->GetDefault(); + + return true; +} + +bool CSettingsOperations::SerializeSettingInt(const std::shared_ptr<const CSettingInt>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["default"] = setting->GetDefault(); + + switch (setting->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const TranslatableIntegerSettingOptions& options = setting->GetTranslatableOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = g_localizeStrings.Get(itOption.label); + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Static: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const IntegerSettingOptions& options = setting->GetOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Dynamic: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + IntegerSettingOptions options = std::const_pointer_cast<CSettingInt>(setting)->UpdateDynamicOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Unknown: + default: + obj["minimum"] = setting->GetMinimum(); + obj["step"] = setting->GetStep(); + obj["maximum"] = setting->GetMaximum(); + break; + } + + // this must be done after potentially calling CSettingInt::UpdateDynamicOptions() because it can + // change the value of the setting + obj["value"] = setting->GetValue(); + + return true; +} + +bool CSettingsOperations::SerializeSettingNumber( + const std::shared_ptr<const CSettingNumber>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["value"] = setting->GetValue(); + obj["default"] = setting->GetDefault(); + + obj["minimum"] = setting->GetMinimum(); + obj["step"] = setting->GetStep(); + obj["maximum"] = setting->GetMaximum(); + + return true; +} + +bool CSettingsOperations::SerializeSettingString( + const std::shared_ptr<const CSettingString>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["default"] = setting->GetDefault(); + + obj["allowempty"] = setting->AllowEmpty(); + obj["allownewoption"] = setting->AllowNewOption(); + + switch (setting->GetOptionsType()) + { + case SettingOptionsType::StaticTranslatable: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const TranslatableStringSettingOptions& options = setting->GetTranslatableOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = g_localizeStrings.Get(itOption.first); + varOption["value"] = itOption.second; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Static: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + const StringSettingOptions& options = setting->GetOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Dynamic: + { + obj["options"] = CVariant(CVariant::VariantTypeArray); + StringSettingOptions options = std::const_pointer_cast<CSettingString>(setting)->UpdateDynamicOptions(); + for (const auto& itOption : options) + { + CVariant varOption(CVariant::VariantTypeObject); + varOption["label"] = itOption.label; + varOption["value"] = itOption.value; + obj["options"].push_back(varOption); + } + break; + } + + case SettingOptionsType::Unknown: + default: + break; + } + + // this must be done after potentially calling CSettingString::UpdateDynamicOptions() because it + // can change the value of the setting + obj["value"] = setting->GetValue(); + + std::shared_ptr<const ISettingControl> control = setting->GetControl(); + if (control->GetFormat() == "path") + { + if (!SerializeSettingPath(std::static_pointer_cast<const CSettingPath>(setting), obj)) + return false; + } + if (control->GetFormat() == "addon") + { + if (!SerializeSettingAddon(std::static_pointer_cast<const CSettingAddon>(setting), obj)) + return false; + } + if (control->GetFormat() == "date") + { + if (!SerializeSettingDate(std::static_pointer_cast<const CSettingDate>(setting), obj)) + return false; + } + if (control->GetFormat() == "time") + { + if (!SerializeSettingTime(std::static_pointer_cast<const CSettingTime>(setting), obj)) + return false; + } + + return true; +} + +bool CSettingsOperations::SerializeSettingAction( + const std::shared_ptr<const CSettingAction>& setting, CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["data"] = setting->GetData(); + + return true; +} + +bool CSettingsOperations::SerializeSettingList(const std::shared_ptr<const CSettingList>& setting, + CVariant& obj) +{ + if (setting == NULL || + !SerializeSetting(setting->GetDefinition(), obj["definition"])) + return false; + + SerializeSettingListValues(CSettingUtils::GetList(setting), obj["value"]); + SerializeSettingListValues(CSettingUtils::ListToValues(setting, setting->GetDefault()), obj["default"]); + + obj["elementtype"] = obj["definition"]["type"]; + obj["delimiter"] = setting->GetDelimiter(); + obj["minimumItems"] = setting->GetMinimumItems(); + obj["maximumItems"] = setting->GetMaximumItems(); + + return true; +} + +bool CSettingsOperations::SerializeSettingPath(const std::shared_ptr<const CSettingPath>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "path"; + obj["writable"] = setting->Writable(); + obj["sources"] = setting->GetSources(); + + return true; +} + +bool CSettingsOperations::SerializeSettingAddon(const std::shared_ptr<const CSettingAddon>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "addon"; + obj["addontype"] = ADDON::CAddonInfo::TranslateType(setting->GetAddonType()); + + return true; +} + +bool CSettingsOperations::SerializeSettingDate(const std::shared_ptr<const CSettingDate>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "date"; + + return true; +} + +bool CSettingsOperations::SerializeSettingTime(const std::shared_ptr<const CSettingTime>& setting, + CVariant& obj) +{ + if (setting == NULL) + return false; + + obj["type"] = "time"; + + return true; +} + +bool CSettingsOperations::SerializeSettingControl( + const std::shared_ptr<const ISettingControl>& control, CVariant& obj) +{ + if (control == NULL) + return false; + + const std::string& type = control->GetType(); + obj["type"] = type; + obj["format"] = control->GetFormat(); + obj["delayed"] = control->GetDelayed(); + + if (type == "spinner") + { + std::shared_ptr<const CSettingControlSpinner> spinner = std::static_pointer_cast<const CSettingControlSpinner>(control); + if (spinner->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(spinner->GetFormatLabel()); + else if (!spinner->GetFormatString().empty() && spinner->GetFormatString() != "{:d}") + obj["formatlabel"] = spinner->GetFormatString(); + if (spinner->GetMinimumLabel() >= 0) + obj["minimumlabel"] = g_localizeStrings.Get(spinner->GetMinimumLabel()); + } + else if (type == "edit") + { + std::shared_ptr<const CSettingControlEdit> edit = std::static_pointer_cast<const CSettingControlEdit>(control); + obj["hidden"] = edit->IsHidden(); + obj["verifynewvalue"] = edit->VerifyNewValue(); + if (edit->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(edit->GetHeading()); + } + else if (type == "button") + { + std::shared_ptr<const CSettingControlButton> button = std::static_pointer_cast<const CSettingControlButton>(control); + if (button->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(button->GetHeading()); + } + else if (type == "list") + { + std::shared_ptr<const CSettingControlList> list = std::static_pointer_cast<const CSettingControlList>(control); + if (list->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(list->GetHeading()); + obj["multiselect"] = list->CanMultiSelect(); + } + else if (type == "slider") + { + std::shared_ptr<const CSettingControlSlider> slider = std::static_pointer_cast<const CSettingControlSlider>(control); + if (slider->GetHeading() >= 0) + obj["heading"] = g_localizeStrings.Get(slider->GetHeading()); + obj["popup"] = slider->UsePopup(); + if (slider->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(slider->GetFormatLabel()); + else + obj["formatlabel"] = slider->GetFormatString(); + } + else if (type == "range") + { + std::shared_ptr<const CSettingControlRange> range = std::static_pointer_cast<const CSettingControlRange>(control); + if (range->GetFormatLabel() >= 0) + obj["formatlabel"] = g_localizeStrings.Get(range->GetFormatLabel()); + else + obj["formatlabel"] = ""; + if (range->GetValueFormatLabel() >= 0) + obj["formatvalue"] = g_localizeStrings.Get(range->GetValueFormatLabel()); + else + obj["formatvalue"] = range->GetValueFormat(); + } + else if (type != "toggle" && type != "label") + return false; + + return true; +} + +void CSettingsOperations::SerializeSettingListValues(const std::vector<CVariant> &values, CVariant &obj) +{ + obj = CVariant(CVariant::VariantTypeArray); + for (const auto& itValue : values) + obj.push_back(itValue); +} + +JSONRPC_STATUS CSettingsOperations::GetSkinSettings(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::set<ADDON::CSkinSettingPtr> settings = CSkinSettings::GetInstance().GetSettings(); + CVariant varSettings(CVariant::VariantTypeArray); + + for (const auto& setting : settings) + { + CVariant varSetting(CVariant::VariantTypeObject); + varSetting["id"] = setting->name; + + if (setting->GetType() == "bool") + { + varSetting["value"] = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value; + varSetting["type"] = "boolean"; + } + else if (setting->GetType() == "string") + { + varSetting["value"] = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value; + varSetting["type"] = setting->GetType(); + } + else + continue; + + varSettings.push_back(varSetting); + } + + result["skin"] = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_LOOKANDFEEL_SKIN); + result["settings"] = varSettings; + return OK; +} + +JSONRPC_STATUS CSettingsOperations::GetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::string settingId = parameterObject["setting"].asString(); + ADDON::CSkinSettingPtr setting = CSkinSettings::GetInstance().GetSetting(settingId); + + if (setting == nullptr) + return InvalidParams; + + CVariant value; + if (setting->GetType() == "string") + value = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value; + else if (setting->GetType() == "bool") + value = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value; + else + return InvalidParams; + + result["value"] = value; + return OK; +} + +JSONRPC_STATUS CSettingsOperations::SetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result) +{ + const std::string settingId = parameterObject["setting"].asString(); + ADDON::CSkinSettingPtr setting = CSkinSettings::GetInstance().GetSetting(settingId); + + if (setting == nullptr) + return InvalidParams; + + CVariant value = parameterObject["value"]; + if (setting->GetType() == "string") + { + if (!value.isString()) + return InvalidParams; + + result = std::static_pointer_cast<ADDON::CSkinSettingString>(setting)->value = value.asString(); + } + else if (setting->GetType() == "bool") + { + if (!value.isBoolean()) + return InvalidParams; + + result = std::static_pointer_cast<ADDON::CSkinSettingBool>(setting)->value = value.asBoolean(); + } + else + { + return InvalidParams; + } + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/SettingsOperations.h b/xbmc/interfaces/json-rpc/SettingsOperations.h new file mode 100644 index 0000000..6267e73 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SettingsOperations.h @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" +#include "settings/lib/SettingLevel.h" + +#include <vector> + +class CVariant; +class ISetting; +class CSettingSection; +class CSettingCategory; +class CSettingGroup; +class CSetting; +class CSettingBool; +class CSettingInt; +class CSettingNumber; +class CSettingString; +class CSettingAction; +class CSettingList; +class CSettingPath; +class CSettingAddon; +class CSettingDate; +class CSettingTime; +class ISettingControl; + +namespace JSONRPC +{ + class CSettingsOperations + { + public: + static JSONRPC_STATUS GetSections(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetCategories(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSettings(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS ResetSettingValue(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetSkinSettings(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS GetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + static JSONRPC_STATUS SetSkinSettingValue(const std::string& method, + ITransportLayer* transport, + IClient* client, + const CVariant& parameterObject, + CVariant& result); + + private: + static SettingLevel ParseSettingLevel(const std::string &strLevel); + + static bool SerializeISetting(const std::shared_ptr<const ISetting>& setting, CVariant& obj); + static bool SerializeSettingSection(const std::shared_ptr<const CSettingSection>& setting, + CVariant& obj); + static bool SerializeSettingCategory(const std::shared_ptr<const CSettingCategory>& setting, + CVariant& obj); + static bool SerializeSettingGroup(const std::shared_ptr<const CSettingGroup>& setting, + CVariant& obj); + static bool SerializeSetting(const std::shared_ptr<const CSetting>& setting, CVariant& obj); + static bool SerializeSettingBool(const std::shared_ptr<const CSettingBool>& setting, + CVariant& obj); + static bool SerializeSettingInt(const std::shared_ptr<const CSettingInt>& setting, + CVariant& obj); + static bool SerializeSettingNumber(const std::shared_ptr<const CSettingNumber>& setting, + CVariant& obj); + static bool SerializeSettingString(const std::shared_ptr<const CSettingString>& setting, + CVariant& obj); + static bool SerializeSettingAction(const std::shared_ptr<const CSettingAction>& setting, + CVariant& obj); + static bool SerializeSettingList(const std::shared_ptr<const CSettingList>& setting, + CVariant& obj); + static bool SerializeSettingPath(const std::shared_ptr<const CSettingPath>& setting, + CVariant& obj); + static bool SerializeSettingAddon(const std::shared_ptr<const CSettingAddon>& setting, + CVariant& obj); + static bool SerializeSettingDate(const std::shared_ptr<const CSettingDate>& setting, + CVariant& obj); + static bool SerializeSettingTime(const std::shared_ptr<const CSettingTime>& setting, + CVariant& obj); + static bool SerializeSettingControl(const std::shared_ptr<const ISettingControl>& control, + CVariant& obj); + + static void SerializeSettingListValues(const std::vector<CVariant> &values, CVariant &obj); + }; +} diff --git a/xbmc/interfaces/json-rpc/SystemOperations.cpp b/xbmc/interfaces/json-rpc/SystemOperations.cpp new file mode 100644 index 0000000..b4d508c --- /dev/null +++ b/xbmc/interfaces/json-rpc/SystemOperations.cpp @@ -0,0 +1,101 @@ +/* + * 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 "SystemOperations.h" + +#include "ServiceBroker.h" +#include "interfaces/builtins/Builtins.h" +#include "messaging/ApplicationMessenger.h" +#include "powermanagement/PowerManager.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CSystemOperations::GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVariant properties = CVariant(CVariant::VariantTypeObject); + for (unsigned int index = 0; index < parameterObject["properties"].size(); index++) + { + std::string propertyName = parameterObject["properties"][index].asString(); + CVariant property; + JSONRPC_STATUS ret; + if ((ret = GetPropertyValue(client->GetPermissionFlags(), propertyName, property)) != OK) + return ret; + + properties[propertyName] = property; + } + + result = properties; + + return OK; +} + +JSONRPC_STATUS CSystemOperations::EjectOpticalDrive(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return CBuiltins::GetInstance().Execute("EjectTray") == 0 ? ACK : FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Shutdown(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanPowerdown()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_POWERDOWN); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Suspend(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanSuspend()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SUSPEND); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Hibernate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanHibernate()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_HIBERNATE); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::Reboot(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + if (CServiceBroker::GetPowerManager().CanReboot()) + { + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESTART); + return ACK; + } + else + return FailedToExecute; +} + +JSONRPC_STATUS CSystemOperations::GetPropertyValue(int permissions, const std::string &property, CVariant &result) +{ + if (property == "canshutdown") + result = CServiceBroker::GetPowerManager().CanPowerdown() && (permissions & ControlPower); + else if (property == "cansuspend") + result = CServiceBroker::GetPowerManager().CanSuspend() && (permissions & ControlPower); + else if (property == "canhibernate") + result = CServiceBroker::GetPowerManager().CanHibernate() && (permissions & ControlPower); + else if (property == "canreboot") + result = CServiceBroker::GetPowerManager().CanReboot() && (permissions & ControlPower); + else + return InvalidParams; + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/SystemOperations.h b/xbmc/interfaces/json-rpc/SystemOperations.h new file mode 100644 index 0000000..1bc1ce2 --- /dev/null +++ b/xbmc/interfaces/json-rpc/SystemOperations.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CSystemOperations + { + public: + static JSONRPC_STATUS GetProperties(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS EjectOpticalDrive(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Shutdown(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Suspend(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Hibernate(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Reboot(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + private: + static JSONRPC_STATUS GetPropertyValue(int permissions, const std::string &property, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/TextureOperations.cpp b/xbmc/interfaces/json-rpc/TextureOperations.cpp new file mode 100644 index 0000000..326093b --- /dev/null +++ b/xbmc/interfaces/json-rpc/TextureOperations.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "TextureOperations.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureCache.h" +#include "TextureDatabase.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CTextureOperations::GetTextures(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CFileItemList listItems; + + CTextureDatabase db; + if (!db.Open()) + return InternalError; + + CDatabase::Filter dbFilter; + const CVariant &filter = parameterObject["filter"]; + if (filter.isObject()) + { + CVariant xspObj(CVariant::VariantTypeObject); + + if (filter.isMember("field")) + { + xspObj["and"] = CVariant(CVariant::VariantTypeArray); + xspObj["and"].push_back(filter); + } + else + xspObj = filter; + + // decipher the rules + CDatabaseQueryRuleCombination rule; + if (!rule.Load(xspObj, &db)) + return InvalidParams; + + dbFilter.AppendWhere(rule.GetWhereClause(db, "")); + } + + // fetch textures from the database + CVariant items = CVariant(CVariant::VariantTypeArray); + if (!db.GetTextures(items, dbFilter)) + return InternalError; + + // return only what was asked for, plus textureid + CVariant prop = parameterObject["properties"]; + prop.push_back("textureid"); + if (!items.empty() && prop.isArray()) + { + std::set<std::string> fields; + CVariant &item = items[0]; + for (CVariant::const_iterator_map field = item.begin_map(); field != item.end_map(); ++field) + { + if (std::find(prop.begin_array(), prop.end_array(), field->first) == prop.end_array()) + fields.insert(field->first); + } + // erase these fields + for (CVariant::iterator_array item = items.begin_array(); item != items.end_array(); ++item) + { + for (const auto& i : fields) + item->erase(i); + } + if (fields.find("url") == fields.end()) + { + // wrap cached url to something retrieval from Files.GetFiles() + for (CVariant::iterator_array item = items.begin_array(); item != items.end_array(); ++item) + { + CVariant &cachedUrl = (*item)["url"]; + cachedUrl = CTextureUtils::GetWrappedImageURL(cachedUrl.asString()); + } + } + } + + result["textures"] = items; + return OK; +} + +JSONRPC_STATUS CTextureOperations::RemoveTexture(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["textureid"].asInteger(); + + if (!CServiceBroker::GetTextureCache()->ClearCachedImage(id)) + return InvalidParams; + + return ACK; +} diff --git a/xbmc/interfaces/json-rpc/TextureOperations.h b/xbmc/interfaces/json-rpc/TextureOperations.h new file mode 100644 index 0000000..63a53a3 --- /dev/null +++ b/xbmc/interfaces/json-rpc/TextureOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CTextureOperations + { + public: + static JSONRPC_STATUS GetTextures(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveTexture(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.cpp b/xbmc/interfaces/json-rpc/VideoLibrary.cpp new file mode 100644 index 0000000..db152cd --- /dev/null +++ b/xbmc/interfaces/json-rpc/VideoLibrary.cpp @@ -0,0 +1,1385 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "VideoLibrary.h" + +#include "FileItem.h" +#include "PVROperations.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "Util.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/SortUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "video/VideoDatabase.h" +#include "video/VideoDbUrl.h" +#include "video/VideoLibraryQueue.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CVideoLibrary::GetMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://movies/titles/")) + return InternalError; + + int genreID = -1, year = -1, setID = 0; + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + genreID = (int)filter["genreid"].asInteger(); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + year = (int)filter["year"].asInteger(); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("country")) + videoUrl.AddOption("country", filter["country"].asString()); + else if (filter.isMember("setid")) + setID = (int)filter["setid"].asInteger(); + else if (filter.isMember("set")) + videoUrl.AddOption("set", filter["set"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("movies", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + // setID must not be -1 otherwise GetMoviesNav() will return sets + if (setID < 0) + setID = 0; + + CFileItemList items; + if (!videodatabase.GetMoviesNav(videoUrl.ToString(), items, genreID, year, -1, -1, -1, -1, setID, -1, sorting, RequiresAdditionalDetails(MediaTypeMovie, parameterObject))) + return InvalidParams; + + return HandleItems("movieid", "movies", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["movieid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id, RequiresAdditionalDetails(MediaTypeMovie, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("movieid", true, "moviedetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMovieSets(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetSetsNav("videodb://movies/sets/", items, VideoDbContentType::MOVIES)) + return InternalError; + + HandleFileItemList("setid", false, "sets", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["setid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + // Get movie set details + CVideoInfoTag infos; + if (!videodatabase.GetSetInfo(id, infos) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("setid", false, "setdetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + + // Get movies from the set + CFileItemList items; + if (!videodatabase.GetMoviesNav("videodb://movies/titles/", items, -1, -1, -1, -1, -1, -1, id, -1, SortDescription(), RequiresAdditionalDetails(MediaTypeMovie, parameterObject["movies"]))) + return InternalError; + + return HandleItems("movieid", "movies", items, parameterObject["movies"], result["setdetails"], true); +} + +JSONRPC_STATUS CVideoLibrary::GetTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://tvshows/titles/")) + return InternalError; + + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + videoUrl.AddOption("genreid", (int)filter["genreid"].asInteger()); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + videoUrl.AddOption("year", (int)filter["year"].asInteger()); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("tvshows", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + CFileItemList items; + CDatabase::Filter nofilter; + if (!videodatabase.GetTvShowsByWhere(videoUrl.ToString(), nofilter, items, sorting, RequiresAdditionalDetails(MediaTypeTvShow, parameterObject))) + return InvalidParams; + + return HandleItems("tvshowid", "tvshows", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["tvshowid"].asInteger(); + + CFileItemPtr fileItem(new CFileItem()); + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id, fileItem.get(), RequiresAdditionalDetails(MediaTypeTvShow, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + fileItem->SetFromVideoInfoTag(infos); + HandleFileItem("tvshowid", true, "tvshowdetails", fileItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetSeasons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int tvshowID = (int)parameterObject["tvshowid"].asInteger(); + + std::string strPath = StringUtils::Format("videodb://tvshows/titles/{}/", tvshowID); + CFileItemList items; + if (!videodatabase.GetSeasonsNav(strPath, items, -1, -1, -1, -1, tvshowID, false)) + return InternalError; + + HandleFileItemList("seasonid", false, "seasons", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["seasonid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetSeasonInfo(id, infos) || + infos.m_iDbId <= 0 || infos.m_iIdShow <= 0) + return InvalidParams; + + CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + HandleFileItem("seasonid", false, "seasondetails", pItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + int tvshowID = (int)parameterObject["tvshowid"].asInteger(); + int season = (int)parameterObject["season"].asInteger(); + + std::string strPath = StringUtils::Format("videodb://tvshows/titles/{}/{}/", tvshowID, season); + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString(strPath)) + return InternalError; + + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("genreid")) + videoUrl.AddOption("genreid", (int)filter["genreid"].asInteger()); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + videoUrl.AddOption("year", (int)filter["year"].asInteger()); + else if (filter.isMember("actor")) + videoUrl.AddOption("actor", filter["actor"].asString()); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("episodes", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + if (tvshowID <= 0 && (season > 0 || videoUrl.HasOption("genreid") || videoUrl.HasOption("genre") || videoUrl.HasOption("actor"))) + return InvalidParams; + + if (tvshowID > 0) + { + videoUrl.AddOption("tvshowid", tvshowID); + if (season >= 0) + videoUrl.AddOption("season", season); + } + + CFileItemList items; + if (!videodatabase.GetEpisodesByWhere(videoUrl.ToString(), CDatabase::Filter(), items, false, sorting, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject))) + return InvalidParams; + + return HandleItems("episodeid", "episodes", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetEpisodeInfo("", infos, id, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + // We need to set the correct base path to get the valid fanart + int tvshowid = infos.m_iIdShow; + if (tvshowid <= 0) + tvshowid = videodatabase.GetTvShowForEpisode(id); + + std::string basePath = + StringUtils::Format("videodb://tvshows/titles/{}/{}/{}", tvshowid, infos.m_iSeason, id); + pItem->SetPath(basePath); + + HandleFileItem("episodeid", true, "episodedetails", pItem, parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + SortDescription sorting; + ParseLimits(parameterObject, sorting.limitStart, sorting.limitEnd); + if (!ParseSorting(parameterObject, sorting.sortBy, sorting.sortOrder, sorting.sortAttributes)) + return InvalidParams; + + CVideoDbUrl videoUrl; + if (!videoUrl.FromString("videodb://musicvideos/titles/")) + return InternalError; + + int genreID = -1, year = -1; + const CVariant &filter = parameterObject["filter"]; + if (filter.isMember("artist")) + videoUrl.AddOption("artist", filter["artist"].asString()); + else if (filter.isMember("genreid")) + genreID = (int)filter["genreid"].asInteger(); + else if (filter.isMember("genre")) + videoUrl.AddOption("genre", filter["genre"].asString()); + else if (filter.isMember("year")) + year = (int)filter["year"].asInteger(); + else if (filter.isMember("director")) + videoUrl.AddOption("director", filter["director"].asString()); + else if (filter.isMember("studio")) + videoUrl.AddOption("studio", filter["studio"].asString()); + else if (filter.isMember("tag")) + videoUrl.AddOption("tag", filter["tag"].asString()); + else if (filter.isObject()) + { + std::string xsp; + if (!GetXspFiltering("musicvideos", filter, xsp)) + return InvalidParams; + + videoUrl.AddOption("xsp", xsp); + } + + CFileItemList items; + if (!videodatabase.GetMusicVideosNav(videoUrl.ToString(), items, genreID, year, -1, -1, -1, -1, -1, sorting, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject))) + return InternalError; + + return HandleItems("musicvideoid", "musicvideos", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + int id = (int)parameterObject["musicvideoid"].asInteger(); + + CVideoInfoTag infos; + if (!videodatabase.GetMusicVideoInfo("", infos, id, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject)) || infos.m_iDbId <= 0) + return InvalidParams; + + HandleFileItem("musicvideoid", true, "musicvideodetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedMoviesNav("videodb://recentlyaddedmovies/", items, 0, RequiresAdditionalDetails(MediaTypeMovie, parameterObject))) + return InternalError; + + return HandleItems("movieid", "movies", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedEpisodesNav("videodb://recentlyaddedepisodes/", items, 0, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject))) + return InternalError; + + return HandleItems("episodeid", "episodes", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetRecentlyAddedMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetRecentlyAddedMusicVideosNav("videodb://recentlyaddedmusicvideos/", items, 0, RequiresAdditionalDetails(MediaTypeMusicVideo, parameterObject))) + return InternalError; + + return HandleItems("musicvideoid", "musicvideos", items, parameterObject, result, true); +} + +JSONRPC_STATUS CVideoLibrary::GetInProgressTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetInProgressTvShowsNav("videodb://inprogresstvshows/", items, 0, RequiresAdditionalDetails(MediaTypeTvShow, parameterObject))) + return InternalError; + + return HandleItems("tvshowid", "tvshows", items, parameterObject, result, false); +} + +JSONRPC_STATUS CVideoLibrary::GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["type"].asString(); + StringUtils::ToLower(media); + VideoDbContentType idContent = VideoDbContentType::UNKNOWN; + + std::string strPath = "videodb://"; + /* select which video content to get genres from*/ + if (media == MediaTypeMovie) + { + idContent = VideoDbContentType::MOVIES; + strPath += "movies"; + } + else if (media == MediaTypeTvShow) + { + idContent = VideoDbContentType::TVSHOWS; + strPath += "tvshows"; + } + else if (media == MediaTypeMusicVideo) + { + idContent = VideoDbContentType::MUSICVIDEOS; + strPath += "musicvideos"; + } + strPath += "/genres/"; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetGenresNav(strPath, items, idContent)) + return InternalError; + + /* need to set strTitle in each item*/ + for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) + items[i]->GetVideoInfoTag()->m_strTitle = items[i]->GetLabel(); + + HandleFileItemList("genreid", false, "genres", items, parameterObject, result); + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetTags(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string media = parameterObject["type"].asString(); + StringUtils::ToLower(media); + VideoDbContentType idContent = VideoDbContentType::UNKNOWN; + + std::string strPath = "videodb://"; + /* select which video content to get tags from*/ + if (media == MediaTypeMovie) + { + idContent = VideoDbContentType::MOVIES; + strPath += "movies"; + } + else if (media == MediaTypeTvShow) + { + idContent = VideoDbContentType::TVSHOWS; + strPath += "tvshows"; + } + else if (media == MediaTypeMusicVideo) + { + idContent = VideoDbContentType::MUSICVIDEOS; + strPath += "musicvideos"; + } + strPath += "/tags/"; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemList items; + if (!videodatabase.GetTagsNav(strPath, items, idContent)) + return InternalError; + + /* need to set strTitle in each item*/ + for (int i = 0; i < items.Size(); i++) + items[i]->GetVideoInfoTag()->m_strTitle = items[i]->GetLabel(); + + HandleFileItemList("tagid", false, "tags", items, parameterObject, result); + return OK; +} + +namespace +{ + const std::map<std::string, std::string> mediaIDTypes = { + {"episodeid", MediaTypeEpisode}, + {"tvshowid", MediaTypeTvShow}, + {"seasonid", MediaTypeSeason}, + {"movieid", MediaTypeMovie}, + {"setid", MediaTypeVideoCollection}, + {"musicvideoid", MediaTypeMusicVideo}, + }; +} + +JSONRPC_STATUS CVideoLibrary::GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + for (const auto& mediaIDType : mediaIDTypes) { + if (parameterObject["item"].isMember(mediaIDType.first)) + { + mediaType = mediaIDType.second; + mediaID = parameterObject["item"][mediaIDType.first].asInteger32(); + break; + } + } + if (mediaID == -1) + return InternalError; + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVariant availablearttypes = CVariant(CVariant::VariantTypeArray); + for (const auto& artType : videodatabase.GetAvailableArtTypesForItem(mediaID, mediaType)) + { + availablearttypes.append(artType); + } + result = CVariant(CVariant::VariantTypeObject); + result["availablearttypes"] = availablearttypes; + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result) +{ + std::string mediaType; + int mediaID = -1; + for (const auto& mediaIDType : mediaIDTypes) { + if (parameterObject["item"].isMember(mediaIDType.first)) + { + mediaType = mediaIDType.second; + mediaID = parameterObject["item"][mediaIDType.first].asInteger32(); + break; + } + } + if (mediaID == -1) + return InternalError; + + std::string artType = parameterObject["arttype"].asString(); + StringUtils::ToLower(artType); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVariant availableart = CVariant(CVariant::VariantTypeArray); + for (const auto& artentry : videodatabase.GetAvailableArtForItem(mediaID, mediaType, artType)) + { + CVariant item = CVariant(CVariant::VariantTypeObject); + item["url"] = CTextureUtils::GetWrappedImageURL(artentry.m_url); + item["arttype"] = artentry.m_aspect; + if (!artentry.m_preview.empty()) + item["previewurl"] = CTextureUtils::GetWrappedImageURL(artentry.m_preview); + availableart.append(item); + } + result = CVariant(CVariant::VariantTypeObject); + result["availableart"] = availableart; + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::SetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["movieid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.UpdateDetailsForMovie(id, infos, artwork, updatedDetails) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeMovie, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(infos, artwork); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["setid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetSetInfo(id, infos); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.SetDetailsForMovieSet(infos, artwork, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, "set", removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["tvshowid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::map<int, std::map<std::string, std::string> > seasonArt; + videodatabase.GetTvShowSeasonArt(infos.m_iDbId, seasonArt); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + // we need to manually remove tags/taglinks for now because they aren't replaced + // due to scrapers not supporting them + videodatabase.RemoveTagsFromItem(id, MediaTypeTvShow); + + if (!videodatabase.UpdateDetailsForTvShow(id, infos, artwork, seasonArt)) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeTvShow, removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["seasonid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetSeasonInfo(id, infos); + if (infos.m_iDbId <= 0 || infos.m_iIdShow <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + if (ParameterNotNull(parameterObject, "title")) + infos.SetSortTitle(parameterObject["title"].asString()); + + if (videodatabase.SetDetailsForSeason(infos, artwork, infos.m_iIdShow, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeSeason, removedArtwork)) + return InternalError; + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetEpisodeInfo("", infos, id); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + int tvshowid = videodatabase.GetTvShowForEpisode(id); + if (tvshowid <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + if (videodatabase.SetDetailsForEpisode(infos, artwork, tvshowid, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeEpisode, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount() || lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::SetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["musicvideoid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + videodatabase.GetMusicVideoInfo("", infos, id); + if (infos.m_iDbId <= 0) + { + videodatabase.Close(); + return InvalidParams; + } + + // get artwork + std::map<std::string, std::string> artwork; + videodatabase.GetArtForItem(infos.m_iDbId, infos.m_type, artwork); + + int playcount = infos.GetPlayCount(); + CDateTime lastPlayed = infos.m_lastPlayed; + + std::set<std::string> removedArtwork; + std::set<std::string> updatedDetails; + UpdateVideoTag(parameterObject, infos, artwork, removedArtwork, updatedDetails); + + // we need to manually remove tags/taglinks for now because they aren't replaced + // due to scrapers not supporting them + videodatabase.RemoveTagsFromItem(id, MediaTypeMusicVideo); + + if (videodatabase.SetDetailsForMusicVideo(infos, artwork, id) <= 0) + return InternalError; + + if (!videodatabase.RemoveArtForItem(infos.m_iDbId, MediaTypeMusicVideo, removedArtwork)) + return InternalError; + + if (playcount != infos.GetPlayCount()|| lastPlayed != infos.m_lastPlayed) + { + // restore original playcount or the new one won't be announced + int newPlaycount = infos.GetPlayCount(); + infos.SetPlayCount(playcount); + videodatabase.SetPlayCount(CFileItem(infos), newPlaycount, infos.m_lastPlayed); + } + + UpdateResumePoint(parameterObject, infos, videodatabase); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["movieid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMovieInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["tvshowid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CFileItemPtr item(new CFileItem()); + CVideoInfoTag infos; + if (!videodatabase.GetTvShowInfo("", infos, id, item.get()) || infos.m_iDbId <= 0) + return InvalidParams; + + item->SetFromVideoInfoTag(infos); + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + bool refreshEpisodes = parameterObject["refreshepisodes"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(item, ignoreNfo, true, refreshEpisodes, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = (int)parameterObject["episodeid"].asInteger(); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetEpisodeInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + CFileItemPtr item = CFileItemPtr(new CFileItem(infos)); + // We need to set the correct base path to get the valid fanart + int tvshowid = infos.m_iIdShow; + if (tvshowid <= 0) + tvshowid = videodatabase.GetTvShowForEpisode(id); + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(item, ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RefreshMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + int id = static_cast<int>(parameterObject["musicvideoid"].asInteger()); + + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + CVideoInfoTag infos; + if (!videodatabase.GetMusicVideoInfo("", infos, id) || infos.m_iDbId <= 0) + return InvalidParams; + + bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); + std::string searchTitle = parameterObject["title"].asString(); + CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::RemoveMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::RemoveMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + return RemoveVideo(parameterObject); +} + +JSONRPC_STATUS CVideoLibrary::Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd = + StringUtils::Format("updatelibrary(video, {}, {})", StringUtils::Paramify(directory), + parameterObject["showdialogs"].asBoolean() ? "true" : "false"); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string cmd; + if (parameterObject["options"].isMember("path")) + cmd = StringUtils::Format("exportlibrary2(video, singlefile, {})", + StringUtils::Paramify(parameterObject["options"]["path"].asString())); + else + { + cmd = "exportlibrary2(video, separate, dummy"; + if (parameterObject["options"]["images"].isBoolean() && + parameterObject["options"]["images"].asBoolean() == true) + cmd += ", artwork"; + if (parameterObject["options"]["overwrite"].isBoolean() && + parameterObject["options"]["overwrite"].asBoolean() == true) + cmd += ", overwrite"; + if (parameterObject["options"]["actorthumbs"].isBoolean() && + parameterObject["options"]["actorthumbs"].asBoolean() == true) + cmd += ", actorthumbs"; + cmd += ")"; + } + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +JSONRPC_STATUS CVideoLibrary::Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::string directory = parameterObject["directory"].asString(); + std::string cmd; + if (parameterObject["content"].empty()) + cmd = StringUtils::Format("cleanlibrary(video, {0}, {1})", + parameterObject["showdialogs"].asBoolean() ? "true" : "false", + StringUtils::Paramify(directory)); + else + cmd = StringUtils::Format("cleanlibrary({0}, {1}, {2})", parameterObject["content"].asString(), + parameterObject["showdialogs"].asBoolean() ? "true" : "false", + StringUtils::Paramify(directory)); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, cmd); + return ACK; +} + +bool CVideoLibrary::FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject /* = CVariant(CVariant::VariantTypeArray) */) +{ + CVideoDatabase videodatabase; + if (strFilename.empty()) + return false; + + bool filled = false; + if (videodatabase.Open()) + { + CVideoInfoTag details; + if (videodatabase.LoadVideoInfo(strFilename, details)) + { + item->SetFromVideoInfoTag(details); + item->SetDynPath(strFilename); + filled = true; + } + } + + if (item->GetLabel().empty()) + { + item->SetLabel(CUtil::GetTitleFromPath(strFilename, false)); + if (item->GetLabel().empty()) + item->SetLabel(URIUtils::GetFileName(strFilename)); + } + + return filled; +} + +bool CVideoLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemList &list) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return false; + + std::string file = parameterObject["file"].asString(); + int movieID = (int)parameterObject["movieid"].asInteger(-1); + int episodeID = (int)parameterObject["episodeid"].asInteger(-1); + int musicVideoID = (int)parameterObject["musicvideoid"].asInteger(-1); + int recordingID = static_cast<int>(parameterObject["recordingid"].asInteger()); + + bool success = false; + CFileItemPtr fileItem(new CFileItem()); + if (FillFileItem(file, fileItem)) + { + success = true; + list.Add(fileItem); + } + + if (movieID > 0) + { + CVideoInfoTag details; + videodatabase.GetMovieInfo("", details, movieID); + if (!details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (episodeID > 0) + { + CVideoInfoTag details; + if (videodatabase.GetEpisodeInfo("", details, episodeID) && !details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (musicVideoID > 0) + { + CVideoInfoTag details; + videodatabase.GetMusicVideoInfo("", details, musicVideoID); + if (!details.IsEmpty()) + { + list.Add(CFileItemPtr(new CFileItem(details))); + success = true; + } + } + if (recordingID > 0) + { + std::shared_ptr<CFileItem> recordingFileItem = + CPVROperations::GetRecordingFileItem(recordingID); + + if (recordingFileItem) + { + list.Add(recordingFileItem); + success = true; + } + } + + return success; +} + +int CVideoLibrary::RequiresAdditionalDetails(const MediaType& mediaType, const CVariant ¶meterObject) +{ + if (mediaType != MediaTypeMovie && mediaType != MediaTypeTvShow && mediaType != MediaTypeEpisode && mediaType != MediaTypeMusicVideo) + return VideoDbDetailsNone; + + return GetDetailsFromJsonParameters(parameterObject); +} + +int CVideoLibrary::GetDetailsFromJsonParameters(const CVariant& parameterObject) +{ + const CVariant& properties = parameterObject["properties"]; + int details = VideoDbDetailsNone; + for (CVariant::const_iterator_array itr = properties.begin_array(); itr != properties.end_array(); + ++itr) + { + std::string propertyValue = itr->asString(); + if (propertyValue == "cast") + details = details | VideoDbDetailsCast; + else if (propertyValue == "ratings") + details = details | VideoDbDetailsRating; + else if (propertyValue == "uniqueid") + details = details | VideoDbDetailsUniqueID; + else if (propertyValue == "showlink") + details = details | VideoDbDetailsShowLink; + else if (propertyValue == "streamdetails") + details = details | VideoDbDetailsStream; + else if (propertyValue == "tag") + details = details | VideoDbDetailsTag; + } + return details; +} + +JSONRPC_STATUS CVideoLibrary::HandleItems(const char *idProperty, const char *resultName, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool limit /* = true */) +{ + int size = items.Size(); + if (!limit && items.HasProperty("total") && items.GetProperty("total").asInteger() > size) + size = (int)items.GetProperty("total").asInteger(); + HandleFileItemList(idProperty, true, resultName, items, parameterObject, result, size, limit); + + return OK; +} + +JSONRPC_STATUS CVideoLibrary::RemoveVideo(const CVariant ¶meterObject) +{ + CVideoDatabase videodatabase; + if (!videodatabase.Open()) + return InternalError; + + if (parameterObject.isMember("movieid")) + videodatabase.DeleteMovie((int)parameterObject["movieid"].asInteger()); + else if (parameterObject.isMember("tvshowid")) + videodatabase.DeleteTvShow((int)parameterObject["tvshowid"].asInteger()); + else if (parameterObject.isMember("episodeid")) + videodatabase.DeleteEpisode((int)parameterObject["episodeid"].asInteger()); + else if (parameterObject.isMember("musicvideoid")) + videodatabase.DeleteMusicVideo((int)parameterObject["musicvideoid"].asInteger()); + + CJSONRPCUtils::NotifyItemUpdated(); + return ACK; +} + +void CVideoLibrary::UpdateResumePoint(const CVariant ¶meterObject, CVideoInfoTag &details, CVideoDatabase &videodatabase) +{ + if (!parameterObject["resume"].isNull()) + { + double position = (double)parameterObject["resume"]["position"].asDouble(); + if (position == 0.0) + videodatabase.ClearBookMarksOfFile(details.m_strFileNameAndPath, CBookmark::RESUME); + else + { + CBookmark bookmark; + double total = (double)parameterObject["resume"]["total"].asDouble(); + if (total <= 0.0 && !videodatabase.GetResumeBookMark(details.m_strFileNameAndPath, bookmark)) + bookmark.totalTimeInSeconds = details.m_streamDetails.GetVideoDuration(); + else + bookmark.totalTimeInSeconds = total; + + bookmark.timeInSeconds = position; + videodatabase.AddBookMarkToFile(details.m_strFileNameAndPath, bookmark, CBookmark::RESUME); + } + } +} + +void CVideoLibrary::UpdateVideoTagField(const CVariant& parameterObject, const std::string& fieldName, std::vector<std::string>& fieldValue, std::set<std::string>& updatedDetails) +{ + if (ParameterNotNull(parameterObject, fieldName)) + { + CopyStringArray(parameterObject[fieldName], fieldValue); + updatedDetails.insert(fieldName); + } +} + +void CVideoLibrary::UpdateVideoTag(const CVariant ¶meterObject, CVideoInfoTag& details, std::map<std::string, std::string> &artwork, std::set<std::string> &removedArtwork, std::set<std::string> &updatedDetails) +{ + if (ParameterNotNull(parameterObject, "title")) + details.SetTitle(parameterObject["title"].asString()); + if (ParameterNotNull(parameterObject, "playcount")) + details.SetPlayCount(static_cast<int>(parameterObject["playcount"].asInteger())); + if (ParameterNotNull(parameterObject, "runtime")) + details.SetDuration(static_cast<int>(parameterObject["runtime"].asInteger())); + + std::vector<std::string> director(details.m_director); + UpdateVideoTagField(parameterObject, "director", director, updatedDetails); + details.SetDirector(director); + + std::vector<std::string> studio(details.m_studio); + UpdateVideoTagField(parameterObject, "studio", studio, updatedDetails); + details.SetStudio(studio); + + if (ParameterNotNull(parameterObject, "plot")) + details.SetPlot(parameterObject["plot"].asString()); + if (ParameterNotNull(parameterObject, "album")) + details.SetAlbum(parameterObject["album"].asString()); + + std::vector<std::string> artist(details.m_artist); + UpdateVideoTagField(parameterObject, "artist", artist, updatedDetails); + details.SetArtist(artist); + + std::vector<std::string> genre(details.m_genre); + UpdateVideoTagField(parameterObject, "genre", genre, updatedDetails); + details.SetGenre(genre); + + if (ParameterNotNull(parameterObject, "track")) + details.m_iTrack = (int)parameterObject["track"].asInteger(); + if (ParameterNotNull(parameterObject, "rating")) + { + details.SetRating(parameterObject["rating"].asFloat()); + updatedDetails.insert("ratings"); + } + if (ParameterNotNull(parameterObject, "votes")) + { + details.SetVotes(StringUtils::ReturnDigits(parameterObject["votes"].asString())); + updatedDetails.insert("ratings"); //Votes and ratings both need updates now, this will trigger those + } + if (ParameterNotNull(parameterObject, "ratings")) + { + CVariant ratings = parameterObject["ratings"]; + for (CVariant::const_iterator_map rIt = ratings.begin_map(); rIt != ratings.end_map(); ++rIt) + { + if (rIt->second.isObject() && ParameterNotNull(rIt->second, "rating")) + { + const auto& rating = rIt->second; + if (ParameterNotNull(rating, "votes")) + { + details.SetRating(rating["rating"].asFloat(), + static_cast<int>(rating["votes"].asInteger()), + rIt->first, + (ParameterNotNull(rating, "default") && rating["default"].asBoolean())); + } + else + details.SetRating(rating["rating"].asFloat(), rIt->first, (ParameterNotNull(rating, "default") && rating["default"].asBoolean())); + + updatedDetails.insert("ratings"); + } + else if (rIt->second.isNull()) + { + details.RemoveRating(rIt->first); + updatedDetails.insert("ratings"); + } + } + } + if (ParameterNotNull(parameterObject, "userrating")) + details.m_iUserRating = static_cast<int>(parameterObject["userrating"].asInteger()); + if (ParameterNotNull(parameterObject, "mpaa")) + details.SetMPAARating(parameterObject["mpaa"].asString()); + if (ParameterNotNull(parameterObject, "imdbnumber")) + { + details.SetUniqueID(parameterObject["imdbnumber"].asString()); + updatedDetails.insert("uniqueid"); + } + if (ParameterNotNull(parameterObject, "uniqueid")) + { + CVariant uniqueids = parameterObject["uniqueid"]; + for (CVariant::const_iterator_map idIt = uniqueids.begin_map(); idIt != uniqueids.end_map(); + ++idIt) + { + if (idIt->second.isString() && !idIt->second.asString().empty()) + { + details.SetUniqueID(idIt->second.asString(), idIt->first); + updatedDetails.insert("uniqueid"); + } + else if (idIt->second.isNull() && idIt->first != details.GetDefaultUniqueID()) + { + details.RemoveUniqueID(idIt->first); + updatedDetails.insert("uniqueid"); + } + } + } + if (ParameterNotNull(parameterObject, "premiered")) + { + CDateTime premiered; + SetFromDBDate(parameterObject["premiered"], premiered); + details.SetPremiered(premiered); + } + else if (ParameterNotNull(parameterObject, "year")) + details.SetYear((int)parameterObject["year"].asInteger()); + if (ParameterNotNull(parameterObject, "lastplayed")) + SetFromDBDateTime(parameterObject["lastplayed"], details.m_lastPlayed); + if (ParameterNotNull(parameterObject, "firstaired")) + SetFromDBDate(parameterObject["firstaired"], details.m_firstAired); + if (ParameterNotNull(parameterObject, "productioncode")) + details.SetProductionCode(parameterObject["productioncode"].asString()); + if (ParameterNotNull(parameterObject, "season")) + details.m_iSeason = (int)parameterObject["season"].asInteger(); + if (ParameterNotNull(parameterObject, "episode")) + details.m_iEpisode = (int)parameterObject["episode"].asInteger(); + if (ParameterNotNull(parameterObject, "originaltitle")) + details.SetOriginalTitle(parameterObject["originaltitle"].asString()); + if (ParameterNotNull(parameterObject, "trailer")) + details.SetTrailer(parameterObject["trailer"].asString()); + if (ParameterNotNull(parameterObject, "tagline")) + details.SetTagLine(parameterObject["tagline"].asString()); + if (ParameterNotNull(parameterObject, "status")) + details.SetStatus(parameterObject["status"].asString()); + if (ParameterNotNull(parameterObject, "plotoutline")) + details.SetPlotOutline(parameterObject["plotoutline"].asString()); + + std::vector<std::string> credits(details.m_writingCredits); + UpdateVideoTagField(parameterObject, "writer", credits, updatedDetails); + details.SetWritingCredits(credits); + + std::vector<std::string> country(details.m_country); + UpdateVideoTagField(parameterObject, "country", country, updatedDetails); + details.SetCountry(country); + + if (ParameterNotNull(parameterObject, "top250")) + details.m_iTop250 = (int)parameterObject["top250"].asInteger(); + if (ParameterNotNull(parameterObject, "sorttitle")) + details.SetSortTitle(parameterObject["sorttitle"].asString()); + if (ParameterNotNull(parameterObject, "episodeguide")) + details.SetEpisodeGuide(parameterObject["episodeguide"].asString()); + if (ParameterNotNull(parameterObject, "set")) + { + details.SetSet(parameterObject["set"].asString()); + updatedDetails.insert("set"); + } + + std::vector<std::string> showLink(details.m_showLink); + UpdateVideoTagField(parameterObject, "showlink", showLink, updatedDetails); + details.SetShowLink(showLink); + + std::vector<std::string> tags(details.m_tags); + UpdateVideoTagField(parameterObject, "tag", tags, updatedDetails); + details.SetTags(tags); + + if (ParameterNotNull(parameterObject, "thumbnail")) + { + std::string value = parameterObject["thumbnail"].asString(); + artwork["thumb"] = StringUtils::Trim(value); + updatedDetails.insert("art.altered"); + } + if (ParameterNotNull(parameterObject, "fanart")) + { + std::string value = parameterObject["fanart"].asString(); + artwork["fanart"] = StringUtils::Trim(value); + updatedDetails.insert("art.altered"); + } + + if (ParameterNotNull(parameterObject, "art")) + { + CVariant art = parameterObject["art"]; + for (CVariant::const_iterator_map artIt = art.begin_map(); artIt != art.end_map(); ++artIt) + { + if (artIt->second.isString() && !artIt->second.asString().empty()) + { + artwork[artIt->first] = CTextureUtils::UnwrapImageURL(artIt->second.asString()); + updatedDetails.insert("art.altered"); + } + else if (artIt->second.isNull()) + { + artwork.erase(artIt->first); + removedArtwork.insert(artIt->first); + } + } + } + + if (ParameterNotNull(parameterObject, "dateadded")) + { + SetFromDBDateTime(parameterObject["dateadded"], details.m_dateAdded); + updatedDetails.insert("dateadded"); + } +} diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.h b/xbmc/interfaces/json-rpc/VideoLibrary.h new file mode 100644 index 0000000..f91c4c8 --- /dev/null +++ b/xbmc/interfaces/json-rpc/VideoLibrary.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2016-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "FileItemHandler.h" +#include "JSONRPC.h" +#include "utils/DatabaseUtils.h" + +#include <memory> +#include <string> +#include <vector> + +class CFileItem; +class CFileItemList; +class CVideoDatabase; +class CVariant; + +namespace JSONRPC +{ + class CVideoLibrary : public CFileItemHandler + { + public: + static JSONRPC_STATUS GetMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieSets(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSeasons(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetRecentlyAddedMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedEpisodes(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetRecentlyAddedMusicVideos(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetInProgressTVShows(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS GetGenres(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetTags(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetAvailableArtTypes(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + static JSONRPC_STATUS GetAvailableArt(const std::string& method, ITransportLayer* transport, IClient* client, const CVariant& parameterObject, CVariant& result); + + static JSONRPC_STATUS SetMovieDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMovieSetDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetTVShowDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetSeasonDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetEpisodeDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS SetMusicVideoDetails(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS RefreshMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RefreshMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS RemoveMovie(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveTVShow(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveEpisode(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS RemoveMusicVideo(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static JSONRPC_STATUS Scan(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Export(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS Clean(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + + static bool FillFileItem( + const std::string& strFilename, + std::shared_ptr<CFileItem>& item, + const CVariant& parameterObject = CVariant(CVariant::VariantTypeArray)); + static bool FillFileItemList(const CVariant ¶meterObject, CFileItemList &list); + static void UpdateResumePoint(const CVariant ¶meterObject, CVideoInfoTag &details, CVideoDatabase &videodatabase); + + /*! \brief Provided the JSON-RPC parameter object compute the VideoDbDetails mask + * \param parameterObject the JSON parameter mask + * \return the mask value for the requested properties + */ + static int GetDetailsFromJsonParameters(const CVariant& parameterObject); + + private: + static int RequiresAdditionalDetails(const MediaType& mediaType, const CVariant ¶meterObject); + static JSONRPC_STATUS HandleItems(const char *idProperty, const char *resultName, CFileItemList &items, const CVariant ¶meterObject, CVariant &result, bool limit = true); + static JSONRPC_STATUS RemoveVideo(const CVariant ¶meterObject); + static void UpdateVideoTag(const CVariant ¶meterObject, CVideoInfoTag &details, std::map<std::string, std::string> &artwork, std::set<std::string> &removedArtwork, std::set<std::string>& updatedDetails); + static void UpdateVideoTagField(const CVariant& parameterObject, const std::string& fieldName, std::vector<std::string>& fieldValue, std::set<std::string>& updatedDetails); + }; +} diff --git a/xbmc/interfaces/json-rpc/XBMCOperations.cpp b/xbmc/interfaces/json-rpc/XBMCOperations.cpp new file mode 100644 index 0000000..67edc29 --- /dev/null +++ b/xbmc/interfaces/json-rpc/XBMCOperations.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 "XBMCOperations.h" + +#include "ServiceBroker.h" +#include "messaging/ApplicationMessenger.h" +#include "powermanagement/PowerManager.h" +#include "utils/Variant.h" + +using namespace JSONRPC; + +JSONRPC_STATUS CXBMCOperations::GetInfoLabels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<std::string> info; + + for (unsigned int i = 0; i < parameterObject["labels"].size(); i++) + { + std::string field = parameterObject["labels"][i].asString(); + StringUtils::ToLower(field); + + info.push_back(parameterObject["labels"][i].asString()); + } + + if (!info.empty()) + { + std::vector<std::string> infoLabels; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_INFOLABEL, -1, -1, + static_cast<void*>(&infoLabels), "", info); + + for (unsigned int i = 0; i < info.size(); i++) + { + if (i >= infoLabels.size()) + break; + result[info[i]] = infoLabels[i]; + } + } + + return OK; +} + +JSONRPC_STATUS CXBMCOperations::GetInfoBooleans(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) +{ + std::vector<std::string> info; + + bool CanControlPower = (client->GetPermissionFlags() & ControlPower) > 0; + + for (unsigned int i = 0; i < parameterObject["booleans"].size(); i++) + { + std::string field = parameterObject["booleans"][i].asString(); + StringUtils::ToLower(field); + + // Need to override power management of whats in infomanager since jsonrpc + // have a security layer aswell. + if (field == "system.canshutdown") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanPowerdown() && CanControlPower); + else if (field == "system.canpowerdown") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanPowerdown() && CanControlPower); + else if (field == "system.cansuspend") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanSuspend() && CanControlPower); + else if (field == "system.canhibernate") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanHibernate() && CanControlPower); + else if (field == "system.canreboot") + result[parameterObject["booleans"][i].asString()] = (CServiceBroker::GetPowerManager().CanReboot() && CanControlPower); + else + info.push_back(parameterObject["booleans"][i].asString()); + } + + if (!info.empty()) + { + std::vector<bool> infoLabels; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_INFOBOOL, -1, -1, + static_cast<void*>(&infoLabels), "", info); + for (unsigned int i = 0; i < info.size(); i++) + { + if (i >= infoLabels.size()) + break; + result[info[i].c_str()] = CVariant(infoLabels[i]); + } + } + + return OK; +} diff --git a/xbmc/interfaces/json-rpc/XBMCOperations.h b/xbmc/interfaces/json-rpc/XBMCOperations.h new file mode 100644 index 0000000..af56172 --- /dev/null +++ b/xbmc/interfaces/json-rpc/XBMCOperations.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "JSONRPC.h" + +class CVariant; + +namespace JSONRPC +{ + class CXBMCOperations + { + public: + static JSONRPC_STATUS GetInfoLabels(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + static JSONRPC_STATUS GetInfoBooleans(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result); + }; +} diff --git a/xbmc/interfaces/json-rpc/schema/CMakeLists.txt b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt new file mode 100644 index 0000000..a4d5583 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt @@ -0,0 +1,30 @@ +set(JSON_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/version.txt + ${CMAKE_CURRENT_SOURCE_DIR}/license.txt + ${CMAKE_CURRENT_SOURCE_DIR}/methods.json + ${CMAKE_CURRENT_SOURCE_DIR}/types.json + ${CMAKE_CURRENT_SOURCE_DIR}/notifications.json) + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescription.h + COMMAND JsonSchemaBuilder::JsonSchemaBuilder ${JSON_SRCS} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + DEPENDS ${JSON_SRCS} + COMMENT "Generating ServiceDescription.h") + +add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/addons/xbmc.json/addon.xml + COMMAND ${CMAKE_COMMAND} + -DCMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DCORE_BINARY_DIR=${CMAKE_BINARY_DIR} + -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} + -P ${CMAKE_CURRENT_SOURCE_DIR}/GenerateAddonXml.cmake + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + DEPENDS ${JSON_SRCS} ${CMAKE_SOURCE_DIR}/addons/xbmc.json/addon.xml.in + COMMENT "Generating xbmc.json/addon.xml") + +add_custom_target(generate_json_header ALL + DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescription.h + ${CMAKE_BINARY_DIR}/addons/xbmc.json/addon.xml) +set_target_properties(generate_json_header PROPERTIES FOLDER "Build Utilities") + +if(BOOTSTRAP_IN_TREE) + add_dependencies(generate_json_header JsonSchemaBuilder::JsonSchemaBuilder) +endif() diff --git a/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake b/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake new file mode 100644 index 0000000..7f0817b --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/GenerateAddonXml.cmake @@ -0,0 +1,6 @@ +include(${CMAKE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake) +core_find_versions() + +file(REMOVE ${CORE_BINARY_DIR}/addons/xbmc.json/addon.xml) +configure_file(${CMAKE_SOURCE_DIR}/addons/xbmc.json/addon.xml.in + ${CORE_BINARY_DIR}/addons/xbmc.json/addon.xml @ONLY) diff --git a/xbmc/interfaces/json-rpc/schema/license.txt b/xbmc/interfaces/json-rpc/schema/license.txt new file mode 100644 index 0000000..97af310 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/license.txt @@ -0,0 +1,7 @@ +/* + * 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. + */ diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json new file mode 100644 index 0000000..4d396cf --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -0,0 +1,2941 @@ +{ + "JSONRPC.Introspect": { + "type": "method", + "description": "Enumerates all actions and descriptions", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "getdescriptions", "type": "boolean", "default": true }, + { "name": "getmetadata", "type": "boolean", "default": false }, + { "name": "filterbytransport", "type": "boolean", "default": true }, + { "name": "filter", "type": "object", + "properties": { + "id": { "type": "string", "required": true, "description": "Name of a namespace, method or type" }, + "type": { "type": "string", "required": true, "enum": [ "method", "namespace", "type", "notification" ], "description": "Type of the given name" }, + "getreferences": { "type": "boolean", "default": true, "description": "Whether or not to print the schema for referenced types" } + } + } + ], + "returns": "object" + }, + "JSONRPC.Version": { + "type": "method", + "description": "Retrieve the JSON-RPC protocol version.", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "version": { "type": "object", "required": true, + "properties": { + "major": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on backwards incompatible changes to the API definition" }, + "minor": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on backwards compatible additions/changes to the API definition" }, + "patch": { "type": "integer", "minimum": 0, "required": true, "description": "Bumped on any changes to the internal implementation but not to the API definition" } + } + } + } + } + }, + "JSONRPC.Permission": { + "type": "method", + "description": "Retrieve the clients permissions", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "ReadData": { "type": "boolean", "required": true }, + "ControlPlayback": { "type": "boolean", "required": true }, + "ControlNotify": { "type": "boolean", "required": true }, + "ControlPower": { "type": "boolean", "required": true }, + "UpdateData": { "type": "boolean", "required": true }, + "RemoveData": { "type": "boolean", "required": true }, + "Navigate": { "type": "boolean", "required": true }, + "WriteFile": { "type": "boolean", "required": true }, + "ControlSystem": { "type": "boolean", "required": true }, + "ControlGUI": { "type": "boolean", "required": true }, + "ManageAddon": { "type": "boolean", "required": true }, + "ExecuteAddon": { "type": "boolean", "required": true }, + "ControlPVR": { "type": "boolean", "required": true } + } + } + }, + "JSONRPC.Ping": { + "type": "method", + "description": "Ping responder", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": "string" + }, + "JSONRPC.GetConfiguration": { + "type": "method", + "description": "Get client-specific configurations", + "transport": "Announcing", + "permission": "ReadData", + "params": [], + "returns": { "$ref": "Configuration" } + }, + "JSONRPC.SetConfiguration": { + "type": "method", + "description": "Change the client-specific configuration", + "transport": "Announcing", + "permission": "ControlNotify", + "params": [ + { "name": "notifications", "type": "object", + "properties": { + "Player": { "$ref": "Optional.Boolean" }, + "Playlist": { "$ref": "Optional.Boolean" }, + "GUI": { "$ref": "Optional.Boolean" }, + "System": { "$ref": "Optional.Boolean" }, + "AudioLibrary": { "$ref": "Optional.Boolean" }, + "VideoLibrary": { "$ref": "Optional.Boolean" }, + "Application": { "$ref": "Optional.Boolean" }, + "Input": { "$ref": "Optional.Boolean" }, + "Other": { "$ref": "Optional.Boolean" } + } + } + ], + "returns": { "$ref": "Configuration" } + }, + "JSONRPC.NotifyAll": { + "type": "method", + "description": "Notify all other connected clients", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "message", "type": "string", "required": true }, + { "name": "data", "type": "any", "default": null } + ], + "returns": "any" + }, + "Player.Open": { + "type": "method", + "description": "Start playback of either the playlist with the given ID, a slideshow with the pictures from the given directory or a single file or an item from the database.", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "item", + "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "position": { "$ref": "Playlist.Position", "default": 0 } + } + }, + { "$ref": "Playlist.Item", "required": true }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true }, + "random": { "type": "boolean", "default": true, "description": "Deprecated, use the shuffled property of the options parameter instead" }, + "recursive": { "type": "boolean", "default": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "partymode": { "type": [ + { "type": "string", "required": true, "enum": [ "music", "video" ] }, + { "type": "string", "required": true, "minLength": 5, "description": "Path to a smartplaylist (*.xsp) file" } + ] + } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "broadcastid": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "channelid": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "recordingid": { "$ref": "Library.Id", "required": true } + } + } + ] + }, + { "name": "options", "type": "object", "additionalProperties": false, + "properties": { + "playername": { "type": [ + "null", + { "type": "string", "enum": [ "default" ], "required": true }, + { "type": "string", "minLength": 1, "required": true, "description": "name of player" } + ], + "default": null + }, + "shuffled": { "$ref": "Optional.Boolean" }, + "repeat": { "type": [ "null", { "$ref": "Player.Repeat", "required": true } ], "default": null }, + "resume": { "type": [ + { "type": "boolean", "required": true, "description": "Whether to resume from the resume point or not" }, + { "$ref": "Player.Position.Percentage", "required": true, "description": "Percentage value to start from" }, + { "$ref": "Player.Position.Time", "required": true, "description": "Time to start from" } + ], + "default": false + } + } + } + ], + "returns": "string" + }, + "Player.GetActivePlayers": { + "type": "method", + "description": "Returns all active players", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "playerid": { "$ref": "Player.Id", "required": true }, + "type": { "$ref": "Player.Type", "required": true }, + "playertype": { "type": "string", "enum": [ "internal", "external", "remote" ], "required": true } + } + } + } + }, + "Player.GetPlayers": { + "type": "method", + "description": "Get a list of available players", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "media", "type": "string", "enum": [ "all", "video", "audio" ], "default": "all" } + ], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "name": { "$ref": "Global.String.NotEmpty", "required": true }, + "type": { "type": "string", "enum": [ "internal", "external", "remote" ], "required": true }, + "playsvideo": { "type": "boolean", "required": true }, + "playsaudio": { "type": "boolean", "required": true } + } + } + } + }, + "Player.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Player.Property.Name" } } + ], + "returns": { "$ref": "Player.Property.Value", "required": true } + }, + "Player.GetItem": { + "type": "method", + "description": "Retrieves the currently played item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "properties", "$ref": "List.Fields.All" } + ], + "returns": { "type": "object", + "properties": { + "item": { "$ref": "List.Item.All", "required": true } + } + } + }, + "Player.PlayPause": { + "type": "method", + "description": "Pauses or unpause playback and returns the new state", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "play", "$ref": "Global.Toggle", "default": "toggle" } + ], + "returns": { "$ref": "Player.Speed" } + }, + "Player.Stop": { + "type": "method", + "description": "Stops playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true } + ], + "returns": "string" + }, + "Player.GetAudioDelay": { + "type": "method", + "description": "Get the audio delay for the current playback", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "The offset value used in the current playback.", + "required": true + } + } + } + }, + "Player.SetAudioDelay": { + "type": "method", + "description": "Set the audio delay for the current playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { + "name": "playerid", + "$ref": "Player.Id", + "required": true + }, + { + "name": "offset", + "type": [ + { + "type": "number", + "description": "The value should be a multiple of 0.025 in a range of +/-10 (the default range can be overriden by advancedsettings.xml).", + "required": true + }, + { + "$ref": "Global.IncrementDecrement", + "required": true + } + ], + "required": true + } + ], + "returns": { + "type": "object", + "properties": { + "offset": { + "type": "number", + "description": "The offset value used in the current playback.", + "required": true + } + } + } + }, + "Player.SetSpeed": { + "type": "method", + "description": "Set the speed of the current playback", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "speed", "type": [ + { "type": "integer", "required": true, "enum": [ -32, -16, -8, -4, -2, -1, 0, 1, 2, 4, 8, 16, 32 ] }, + { "$ref": "Global.IncrementDecrement", "required": true } + ], + "required": true + } + ], + "returns": { "$ref": "Player.Speed" } + }, + "Player.Seek": { + "type": "method", + "description": "Seek through the playing item", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "value", "required": true, "type": [ + { "type": "object", "properties": { "percentage": { "$ref": "Player.Position.Percentage", "required": true, "description": "Percentage value to seek to" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "time": { "$ref": "Player.Position.Time", "required": true, "description": "Time to seek to" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "step": { "type": "string", "enum": [ "smallforward", "smallbackward", "bigforward", "bigbackward" ], "required": true, "description": "Seek by predefined jumps" } }, "additionalProperties": false, "required": true }, + { "type": "object", "properties": { "seconds": { "type": "integer", "required": true, "description": "Seek by the given number of seconds" } }, "additionalProperties": false, "required": true } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "percentage": { "$ref": "Player.Position.Percentage" }, + "time": { "$ref": "Global.Time" }, + "totaltime": { "$ref": "Global.Time" } + } + } + }, + "Player.Move": { + "type": "method", + "description": "If picture is zoomed move viewport left/right/up/down otherwise skip previous/next", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "direction", "type": "string", "enum": [ "left", "right", "up", "down" ], "required": true } + ], + "returns": "string" + }, + "Player.Zoom": { + "type": "method", + "description": "Zoom current picture", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "zoom", "type": [ + { "type": "string", "enum": [ "in", "out" ], "required": true }, + { "type": "integer", "minimum": 1, "maximum": 10, "description": "zoom level", "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Player.SetViewMode": { + "type": "method", + "description": "Set view mode of video player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "viewmode", "type": [ + { "$ref": "Player.CustomViewMode", "description": "Custom view mode", "required": true }, + { "name": "value", "$ref": "Player.ViewMode", "required": true} + ], + "required": true } + ], + "returns": "string" + }, + "Player.GetViewMode": { + "type": "method", + "description": "Get view mode of video player", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "viewmode": { "$ref": "Player.ViewMode", "required": true }, + "zoom": { "type": "number", "required": true }, + "pixelratio": { "type": "number", "required": true }, + "verticalshift": { "type": "number", "required": true }, + "nonlinearstretch": { "type": "boolean", "required": true } + } + } + }, + "Player.Rotate": { + "type": "method", + "description": "Rotates current picture", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "value", "type": "string", "enum": [ "clockwise", "counterclockwise" ], "default": "clockwise" } + ], + "returns": "string" + }, + "Player.GoTo": { + "type": "method", + "description": "Go to previous/next/specific item in the playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "to", "type": [ + { "type": "string", "enum": [ "previous", "next" ], "required": true }, + { "$ref": "Playlist.Position", "description": "position in playlist", "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Player.SetShuffle": { + "type": "method", + "description": "Shuffle/Unshuffle items in the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "shuffle", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Player.SetRepeat": { + "type": "method", + "description": "Set the repeat mode of the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "repeat", "type": [ + { "$ref": "Player.Repeat", "required": true }, + { "type": "string", "enum": [ "cycle" ], "required": true } + ], + "required": true + } + ], + "returns": "string" + }, + "Player.SetPartymode": { + "type": "method", + "description": "Turn partymode on or off", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "partymode", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Player.SetAudioStream": { + "type": "method", + "description": "Set the audio stream played by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "stream", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the audio stream to play" } + ] + } + ], + "returns": "string" + }, + "Player.SetVideoStream": { + "type": "method", + "description": "Set the video stream played by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "stream", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the video stream to play" } + ] + } + ], + "returns": "string" + }, + "Player.AddSubtitle": { + "type": "method", + "description": "Add subtitle to the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "subtitle", "type": "string", "required": true, "description": "Local path or remote URL to the subtitle file to load" } + ], + "returns": "string" + }, + "Player.SetSubtitle": { + "type": "method", + "description": "Set the subtitle displayed by the player", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playerid", "$ref": "Player.Id", "required": true }, + { "name": "subtitle", "required": true, "type": [ + { "type": "string", "enum": [ "previous", "next", "off", "on" ] }, + { "type": "integer", "minimum": 0, "description": "Index of the subtitle to display" } + ] + }, + { "name": "enable", "type": "boolean", "default": false, "description": "Whether to enable subtitles to be displayed after setting the new subtitle" } + ], + "returns": "string" + }, + "Playlist.GetPlaylists": { + "type": "method", + "description": "Returns all existing playlists", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "object", + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "type": { "$ref": "Playlist.Type", "required": true } + } + } + } + }, + "Playlist.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Playlist.Property.Name" } } + ], + "returns": { "$ref": "Playlist.Property.Value", "required": true } + }, + "Playlist.GetItems": { + "type": "method", + "description": "Get all items from playlist", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "properties", "$ref": "List.Fields.All" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "items": { "type": "array", "items": { "$ref": "List.Item.All" }, "required": true } + } + } + }, + "Playlist.Add": { + "type": "method", + "description": "Add item(s) to playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "item", + "type": [ + { "$ref": "Playlist.Item", "required": true }, + { "type": "array", "items": { "$ref": "Playlist.Item" }, "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Playlist.Insert": { + "type": "method", + "description": "Insert item(s) into playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position", "$ref": "Playlist.Position", "required": true }, + { "name": "item", + "type": [ + { "$ref": "Playlist.Item", "required": true }, + { "type": "array", "items": { "$ref": "Playlist.Item" }, "required": true } + ], + "required": true } + ], + "returns": "string" + }, + "Playlist.Remove": { + "type": "method", + "description": "Remove item from playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position", "$ref": "Playlist.Position", "required": true } + ], + "returns": "string" + }, + "Playlist.Clear": { + "type": "method", + "description": "Clear playlist", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true } + ], + "returns": "string" + }, + "Playlist.Swap": { + "type": "method", + "description": "Swap items in the playlist. Does not work for picture playlists (aka slideshows).", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "playlistid", "$ref": "Playlist.Id", "required": true }, + { "name": "position1", "$ref": "Playlist.Position", "required": true }, + { "name": "position2", "$ref": "Playlist.Position", "required": true } + ], + "returns": "string" + }, + "Files.GetSources": { + "type": "method", + "description": "Get the sources of the media windows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "media", "$ref": "Files.Media", "required": true }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sources": { "$ref": "List.Items.Sources", "required": true } + } + } + }, + "Files.PrepareDownload": { + "type": "method", + "description": "Provides a way to download a given file (e.g. providing an URL to the real file location)", + "transport": [ "Response", "FileDownloadRedirect" ], + "permission": "ReadData", + "params": [ + { "name": "path", "type": "string", "required": true } + ], + "returns": { + "type": "object", + "properties": { + "protocol": { "type": "string", "enum": [ "http" ], "required": true }, + "details": { "type": "any", "required": true, "description": "Transport specific details on how/from where to download the given file" }, + "mode": { "type": "string", "enum": [ "redirect", "direct" ], "required": true, "description": "Direct mode allows using Files.Download whereas redirect mode requires the usage of a different protocol" } + } + } + }, + "Files.Download": { + "type": "method", + "description": "Downloads the given file", + "transport": [ "Response", "FileDownloadDirect" ], + "permission": "ReadData", + "params": [ + { "name": "path", "type": "string", "required": true } + ], + "returns": { "type": "any", "required": true } + }, + "Files.GetDirectory": { + "type": "method", + "description": "Get the directories and files in the given directory", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "directory", "type": "string", "required": true }, + { "name": "media", "$ref": "Files.Media", "default": "files" }, + { "name": "properties", "$ref": "List.Fields.Files" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "limits", "$ref": "List.Limits", "description": "Limits are applied after getting the directory content thus retrieval is not faster when they are applied." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "files": { "type": "array", "items": { "$ref": "List.Item.File" }, "required": true } + } + } + }, + "Files.GetFileDetails": { + "type": "method", + "description": "Get details for a specific file", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "file", "type": "string", "required": true, "description": "Full path to the file" }, + { "name": "media", "$ref": "Files.Media", "default": "files" }, + { "name": "properties", "$ref": "List.Fields.Files" } + ], + "returns": { + "type": "object", + "properties": { + "filedetails": { "$ref": "List.Item.File", "required": true } + } + } + }, + "Files.SetFileDetails": { + "type": "method", + "description": "Update the given specific file with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "file", "type": "string", "required": true, "description": "Full path to the file" }, + { "name": "media", "$ref": "Files.Media", "required": true, "description": "File type to update correct database. Currently only \"video\" is supported." }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String", "description": "Setting a valid lastplayed without a playcount will force playcount to 1." }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null } + ], + "returns": "string" + }, + "AudioLibrary.GetProperties": { + "type": "method", + "description": "Retrieves the values of the music library properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Audio.Property.Name" } } + ], + "returns": { "$ref": "Audio.Property.Value", "required": true } + }, + "AudioLibrary.GetArtists": { + "type": "method", + "description": "Retrieve all artists. For backward compatibility by default this implicitly does not include those that only contribute other roles, however absolutely all artists can be returned using allroles=true", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumartistsonly", "$ref": "Optional.Boolean", "description": "Whether or not to only include album artists rather than the artists of only individual songs as well. If the parameter is not passed or is passed as null the GUI setting will be used" }, + { "name": "properties", "$ref": "Audio.Fields.Artist" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Deprecated, use songgenreid. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true, "description": "Song genreid. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songgenreid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Deprecated, use songgenre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songgenre": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "roleid": { "$ref": "Library.Id", "required": true, "description": "Role contributed by artist. Overridden by allroles parameter" } }, "additionalProperties": false }, + { "type": "object", "properties": { "role": { "type": "string", "minLength": 1, "required": true, "description": "Role contributed by artist. Overridden by allroles parameter" } }, "additionalProperties": false }, + { "$ref": "List.Filter.Artists" } + ] + }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all artists irrespective of the role they contributed. When true it overrides any role filter value." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "artists": { "type": "array", + "items": { "$ref": "Audio.Details.Artist" } + } + } + } + }, + "AudioLibrary.GetArtistDetails": { + "type": "method", + "description": "Retrieve details about a specific artist", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "artistid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Artist" } + ], + "returns": { "type": "object", + "properties": { + "artistdetails": { "$ref": "Audio.Details.Artist" } + } + } + }, + "AudioLibrary.GetAlbums": { + "type": "method", + "description": "Retrieve all albums from specified artist (and role) or that has songs of the specified genre", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "$ref": "List.Filter.Albums" } + ] + }, + { "name": "includesingles", "type": "boolean", "default": false }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than the default of excluding other contributions. When true it overrides any role filter value." } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetAlbumDetails": { + "type": "method", + "description": "Retrieve details about a specific album", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Album" } + ], + "returns": { "type": "object", + "properties": { + "albumdetails": { "$ref": "Audio.Details.Album" } + } + } + }, + "AudioLibrary.GetSongs": { + "type": "method", + "description": "Retrieve all songs from specified album, artist or genre", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Song genre. Filter for existence of songs with this genre" } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true }, + "role": { "type": "string", "minLength": 1, "required": true }}, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "album": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Songs" } + ] + }, + { "name": "includesingles", "type": "boolean", "default": true, "description": "Only songs from albums are returned when false, but overridden when singlesonly parameter is true" }, + { "name": "allroles", "type": "boolean", "default":false, "description": "Whether or not to include all roles when filtering by artist, rather than default of excluding other contributors. When true it overrides any role filter value." }, + { "name": "singlesonly", "type": "boolean", "default": false, "description": "Only singles are returned when true, and overrides includesingles parameter" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetSongDetails": { + "type": "method", + "description": "Retrieve details about a specific song", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "songid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Audio.Fields.Song" } + ], + "returns": { "type": "object", + "properties": { + "songdetails": { "$ref": "Audio.Details.Song" } + } + } + }, + "AudioLibrary.GetRecentlyAddedAlbums": { + "type": "method", + "description": "Retrieve recently added albums", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetRecentlyAddedSongs": { + "type": "method", + "description": "Retrieve recently added songs", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "albumlimit", "$ref": "List.Amount", "description": "The amount of recently added albums from which to return the songs" }, + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetRecentlyPlayedAlbums": { + "type": "method", + "description": "Retrieve recently played albums", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Album" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "albums": { "type": "array", + "items": { "$ref": "Audio.Details.Album" } + } + } + } + }, + "AudioLibrary.GetRecentlyPlayedSongs": { + "type": "method", + "description": "Retrieve recently played songs", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Song" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "songs": { "type": "array", + "items": { "$ref": "Audio.Details.Song" } + } + } + } + }, + "AudioLibrary.GetGenres": { + "type": "method", + "description": "Retrieve all genres", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Library.Fields.Genre" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "genres": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Genre" } + } + } + } + }, + "AudioLibrary.GetSources": { + "type": "method", + "description": "Get all music sources, including unique ID", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Library.Fields.Source" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sources": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Source" } + } + } + } + }, + "AudioLibrary.GetRoles": { + "type": "method", + "description": "Retrieve all contributor roles", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Audio.Fields.Role" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "roles": { "type": "array", "required": true, + "items": { "$ref": "Audio.Details.Role" } + } + } + } + }, + "AudioLibrary.GetAvailableArtTypes": { + "type": "method", + "description": "Retrieve a list of potential art types for a media item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "availablearttypes": { "type": "array", "required": true, + "items": { "type": "string" } + } + } + } + }, + "AudioLibrary.GetAvailableArt": { + "type": "method", + "description": "Retrieve all potential art URLs for a media item by art type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + }, + { "name": "arttype", "type": "string" } + ], + "returns": { + "type": "object", + "properties": { + "availableart": { "type": "array", "required": true, + "items": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "URL to the original image", "required": true }, + "arttype": { "type": "string", "required": true }, + "previewurl": { "type": "string", "description": "URL to a preview thumbnail of the image" } + } + } + } + } + } + }, + "AudioLibrary.SetArtistDetails": { + "type": "method", + "description": "Update the given artist with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "artistid", "$ref": "Library.Id", "required": true }, + { "name": "artist", "$ref": "Optional.String" }, + { "name": "instrument", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "style", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "mood", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "born", "$ref": "Optional.String" }, + { "name": "formed", "$ref": "Optional.String" }, + { "name": "description", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "died", "$ref": "Optional.String" }, + { "name": "disbanded", "$ref": "Optional.String" }, + { "name": "yearsactive", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "musicbrainzartistid", "$ref": "Optional.String" }, + { "name": "sortname", "$ref": "Optional.String" }, + { "name": "type", "$ref": "Optional.String" }, + { "name": "gender", "$ref": "Optional.String" }, + { "name": "disambiguation", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "AudioLibrary.SetAlbumDetails": { + "type": "method", + "description": "Update the given album with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "albumid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "description", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "theme", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "mood", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "style", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "type", "$ref": "Optional.String" }, + { "name": "albumlabel", "$ref": "Optional.String" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "year", "$ref": "Optional.Integer" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "votes", "$ref": "Optional.Integer" }, + { "name": "musicbrainzalbumid", "$ref": "Optional.String" }, + { "name": "musicbrainzreleasegroupid", "$ref": "Optional.String" }, + { "name": "sortartist", "$ref": "Optional.String" }, + { "name": "displayartist", "$ref": "Optional.String" }, + { "name": "musicbrainzalbumartistid", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "isboxset", "$ref": "Optional.Boolean" }, + { "name": "releasedate", "$ref": "Optional.String" }, + { "name": "originaldate", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "AudioLibrary.SetSongDetails": { + "type": "method", + "description": "Update the given song with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "songid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "track", "$ref": "Optional.Integer" }, + { "name": "disc", "$ref": "Optional.Integer" }, + { "name": "duration", "$ref": "Optional.Integer" }, + { "name": "comment", "$ref": "Optional.String" }, + { "name": "musicbrainztrackid", "$ref": "Optional.String" }, + { "name": "musicbrainzartistid", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "votes", "$ref": "Optional.Integer" }, + { "name": "displayartist", "$ref": "Optional.String" }, + { "name": "sortartist", "$ref": "Optional.String" }, + { "name": "mood", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "disctitle", "$ref": "Optional.String" }, + { "name": "releasedate", "$ref": "Optional.String" }, + { "name": "originaldate", "$ref": "Optional.String" }, + { "name": "bpm", "$ref": "Optional.Integer" } + ], + "returns": "string" + }, + "AudioLibrary.Scan": { + "type": "method", + "description": "Scans the audio sources for new library items", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "directory", "type": "string", "default": "" }, + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "AudioLibrary.Export": { + "type": "method", + "description": "Exports all items from the audio library", + "transport": "Response", + "permission": "WriteFile", + "params": [ + { "name": "options", "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true, "minLength": 1, "description": "Path to the directory to where the data should be exported" } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "overwrite": { "type": "boolean", "default": false, "description": "Whether to overwrite existing exported files" }, + "images": { "type": "boolean", "default": false, "description": "Whether to export thumbnails and fanart images" } + } + } + ] + } + ], + "returns": "string" + }, + "AudioLibrary.Clean": { + "type": "method", + "description": "Cleans the audio library from non-existent items", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "VideoLibrary.GetMovies": { + "type": "method", + "description": "Retrieve all movies", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Movie" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "country": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "set": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Movies" } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + } + }, + "VideoLibrary.GetMovieDetails": { + "type": "method", + "description": "Retrieve details about a specific movie", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Movie" } + ], + "returns": { "type": "object", + "properties": { + "moviedetails": { "$ref": "Video.Details.Movie" } + } + } + }, + "VideoLibrary.GetMovieSets": { + "type": "method", + "description": "Retrieve all movie sets", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MovieSet" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "sets": { "type": "array", + "items": { "$ref": "Video.Details.MovieSet" } + } + } + } + }, + "VideoLibrary.GetMovieSetDetails": { + "type": "method", + "description": "Retrieve details about a specific movie set", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.MovieSet" }, + { "name": "movies", "type": "object", + "properties": { + "properties": { "$ref": "Video.Fields.Movie" }, + "limits": { "$ref": "List.Limits" }, + "sort": { "$ref": "List.Sort" } + } + } + ], + "returns": { "type": "object", + "properties": { + "setdetails": { "$ref": "Video.Details.MovieSet.Extended" } + } + } + }, + "VideoLibrary.GetTVShows": { + "type": "method", + "description": "Retrieve all tv shows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.TVShow" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.TVShows" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tvshows": { "type": "array", + "items": { "$ref": "Video.Details.TVShow" } + } + } + } + }, + "VideoLibrary.GetTVShowDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.TVShow" } + ], + "returns": { "type": "object", + "properties": { + "tvshowdetails": { "$ref": "Video.Details.TVShow" } + } + } + }, + "VideoLibrary.GetSeasons": { + "type": "method", + "description": "Retrieve all tv seasons", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id" }, + { "name": "properties", "$ref": "Video.Fields.Season" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "seasons": { "type": "array", + "items": { "$ref": "Video.Details.Season" } + } + } + } + }, + "VideoLibrary.GetSeasonDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show season", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "seasonid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Season" } + ], + "returns": { "type": "object", + "properties": { + "seasondetails": { "$ref": "Video.Details.Season" } + } + } + }, + "VideoLibrary.GetEpisodes": { + "type": "method", + "description": "Retrieve all tv show episodes", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id" }, + { "name": "season", "type": "integer", "minimum": 0, "default": -1 }, + { "name": "properties", "$ref": "Video.Fields.Episode" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "actor": { "type": "string", "minLength": 1, "required": true, "description": "Requires tvshowid to be set" } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.Episodes" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "episodes": { "type": "array", + "items": { "$ref": "Video.Details.Episode" } + } + } + } + }, + "VideoLibrary.GetEpisodeDetails": { + "type": "method", + "description": "Retrieve details about a specific tv show episode", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.Episode" } + ], + "returns": { "type": "object", + "properties": { + "episodedetails": { "$ref": "Video.Details.Episode" } + } + } + }, + "VideoLibrary.GetMusicVideos": { + "type": "method", + "description": "Retrieve all music videos", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MusicVideo" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" }, + { "name": "filter", + "type": [ + { "type": "object", "properties": { "artist": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genre": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "year": { "type": "integer", "minimum": 0, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "director": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "studio": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tag": { "type": "string", "minLength": 1, "required": true } }, "additionalProperties": false }, + { "$ref": "List.Filter.MusicVideos" } + ] + } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "musicvideos": { "type": "array", + "items": { "$ref": "Video.Details.MusicVideo" } + } + } + } + }, + "VideoLibrary.GetMusicVideoDetails": { + "type": "method", + "description": "Retrieve details about a specific music video", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "Video.Fields.MusicVideo" } + ], + "returns": { "type": "object", + "properties": { + "musicvideodetails": { "$ref": "Video.Details.MusicVideo" } + } + } + }, + "VideoLibrary.GetRecentlyAddedMovies": { + "type": "method", + "description": "Retrieve all recently added movies", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Movie" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + } + }, + "VideoLibrary.GetRecentlyAddedEpisodes": { + "type": "method", + "description": "Retrieve all recently added tv episodes", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.Episode" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "episodes": { "type": "array", + "items": { "$ref": "Video.Details.Episode" } + } + } + } + }, + "VideoLibrary.GetRecentlyAddedMusicVideos": { + "type": "method", + "description": "Retrieve all recently added music videos", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.MusicVideo" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "musicvideos": { "type": "array", + "items": { "$ref": "Video.Details.MusicVideo" } + } + } + } + }, + "VideoLibrary.GetInProgressTVShows": { + "type": "method", + "description": "Retrieve all in progress tvshows", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Video.Fields.TVShow" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tvshows": { "type": "array", + "items": { "$ref": "Video.Details.TVShow" } + } + } + } + }, + "VideoLibrary.GetGenres": { + "type": "method", + "description": "Retrieve all genres", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": "string", "required": true, "enum": [ "movie", "tvshow", "musicvideo"] }, + { "name": "properties", "$ref": "Library.Fields.Genre" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "genres": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Genre" } + } + } + } + }, + "VideoLibrary.GetTags": { + "type": "method", + "description": "Retrieve all tags", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": "string", "required": true, "enum": [ "movie", "tvshow", "musicvideo" ] }, + { "name": "properties", "$ref": "Library.Fields.Tag" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "tags": { "type": "array", "required": true, + "items": { "$ref": "Library.Details.Tag" } + } + } + } + }, + "VideoLibrary.GetAvailableArtTypes": { + "type": "method", + "description": "Retrieve a list of potential art types for a media item", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tvshowid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "seasonid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + } + ], + "returns": { + "type": "object", + "properties": { + "availablearttypes": { "type": "array", "required": true, + "items": { "type": "string" } + } + } + } + }, + "VideoLibrary.GetAvailableArt": { + "type": "method", + "description": "Retrieve all potential art URLs for a media item by art type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { + "name": "item", "required": true, + "type": [ + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "tvshowid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "seasonid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "setid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false } + ] + }, + { "name": "arttype", "type": "string" } + ], + "returns": { + "type": "object", + "properties": { + "availableart": { "type": "array", "required": true, + "items": { + "type": "object", + "properties": { + "url": { "type": "string", "description": "URL to the original image", "required": true }, + "arttype": { "type": "string", "required": true }, + "previewurl": { "type": "string", "description": "URL to a preview thumbnail of the image" } + } + } + } + } + } + }, + "VideoLibrary.SetMovieDetails": { + "type": "method", + "description": "Update the given movie with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer", "description": "linked with premiered. Overridden by premiered parameter" }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "mpaa", "$ref": "Optional.String" }, + { "name": "imdbnumber", "$ref": "Optional.String" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "trailer", "$ref": "Optional.String" }, + { "name": "tagline", "$ref": "Optional.String" }, + { "name": "plotoutline", "$ref": "Optional.String" }, + { "name": "writer", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "country", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "top250", "$ref": "Optional.Integer" }, + { "name": "sorttitle", "$ref": "Optional.String" }, + { "name": "set", "$ref": "Optional.String" }, + { "name": "showlink", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String", "description": "linked with year. Overrides year" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetMovieSetDetails": { + "type": "method", + "description": "Update the given movie set with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "setid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "VideoLibrary.SetTVShowDetails": { + "type": "method", + "description": "Update the given tvshow with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "mpaa", "$ref": "Optional.String" }, + { "name": "imdbnumber", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "sorttitle", "$ref": "Optional.String" }, + { "name": "episodeguide", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "status", "$ref": "Optional.String", "description": "Valid values: 'returning series', 'in production', 'planned', 'cancelled', 'ended'" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetSeasonDetails": { + "type": "method", + "description": "Update the given season with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "seasonid", "$ref": "Library.Id", "required": true }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "title", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "VideoLibrary.SetEpisodeDetails": { + "type": "method", + "description": "Update the given episode with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "votes", "$ref": "Optional.String" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "writer", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "firstaired", "$ref": "Optional.String" }, + { "name": "productioncode", "$ref": "Optional.String" }, + { "name": "season", "$ref": "Optional.Integer" }, + { "name": "episode", "$ref": "Optional.Integer" }, + { "name": "originaltitle", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "ratings", "$ref": "Video.Ratings.Set" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.SetMusicVideoDetails": { + "type": "method", + "description": "Update the given music video with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "title", "$ref": "Optional.String" }, + { "name": "playcount", "$ref": "Optional.Integer" }, + { "name": "runtime", "$ref": "Optional.Integer", "description": "Runtime in seconds" }, + { "name": "director", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "studio", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "year", "$ref": "Optional.Integer", "description": "linked with premiered. Overridden by premiered parameter" }, + { "name": "plot", "$ref": "Optional.String" }, + { "name": "album", "$ref": "Optional.String" }, + { "name": "artist", "type": [ "null", { "$ref": "Array.String", "required": true } ] }, + { "name": "genre", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "track", "$ref": "Optional.Integer" }, + { "name": "lastplayed", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" }, + { "name": "fanart", "$ref": "Optional.String" }, + { "name": "tag", "type": [ "null", { "$ref": "Array.String", "required": true } ], "default": null }, + { "name": "art", "type": [ "null", { "$ref": "Media.Artwork.Set", "required": true } ], "default": null }, + { "name": "resume", "type": [ "null", { "$ref": "Video.Resume", "required": true } ], "default": null }, + { "name": "rating", "$ref": "Optional.Number" }, + { "name": "userrating", "$ref": "Optional.Integer" }, + { "name": "dateadded", "$ref": "Optional.String" }, + { "name": "premiered", "$ref": "Optional.String", "description": "linked with year. Overrides year" }, + { "name": "uniqueid", "type": [ "null", { "$ref": "Media.UniqueID.Set", "required": true } ], "default": null } + ], + "returns": "string" + }, + "VideoLibrary.RefreshMovie": { + "type": "method", + "description": "Refresh the given movie in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshTVShow": { + "type": "method", + "description": "Refresh the given tv show in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "refreshepisodes", "type": "boolean", "required": false, "default": false, "description": "Whether or not to refresh all episodes belonging to the TV show." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshEpisode": { + "type": "method", + "description": "Refresh the given episode in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RefreshMusicVideo": { + "type": "method", + "description": "Refresh the given music video in the library", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true }, + { "name": "ignorenfo", "type": "boolean", "required": false, "default": false, "description": "Whether or not to ignore a local NFO if present." }, + { "name": "title", "type": "string", "required": "false", "default": "", "description": "Title to use for searching (instead of determining it from the item's filename/path)." } + ], + "returns": "string" + }, + "VideoLibrary.RemoveMovie": { + "type": "method", + "description": "Removes the given movie from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "movieid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveTVShow": { + "type": "method", + "description": "Removes the given tv show from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "tvshowid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveEpisode": { + "type": "method", + "description": "Removes the given episode from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "episodeid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.RemoveMusicVideo": { + "type": "method", + "description": "Removes the given music video from the library", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "musicvideoid", "$ref": "Library.Id", "required": true } + ], + "returns": "string" + }, + "VideoLibrary.Scan": { + "type": "method", + "description": "Scans the video sources for new library items", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "directory", "type": "string", "default": "" }, + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" } + ], + "returns": "string" + }, + "VideoLibrary.Export": { + "type": "method", + "description": "Exports all items from the video library", + "transport": "Response", + "permission": "WriteFile", + "params": [ + { "name": "options", "type": [ + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "path": { "type": "string", "required": true, "minLength": 1, "description": "Path to the directory to where the data should be exported" } + } + }, + { "type": "object", "required": true, "additionalProperties": false, + "properties": { + "overwrite": { "type": "boolean", "default": false, "description": "Whether to overwrite existing exported files" }, + "images": { "type": "boolean", "default": false, "description": "Whether to export thumbnails and fanart images" }, + "actorthumbs": { "type": "boolean", "default": false, "description": "Whether to export actor thumbnails" } + } + } + ] + } + ], + "returns": "string" + }, + "VideoLibrary.Clean": { + "type": "method", + "description": "Cleans the video library for non-existent items", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "showdialogs", "type": "boolean", "default": true, "description": "Whether or not to show the progress bar or any other GUI dialog" }, + { "name": "content", "type": "string", "default": "video", "enum": [ "video", "movies", "tvshows", "musicvideos" ], "description": "Content type to clean for" }, + { "name": "directory", "type": "string", "default": "", "description": "Path to the directory to clean up; performs a global cleanup if not specified" } + ], + "returns": "string" + }, + "GUI.ActivateWindow": { + "type": "method", + "description": "Activates the given window", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "window", "$ref": "GUI.Window", "required": true }, + { "name": "parameters", "type": "array", "items": { "type": "string", "minLength": 1, "required": true }, "minItems": 1 } + ], + "returns": "string" + }, + "GUI.ShowNotification": { + "type": "method", + "description": "Shows a GUI notification", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "title", "type": "string", "required": true }, + { "name": "message", "type": "string", "required": true }, + { "name": "image", "type": [ + { "type": "string", "required": true, "enum": [ "info", "warning", "error" ] }, + { "type": "string", "required": true } + ], "default": "" + }, + { "name": "displaytime", "type": "integer", "minimum": 1500, "default": 5000, "description": "The time in milliseconds the notification will be visible" } + ], + "returns": "string" + }, + "GUI.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "GUI.Property.Name" } } + ], + "returns": { "$ref": "GUI.Property.Value", "required": true } + }, + "GUI.SetFullscreen": { + "type": "method", + "description": "Toggle fullscreen/GUI", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "fullscreen", "required": true, "$ref": "Global.Toggle" } + ], + "returns": { "type": "boolean", "description": "Fullscreen state" } + }, + "GUI.SetStereoscopicMode": { + "type": "method", + "description": "Sets the stereoscopic mode of the GUI to the given mode", + "transport": "Response", + "permission": "ControlGUI", + "params": [ + { "name": "mode", "type": "string", "enum": [ "toggle", "tomono", "next", "previous", "select", "off", "split_vertical", "split_horizontal", "row_interleaved", "hardware_based", "anaglyph_cyan_red", "anaglyph_green_magenta", "monoscopic" ], "required": true } + ], + "returns": "string" + }, + "GUI.GetStereoscopicModes": { + "type": "method", + "description": "Returns the supported stereoscopic modes of the GUI", + "transport": "Response", + "permission": "ReadData", + "params": [], + "returns": { + "type": "object", + "properties": { + "stereoscopicmodes" : { + "type": "array", + "uniqueItems": true, + "items": { "$ref": "GUI.Stereoscopy.Mode" } + } + } + } + }, + "Addons.GetAddons": { + "type": "method", + "description": "Gets all available addons", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "$ref": "Addon.Types" }, + { "name": "content", "$ref": "Addon.Content", "description": "Content provided by the addon. Only considered for plugins and scripts." }, + { "name": "enabled", "type": [ { "type": "boolean" }, { "type": "string", "enum": [ "all" ] } ], "default": "all" }, + { "name": "properties", "$ref": "Addon.Fields" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "installed", "type": [ { "type": "boolean" }, { "type": "string", "enum": [ "all" ] } ], "default": true } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "addons": { "type": "array", + "items": { "$ref": "Addon.Details" } + } + } + } + }, + "Addons.GetAddonDetails": { + "type": "method", + "description": "Gets the details of a specific addon", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "properties", "$ref": "Addon.Fields" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "addon": { "$ref": "Addon.Details", "required": true } + } + } + }, + "Addons.SetAddonEnabled": { + "type": "method", + "description": "Enables/Disables a specific addon", + "transport": "Response", + "permission": "ManageAddon", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "enabled", "$ref": "Global.Toggle", "required": true } + ], + "returns": "string" + }, + "Addons.ExecuteAddon": { + "type": "method", + "description": "Executes the given addon with the given parameters (if possible)", + "transport": "Response", + "permission": "ExecuteAddon", + "params": [ + { "name": "addonid", "type": "string", "required": true }, + { "name": "params", "type": [ + { "type": "object", "additionalProperties": { "type": "string" } }, + { "type": "array", "items": { "type": "string" } }, + { "type": "string", "description": "URL path (must start with / or ?" } + ], + "default": "" + }, + { "name": "wait", "type": "boolean", "default": false } + ], + "returns": "string" + }, + "PVR.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "PVR.Property.Name" } } + ], + "returns": { "$ref": "PVR.Property.Value", "required": true } + }, + "PVR.GetChannelGroups": { + "type": "method", + "description": "Retrieves the channel groups for the specified type", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channeltype", "$ref": "PVR.Channel.Type", "required": true }, + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channelgroups": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.ChannelGroup" } + } + } + } + }, + "PVR.GetChannelGroupDetails": { + "type": "method", + "description": "Retrieves the details of a specific channel group", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelgroupid", "$ref": "PVR.ChannelGroup.Id", "required": true }, + { "name": "channels", "type": "object", + "properties": { + "properties": { "$ref": "PVR.Fields.Channel" }, + "limits": { "$ref": "List.Limits" } + } + } + ], + "returns": { "type": "object", + "properties": { + "channelgroupdetails": { "$ref": "PVR.Details.ChannelGroup.Extended" } + } + } + }, + "PVR.GetChannels": { + "type": "method", + "description": "Retrieves the channel list", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelgroupid", "$ref": "PVR.ChannelGroup.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Channel" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channels": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Channel" } + } + } + } + }, + "PVR.GetChannelDetails": { + "type": "method", + "description": "Retrieves the details of a specific channel", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Channel" } + ], + "returns": { "type": "object", + "properties": { + "channeldetails": { "$ref": "PVR.Details.Channel" } + } + } + }, + "PVR.GetClients": { + "type": "method", + "description": "Retrieves the enabled PVR clients and their capabilities", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "clients": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Client" } + } + } + } + }, + "PVR.GetBroadcasts": { + "type": "method", + "description": "Retrieves the program of a specific channel", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "channelid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Broadcast" }, + { "name": "limits", "$ref": "List.Limits" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "broadcasts": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Broadcast" } + } + } + } + }, + "PVR.GetBroadcastDetails": { + "type": "method", + "description": "Retrieves the details of a specific broadcast", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Broadcast" } + ], + "returns": { "type": "object", + "properties": { + "broadcastdetails": { "$ref": "PVR.Details.Broadcast" } + } + } + }, + "PVR.GetBroadcastIsPlayable": { + "type": "method", + "description": "Retrieves whether or not a broadcast is playable", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the id of the broadcast to check for playability" } + ], + "returns": "boolean" + }, + "PVR.GetTimers": { + "type": "method", + "description": "Retrieves the timers", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "PVR.Fields.Timer" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "timers": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Timer" } + } + } + } + }, + "PVR.GetTimerDetails": { + "type": "method", + "description": "Retrieves the details of a specific timer", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "timerid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Timer" } + ], + "returns": { "type": "object", + "properties": { + "timerdetails": { "$ref": "PVR.Details.Timer" } + } + } + }, + "PVR.AddTimer": { + "type": "method", + "description": "Adds a timer to record the given show one times or a timer rule to record all showings of the given show or adds a reminder timer or reminder timer rule", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the broadcast id of the item to record" }, + { "name": "timerrule", "type": "boolean", "default": false, "description": "controls whether to create a timer rule or a onetime timer" }, + { "name": "reminder", "type": "boolean", "default": false, "description": "controls whether to create a reminder timer or a recording timer" } + ], + "returns": "string" + }, + "PVR.DeleteTimer": { + "type": "method", + "description": "Deletes a onetime timer or a timer rule", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "timerid", "$ref": "Library.Id", "required": true, "description": "the id of the onetime timer or timer rule to delete" } + ], + "returns": "string" + }, + "PVR.ToggleTimer": { + "type": "method", + "description": "Creates or deletes a onetime timer or timer rule for a given show. If it exists, it will be deleted. If it does not exist, it will be created", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "broadcastid", "$ref": "Library.Id", "required": true, "description": "the broadcast id of the item to toggle a onetime timer or time rule for" }, + { "name": "timerrule", "type": "boolean", "default": false, "description": "controls whether to create / delete a timer rule or a onetime timer" } + ], + "returns": "string" + }, + "PVR.GetRecordings": { + "type": "method", + "description": "Retrieves the recordings", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "PVR.Fields.Recording" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "recordings": { "type": "array", "required": true, + "items": { "$ref": "PVR.Details.Recording" } + } + } + } + }, + "PVR.GetRecordingDetails": { + "type": "method", + "description": "Retrieves the details of a specific recording", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "recordingid", "$ref": "Library.Id", "required": true }, + { "name": "properties", "$ref": "PVR.Fields.Recording" } + ], + "returns": { "type": "object", + "properties": { + "recordingdetails": { "$ref": "PVR.Details.Recording" } + } + } + }, + "PVR.Record": { + "type": "method", + "description": "Toggle recording of a channel", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "record", "$ref": "Global.Toggle", "default": "toggle" }, + { "name": "channel", "type": [ + { "type": "string", "enum": [ "current" ], "required": true }, + { "$ref": "Library.Id", "required": true } + ], + "default": "current" + } + ], + "returns": "string" + }, + "PVR.Scan": { + "type": "method", + "description": "Starts a channel scan", + "transport": "Response", + "permission": "ControlPVR", + "params": [ + { "name": "clientid", "$ref": "Library.Id", "description": "Specify a PVR client id to avoid UI dialog, optional in kodi 19, required in kodi 20" } + ], + "returns": "string" + }, + "Textures.GetTextures": { + "type": "method", + "description": "Retrieve all textures", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Textures.Fields.Texture" }, + { "name": "filter", "$ref": "List.Filter.Textures" } + ], + "returns": { + "type": "object", + "properties": { + "textures": { "type": "array", "required": true, + "items": { "$ref": "Textures.Details.Texture" } + } + } + } + }, + "Textures.RemoveTexture": { + "type": "method", + "description": "Remove the specified texture", + "transport": "Response", + "permission": "RemoveData", + "params": [ + { "name": "textureid", "$ref": "Library.Id", "required": true, "description": "Texture database identifier" } + ], + "returns": "string" + }, + "Profiles.GetProfiles": { + "type": "method", + "description": "Retrieve all profiles", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Profiles.Fields.Profile" }, + { "name": "limits", "$ref": "List.Limits" }, + { "name": "sort", "$ref": "List.Sort" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "profiles": { "type": "array", "required": true, + "items": { "$ref": "Profiles.Details.Profile" } + } + } + } + }, + "Profiles.GetCurrentProfile": { + "type": "method", + "description": "Retrieve the current profile", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "$ref": "Profiles.Fields.Profile" } + ], + "returns": { "$ref": "Profiles.Details.Profile", "required": true } + }, + "Profiles.LoadProfile": { + "type": "method", + "description": "Load the specified profile", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "profile", "type": "string", "required": true, "description": "Profile name" }, + { "name": "prompt", "type": "boolean", "description": "Prompt for password" }, + { "name": "password", "$ref": "Profiles.Password" } + ], + "returns": "string" + }, + "System.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "System.Property.Name" } } + ], + "returns": { "$ref": "System.Property.Value", "required": true } + }, + "System.EjectOpticalDrive": { + "type": "method", + "description": "Ejects or closes the optical disc drive (if available)", + "transport": "Response", + "permission": "ControlSystem", + "params": [ ], + "returns": "string" + }, + "System.Shutdown": { + "type": "method", + "description": "Shuts the system running Kodi down", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Suspend": { + "type": "method", + "description": "Suspends the system running Kodi", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Hibernate": { + "type": "method", + "description": "Puts the system running Kodi into hibernate mode", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "System.Reboot": { + "type": "method", + "description": "Reboots the system running Kodi", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "Input.SendText": { + "type": "method", + "description": "Send a generic (unicode) text", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "text", "type": "string", "required": true, "description": "Unicode text" }, + { "name": "done", "type": "boolean", "default": true, "description": "Whether this is the whole input or not (closes an open input dialog if true)." } + ], + "returns": "string" + }, + "Input.ExecuteAction": { + "type": "method", + "description": "Execute a specific action", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "action", "$ref": "Input.Action", "required": true } + ], + "returns": "string" + }, + "Input.ButtonEvent": { + "type": "method", + "description": "Send a button press event", + "transport": "Response", + "permission": "Navigate", + "params": [ + { "name": "button", "type": "string", "required": true, "description": "Button name" }, + { "name": "keymap", "type": "string", "required": true, "description": "Keymap name (KB, XG, R1, or R2)", "enum": [ "KB", "XG", "R1", "R2" ] }, + { "name": "holdtime", "type": "integer", "required": false, "minimum" : 0, "default" : 0, "description": "Number of milliseconds to simulate button hold." } + ], + "returns": "string" + }, + "Input.Left": { + "type": "method", + "description": "Navigate left in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Right": { + "type": "method", + "description": "Navigate right in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Down": { + "type": "method", + "description": "Navigate down in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Up": { + "type": "method", + "description": "Navigate up in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Select": { + "type": "method", + "description": "Select current item in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Back": { + "type": "method", + "description": "Goes back in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ContextMenu": { + "type": "method", + "description": "Shows the context menu", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Info": { + "type": "method", + "description": "Shows the information dialog", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.Home": { + "type": "method", + "description": "Goes to home window in GUI", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowCodec": { + "type": "method", + "description": "Show codec information of the playing item", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowOSD": { + "type": "method", + "description": "Show the on-screen display for the current player", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Input.ShowPlayerProcessInfo": { + "type": "method", + "description": "Show player process information of the playing item, like video decoder, pixel format, pvr signal strength, ...", + "transport": "Response", + "permission": "Navigate", + "params": [], + "returns": "string" + }, + "Application.GetProperties": { + "type": "method", + "description": "Retrieves the values of the given properties", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "properties", "type": "array", "uniqueItems": true, "required": true, "items": { "$ref": "Application.Property.Name" } } + ], + "returns": { "$ref": "Application.Property.Value", "required": true } + }, + "Application.SetVolume": { + "type": "method", + "description": "Set the current volume", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "volume", "type": [ + { "type": "integer", "minimum": 0, "maximum": 100, "required": true }, + { "$ref": "Global.IncrementDecrement", "required": true } + ], + "required": true + } + ], + "returns": "integer" + }, + "Application.SetMute": { + "type": "method", + "description": "Toggle mute/unmute", + "transport": "Response", + "permission": "ControlPlayback", + "params": [ + { "name": "mute", "required": true, "$ref": "Global.Toggle" } + ], + "returns": { "type": "boolean", "description": "Mute state" } + }, + "Application.Quit": { + "type": "method", + "description": "Quit application", + "transport": "Response", + "permission": "ControlPower", + "params": [], + "returns": "string" + }, + "XBMC.GetInfoLabels": { + "type": "method", + "description": "Retrieve info labels about Kodi and the system", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "labels", "type": "array", "required": true, "items": { "type": "string" }, "minItems": 1, "description": "See http://kodi.wiki/view/InfoLabels for a list of possible info labels" } + ], + "returns": { + "type": "object", + "description": "Object containing key-value pairs of the retrieved info labels", + "additionalProperties": { "type": "string" } + } + }, + "XBMC.GetInfoBooleans": { + "type": "method", + "description": "Retrieve info booleans about Kodi and the system", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "booleans", "type": "array", "required": true, "items": { "type": "string" }, "minItems": 1 } + ], + "returns": { + "type": "object", + "description": "Object containing key-value pairs of the retrieved info booleans", + "additionalProperties": { "type": "string" } + } + }, + "Favourites.GetFavourites": { + "type": "method", + "description": "Retrieve all favourites", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "type", "type": [ "null", { "$ref": "Favourite.Type" } ], "default": null }, + { "name": "properties", "$ref": "Favourite.Fields.Favourite" } + ], + "returns": { + "type": "object", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "favourites": { "type": "array", + "items": { "$ref": "Favourite.Details.Favourite" } + } + } + } + }, + "Favourites.AddFavourite": { + "type": "method", + "description": "Add a favourite with the given details", + "transport": "Response", + "permission": "UpdateData", + "params": [ + { "name": "title", "type": "string", "required": true }, + { "name": "type", "$ref": "Favourite.Type", "required": true }, + { "name": "path", "$ref": "Optional.String", "description": "Required for media, script and androidapp favourites types" }, + { "name": "window", "$ref": "Optional.String", "description": "Required for window favourite type" }, + { "name": "windowparameter", "$ref": "Optional.String" }, + { "name": "thumbnail", "$ref": "Optional.String" } + ], + "returns": "string" + }, + "Settings.GetSections": { + "type": "method", + "description": "Retrieves all setting sections", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "properties", "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "categories" ] + } + } + ], + "returns": { + "type": "object", + "properties": { + "sections": { "type": "array", + "items": { "$ref": "Setting.Details.Section" } + } + } + } + }, + "Settings.GetCategories": { + "type": "method", + "description": "Retrieves all setting categories", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "section", "type": "string", "default": "" }, + { "name": "properties", "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "settings" ] + } + } + ], + "returns": { + "type": "object", + "properties": { + "categories": { "type": "array", + "items": { "$ref": "Setting.Details.Category" } + } + } + } + }, + "Settings.GetSettings": { + "type": "method", + "description": "Retrieves all settings", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "level", "$ref": "Setting.Level", "default": "standard" }, + { "name": "filter", "type": [ + { "type": "object", + "properties": { + "section": { "type": "string", "required": true, "minLength": 1 }, + "category": { "type": "string", "required": true, "minLength": 1 } + }, + "additionalProperties": false, + "required": true + } + ], + "default": null + } + ], + "returns": { + "type": "object", + "properties": { + "settings": { "type": "array", + "items": { "$ref": "Setting.Details.Setting" } + } + } + } + }, + "Settings.GetSettingValue": { + "type": "method", + "description": "Retrieves the value of a setting", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": { + "type": "object", + "properties": { + "value": { "$ref": "Setting.Value.Extended", "required": true } + } + } + }, + "Settings.SetSettingValue": { + "type": "method", + "description": "Changes the value of a setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 }, + { "name": "value", "$ref": "Setting.Value.Extended", "required": true } + ], + "returns": "boolean" + }, + "Settings.ResetSettingValue": { + "type": "method", + "description": "Resets the value of a setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": "string" + }, + "Settings.GetSkinSettingValue": { + "type": "method", + "description": "Retrieves the value of the specified skin setting", + "transport": "Response", + "permission": "ReadData", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 } + ], + "returns": { + "type": "object", + "properties": { + "value": { "type": [ "boolean", "string" ], "required": true } + } + } + }, + "Settings.SetSkinSettingValue": { + "type": "method", + "description": "Changes the value of the specified skin setting", + "transport": "Response", + "permission": "WriteSetting", + "params": [ + { "name": "setting", "type": "string", "required": true, "minLength": 1 }, + { "name": "value", "type": [ "boolean", "string" ], "required": true } + ], + "returns": "boolean" + }, + "Settings.GetSkinSettings": { + "type": "method", + "description": "Retrieves all skin settings of the currently used skin", + "transport": "Response", + "permission": "ReadData", + "returns": { + "type": "object", + "properties": { + "skin": { "type": "string", "required": true, "minLength": 1 }, + "settings": { "type": "array", + "items": { "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "type": { "type": "string", "enum": [ "boolean", "string" ], "required": true }, + "value": { "type": [ "boolean", "string" ], "required": true } + } + } + } + } + } + } +} diff --git a/xbmc/interfaces/json-rpc/schema/notifications.json b/xbmc/interfaces/json-rpc/schema/notifications.json new file mode 100644 index 0000000..fa738cf --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/notifications.json @@ -0,0 +1,464 @@ +{ + "Player.OnPlay": { + "type": "notification", + "description": "Playback of a media item has been started or the playback speed has changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnResume": { + "type": "notification", + "description": "Playback of a media item has been resumed. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnAVStart": { + "type": "notification", + "description": "Playback of a media item has been started and first frame is available. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnAVChange": { + "type": "notification", + "description": "Audio- or videostream has changed. If there is no ID available extra information will be provided.", + "params": [ + { + "name": "sender", + "type": "string", + "required": true + }, + { + "name": "data", + "$ref": "Player.Notifications.Data", + "required": true + } + ], + "returns": null + }, + "Player.OnPause": { + "type": "notification", + "description": "Playback of a media item has been paused. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnStop": { + "type": "notification", + "description": "Playback of a media item has been stopped. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "item": { "$ref": "Notifications.Item" }, + "end": { "type": "boolean", "required": true, "description": "Whether the player has reached the end of the playable item(s) or not" } + } + } + ], + "returns": null + }, + "Player.OnSpeedChanged": { + "type": "notification", + "description": "Speed of the playback of a media item has been changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "$ref": "Player.Notifications.Data", "required": true } + ], + "returns": null + }, + "Player.OnSeek": { + "type": "notification", + "description": "The playback position has been changed. If there is no ID available extra information will be provided.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "item": { "$ref": "Notifications.Item" }, + "player": { "$ref": "Player.Notifications.Player.Seek", "required": true } + } + } + ], + "returns": null + }, + "Player.OnPropertyChanged": { + "type": "notification", + "description": "A property of the playing items has changed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "property": { "$ref": "Player.Property.Value" }, + "player": { "$ref": "Player.Notifications.Player", "required": true } + } + } + ], + "returns": null + }, + "Playlist.OnAdd": { + "type": "notification", + "description": "A playlist item has been added.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "item": { "$ref": "Notifications.Item" }, + "position": { "$ref": "Playlist.Position" } + } + } + ], + "returns": null + }, + "Playlist.OnRemove": { + "type": "notification", + "description": "A playlist item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true }, + "position": { "$ref": "Playlist.Position" } + } + } + ], + "returns": null + }, + "Playlist.OnClear": { + "type": "notification", + "description": "A playlist item has been cleared.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "playlistid": { "$ref": "Playlist.Id", "required": true } + } + } + ], + "returns": null + }, + "AudioLibrary.OnUpdate": { + "type": "notification", + "description": "An audio item has been updated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "type": "string", "id": "Notifications.Library.Audio.Type", "enum": [ "song" ], "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the update is being performed within a transaction." }, + "added": { "$ref": "Optional.Boolean", "description": "True if the update is for a newly added item." } + } + } + ], + "returns": null + }, + "AudioLibrary.OnRemove": { + "type": "notification", + "description": "An audio item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "$ref": "Notifications.Library.Audio.Type", "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the removal is being performed within a transaction." } + } + } + ], + "returns": null + }, + "AudioLibrary.OnScanStarted": { + "type": "notification", + "description": "An audio library scan has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnScanFinished": { + "type": "notification", + "description": "Scanning the audio library has been finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnCleanStarted": { + "type": "notification", + "description": "An audio library clean operation has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnCleanFinished": { + "type": "notification", + "description": "The audio library has been cleaned.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "AudioLibrary.OnExport": { + "type": "notification", + "description": "An audio library export has finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": false, + "properties": { + "file": { "type": "string", "required": false, "default": "" }, + "failcount": { "type": "integer", "minimum": 0, "required": false, "default": 0 } + } + } + ], + "returns": null + }, + "VideoLibrary.OnUpdate": { + "type": "notification", + "description": "A video item has been updated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "type": "string", "id": "Notifications.Library.Video.Type", "enum": [ "movie", "tvshow", "episode", "musicvideo" ], "required": true }, + "playcount": { "type": "integer", "minimum": 0, "default": -1 }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the update is being performed within a transaction." }, + "added": { "$ref": "Optional.Boolean", "description": "True if the update is for a newly added item." } + } + } + ], + "returns": null + }, + "VideoLibrary.OnExport": { + "type": "notification", + "description": "A video library export has finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": false, + "properties": { + "file": { "type": "string", "required": false, "default": "" }, + "root": { "type": "string", "required": false, "default": "" }, + "failcount": { "type": "integer", "minimum": 0, "required": false, "default": 0 } + } + } + ], + "returns": null + }, + "VideoLibrary.OnRemove": { + "type": "notification", + "description": "A video item has been removed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "id": { "$ref": "Library.Id", "required": true }, + "type": { "$ref": "Notifications.Library.Video.Type", "required": true }, + "transaction": { "$ref": "Optional.Boolean", "description": "True if the removal is being performed within a transaction." } + } + } + ], + "returns": null + }, + "VideoLibrary.OnScanStarted": { + "type": "notification", + "description": "A video library scan has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnScanFinished": { + "type": "notification", + "description": "Scanning the video library has been finished.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnCleanStarted": { + "type": "notification", + "description": "A video library clean operation has started.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnCleanFinished": { + "type": "notification", + "description": "The video library has been cleaned.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "VideoLibrary.OnRefresh": { + "type": "notification", + "description": "The video library has been refreshed and a home screen reload might be necessary.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnQuit": { + "type": "notification", + "description": "Kodi will be closed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "exitcode": { "type": "integer", "minimum": 0, "required": true } + } + } + ], + "returns": null + }, + "System.OnRestart": { + "type": "notification", + "description": "The system will be restarted.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnSleep": { + "type": "notification", + "description": "The system will be suspended.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnWake": { + "type": "notification", + "description": "The system woke up from suspension.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "System.OnLowBattery": { + "type": "notification", + "description": "The system is on low battery.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "Application.OnVolumeChanged": { + "type": "notification", + "description": "The volume of the application has changed.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "volume": { "type": "integer", "minimum": 0, "maximum": 100, "required": true }, + "muted": { "type": "boolean", "required": true } + } + } + ], + "returns": null + }, + "Input.OnInputRequested": { + "type": "notification", + "description": "The user is requested to provide some information.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "type": { "type": "string", "enum": [ "keyboard", "time", "date", "ip", "password", "numericpassword", "number", "seconds" ], "required": true }, + "value": { "type": "string", "required": true }, + "title": { "type": "string" } + } + } + ], + "returns": null + }, + "Input.OnInputFinished": { + "type": "notification", + "description": "The user has provided the requested input.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnScreensaverActivated": { + "type": "notification", + "description": "The screensaver has been activated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnScreensaverDeactivated": { + "type": "notification", + "description": "The screensaver has been deactivated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "object", "required": true, + "properties": { + "shuttingdown": { "type": "boolean", "required": true } + } + } + ], + "returns": null + }, + "GUI.OnDPMSActivated": { + "type": "notification", + "description": "Energy saving/DPMS has been activated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + }, + "GUI.OnDPMSDeactivated": { + "type": "notification", + "description": "Energy saving/DPMS has been deactivated.", + "params": [ + { "name": "sender", "type": "string", "required": true }, + { "name": "data", "type": "null", "required": true } + ], + "returns": null + } +} diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json new file mode 100644 index 0000000..37b5140 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -0,0 +1,2081 @@ +{ + "Optional.Boolean": { + "type": [ "null", "boolean" ], + "default": null + }, + "Optional.String": { + "type": [ "null", "string" ], + "default": null + }, + "Optional.Integer": { + "type": [ "null", "integer" ], + "default": null + }, + "Optional.Number": { + "type": [ "null", "number" ], + "default": null + }, + "Array.String": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "Array.Integer": { + "type": "array", + "items": { "type": "integer" } + }, + "Global.Time": { + "type": "object", + "description": "A duration.", + "properties": { + "hours": { "type": "integer", "required": true, "minimum": 0 }, + "minutes": { "type": "integer", "required": true, "minimum": 0, "maximum": 59 }, + "seconds": { "type": "integer", "required": true, "minimum": 0, "maximum": 59 }, + "milliseconds": { "type": "integer", "required": true, "minimum": 0, "maximum": 999 } + }, + "additionalProperties": false + }, + "Global.Weekday": { + "type": "string", + "enum": [ "monday", "tuesday", "wednesday", "thursday", + "friday", "saturday", "sunday" ] + }, + "Global.IncrementDecrement": { + "type": "string", + "enum": [ "increment", "decrement" ] + }, + "Global.Toggle": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "string", "enum": [ "toggle" ], "required": true } + ] + }, + "Global.String.NotEmpty": { + "type": "string", + "minLength": 1 + }, + "Configuration.Notifications": { + "type": "object", + "properties": { + "Player": { "type": "boolean", "required": true }, + "Playlist": { "type": "boolean", "required": true }, + "GUI": { "type": "boolean", "required": true }, + "System": { "type": "boolean", "required": true }, + "VideoLibrary": { "type": "boolean", "required": true }, + "AudioLibrary": { "type": "boolean", "required": true }, + "Application": { "type": "boolean", "required": true }, + "Input": { "type": "boolean", "required": true }, + "PVR": { "type": "boolean", "required": true }, + "Other": { "type": "boolean", "required": true } + }, + "additionalProperties": false + }, + "Configuration": { + "type": "object", "required": true, + "properties": { + "notifications": { "$ref": "Configuration.Notifications", "required": true } + } + }, + "Files.Media": { + "type": "string", + "enum": [ "video", "music", "pictures", "files", "programs" ] + }, + "List.Amount": { + "type": "integer", + "default": -1, + "minimum": 0 + }, + "List.Limits": { + "type": "object", + "properties": { + "start": { "type": "integer", "minimum": 0, "default": 0, "description": "Index of the first item to return" }, + "end": { "$ref": "List.Amount", "description": "Index of the last item to return" } + }, + "additionalProperties": false + }, + "List.LimitsReturned": { + "type": "object", + "properties": { + "start": { "type": "integer", "minimum": 0, "default": 0 }, + "end": { "$ref": "List.Amount" }, + "total": { "type": "integer", "minimum": 0, "required": true } + }, + "additionalProperties": false + }, + "List.Sort": { + "type": "object", + "properties": { + "method": { "type": "string", "default": "none", + "enum": [ "none", "label", "date", "size", "file", "path", "drivetype", "title", "track", "time", "artist", + "album", "albumtype", "genre", "country", "year", "rating", "userrating", "votes", "top250", "programcount", + "playlist", "episode", "season", "totalepisodes", "watchedepisodes", "tvshowstatus", "tvshowtitle", + "sorttitle", "productioncode", "mpaa", "studio", "dateadded", "lastplayed", "playcount", "listeners", + "bitrate", "random", "totaldiscs", "originaldate", "bpm", "originaltitle" ] + }, + "order": { "type": "string", "default": "ascending", "enum": [ "ascending", "descending" ] }, + "ignorearticle": { "type": "boolean", "default": false }, + "useartistsortname": { "type": "boolean", "default": false } + } + }, + "Library.Id": { + "type": "integer", + "default": -1, + "minimum": 1 + }, + "PVR.Channel.Type": { + "type": "string", + "enum": [ "tv", "radio" ] + }, + "Playlist.Id": { + "type": "integer", + "minimum": 0, + "maximum": 2, + "default": -1 + }, + "Playlist.Type": { + "type": "string", + "enum": [ "unknown", "video", "audio", "picture", "mixed" ] + }, + "Playlist.Property.Name": { + "type": "string", + "enum": [ "type", "size" ] + }, + "Playlist.Property.Value": { + "type": "object", + "properties": { + "type": { "$ref": "Playlist.Type" }, + "size": { "type": "integer", "minimum": 0 } + } + }, + "Playlist.Position": { + "type": "integer", + "minimum": 0, + "default": -1 + }, + "Playlist.Item": { + "type": [ + { "type": "object", "properties": { "file": { "type": "string", "description": "Path to a file (not a directory) to be added to the playlist", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "directory": { "type": "string", "required": true }, "recursive": { "type": "boolean", "default": false }, "media": { "$ref": "Files.Media", "default": "files" } }, "additionalProperties": false }, + { "type": "object", "properties": { "movieid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "episodeid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "musicvideoid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "artistid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "albumid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "songid": { "$ref": "Library.Id", "required": true } }, "additionalProperties": false }, + { "type": "object", "properties": { "genreid": { "$ref": "Library.Id", "required": true, "description": "Identification of a genre from the AudioLibrary" } }, "additionalProperties": false }, + { "type": "object", "properties": { "recordingid": { "$ref": "Library.Id", "required": true, "description": "Identification of a PVR recording" } }, "additionalProperties": false } + ] + }, + "Player.Id": { + "type": "integer", + "minimum": 0, + "maximum": 2, + "default": -1 + }, + "Player.Type": { + "type": "string", + "enum": [ "video", "audio", "picture" ] + }, + "Player.Position.Percentage": { + "type": "number", + "minimum": 0.0, + "maximum": 100.0 + }, + "Player.Position.Time": { + "type": "object", + "description": "A position in duration.", + "additionalProperties": false, + "properties": { + "hours": { "type": "integer", "minimum": 0, "default": 0 }, + "minutes": { "type": "integer", "minimum": 0, "maximum": 59, "default": 0 }, + "seconds": { "type": "integer", "minimum": 0, "maximum": 59, "default": 0 }, + "milliseconds": { "type": "integer", "minimum": 0, "maximum": 999, "default": 0 } + } + }, + "Player.Speed": { + "type": "object", + "required": true, + "properties": { + "speed": { "type": "integer" } + } + }, + "Player.ViewMode": { + "type": "string", + "enum": [ "normal", "zoom", "stretch4x3", "widezoom", "stretch16x9", "original", + "stretch16x9nonlin", "zoom120width", "zoom110width" ] + }, + "Player.CustomViewMode": { + "type": "object", + "required": true, + "properties": { + "zoom": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum":0.5, "maximum": 2.0, "description": "Zoom where 1.0 means 100%", "required": true } ] }, + "pixelratio": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum":0.5, "maximum": 2.0, "description": "Pixel aspect ratio where 1.0 means square pixel", "required": true } ] }, + "verticalshift": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Number", "minimum": -2.0, "maximum": 2.0, "description": "Vertical shift 1.0 means shift to bottom", "required": true } ] }, + "nonlinearstretch": { "type": [ + { "type": "string", "enum": [ "increase", "decrease" ], "required": true }, + { "$ref": "Optional.Boolean", "description": "Flag to enable nonlinear stretch", "required": true } ] } + } + }, + "Player.Repeat": { + "type": "string", + "enum": [ "off", "one", "all" ] + }, + "Player.Audio.Stream": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "codec": { "type": "string", "required": true }, + "bitrate": { "type": "integer", "required": true }, + "channels": { "type": "integer", "required": true }, + "isdefault": { "type": "boolean", "required": true }, + "isoriginal": { "type": "boolean", "required": true }, + "isimpaired": { "type": "boolean", "required": true }, + "samplerate": { "type": "integer", "required": true } + } + }, + "Player.Video.Stream": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "codec": { "type": "string", "required": true }, + "width": { "type": "integer", "required": true }, + "height": { "type": "integer", "required": true } + } + }, + "Player.Subtitle": { + "type": "object", + "properties": { + "index": { "type": "integer", "minimum": 0, "required": true }, + "name": { "type": "string", "required": true }, + "language": { "type": "string", "required": true }, + "isdefault": { "type": "boolean", "required": true }, + "isforced": { "type": "boolean", "required": true }, + "isimpaired": { "type": "boolean", "required": true } + } + }, + "Player.Property.Name": { + "type": "string", + "enum": [ "type", "partymode", "speed", "time", "percentage", + "totaltime", "playlistid", "position", "repeat", "shuffled", + "canseek", "canchangespeed", "canmove", "canzoom", "canrotate", + "canshuffle", "canrepeat", "currentaudiostream", "audiostreams", + "subtitleenabled", "currentsubtitle", "subtitles", "live", + "currentvideostream", "videostreams", "cachepercentage" ] + }, + "Player.Property.Value": { + "type": "object", + "properties": { + "type": { "$ref": "Player.Type" }, + "partymode": { "type": "boolean" }, + "speed": { "type": "integer" }, + "time": { "$ref": "Global.Time" }, + "percentage": { "$ref": "Player.Position.Percentage" }, + "totaltime": { "$ref": "Global.Time" }, + "playlistid": { "$ref": "Playlist.Id" }, + "position": { "$ref": "Playlist.Position" }, + "repeat": { "$ref": "Player.Repeat" }, + "shuffled": { "type": "boolean" }, + "canseek": { "type": "boolean" }, + "canchangespeed": { "type": "boolean" }, + "canmove": { "type": "boolean" }, + "canzoom": { "type": "boolean" }, + "canrotate": { "type": "boolean" }, + "canshuffle": { "type": "boolean" }, + "canrepeat": { "type": "boolean" }, + "currentaudiostream": { "$ref": "Player.Audio.Stream" }, + "audiostreams": { "type": "array", "items": { "$ref": "Player.Audio.Stream" } }, + "currentvideostream": { "$ref": "Player.Video.Stream" }, + "videostreams": { "type": "array", "items": { "$ref": "Player.Video.Stream" } }, + "subtitleenabled": { "type": "boolean" }, + "currentsubtitle": { "$ref": "Player.Subtitle" }, + "subtitles": { "type": "array", "items": { "$ref": "Player.Subtitle" } }, + "live": { "type": "boolean" }, + "cachepercentage": { "$ref": "Player.Position.Percentage" } + } + }, + "Notifications.Item.Type": { + "type": "string", + "enum": [ "unknown", "movie", "episode", "musicvideo", "song", "picture", "channel" ] + }, + "Notifications.Item": { + "type": [ + { "type": "object", "description": "An unknown item does not have any additional information.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true } + } + }, + { "type": "object", "description": "An item known to the database has an identification.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "id": { "$ref": "Library.Id", "required": true } + } + }, + { "type": "object", "description": "A movie item has a title and may have a release year.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "year": { "type": "integer" } + } + }, + { "type": "object", "description": "A tv episode has a title and may have an episode number, season number and the title of the show it belongs to.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "episode": { "type": "integer" }, + "season": { "type": "integer" }, + "showtitle": { "type": "string" } + } + }, + { "type": "object", "description": "A music video has a title and may have an album and an artist.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "album": { "type": "string" }, + "artist": { "type": "string" } + } + }, + { "type": "object", "description": "A song has a title and may have an album, an artist and a track number.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "title": { "type": "string", "required": true }, + "album": { "type": "string" }, + "artist": { "type": "string" }, + "track": { "type": "integer" } + } + }, + { "type": "object", "description": "A picture has a file path.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "file": { "type": "string", "required": true } + } + }, + { "type": "object", "description": "A PVR channel is either a radio or tv channel and has a title.", + "properties": { + "type": { "$ref": "Notifications.Item.Type", "required": true }, + "id": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string", "required": true }, + "channeltype": { "$ref": "PVR.Channel.Type", "required": true } + } + } + ] + }, + "Player.Notifications.Player": { + "type": "object", + "properties": { + "playerid": { "$ref": "Player.Id", "required": true }, + "speed": { "type": "integer" } + } + }, + "Player.Notifications.Player.Seek": { + "extends": "Player.Notifications.Player", + "properties": { + "time": { "$ref": "Global.Time" }, + "seekoffset": { "$ref": "Global.Time" } + } + }, + "Player.Notifications.Data": { + "type": "object", + "properties": { + "item": { "$ref": "Notifications.Item", "required": true }, + "player": { "$ref": "Player.Notifications.Player", "required": true } + } + }, + "Item.Fields.Base": { + "type": "array", + "uniqueItems": true, + "items": { "type": "string" } + }, + "Item.Details.Base": { + "type": "object", + "properties": { + "label": { "type": "string", "required": true } + } + }, + "Item.CustomProperties": { + "type": "object", + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.Details.Base": { + "extends": "Item.Details.Base", + "properties": { + "fanart": { "type": "string" }, + "thumbnail": { "type": "string" } + } + }, + "Media.Artwork": { + "type": "object", + "properties": { + "thumb": { "$ref": "Global.String.NotEmpty" }, + "poster": { "$ref": "Global.String.NotEmpty" }, + "banner": { "$ref": "Global.String.NotEmpty" }, + "fanart": { "$ref": "Global.String.NotEmpty" } + }, + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.Artwork.Set": { + "type": "object", + "properties": { + "thumb": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "poster": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "banner": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" }, + "fanart": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ], "default": "" } + }, + "additionalProperties": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ] } + }, + "Video.Rating": { + "type": "object", + "properties": { + "rating": { "type": "number", "required": true }, + "votes": { "type": "integer" }, + "default": { "type": "boolean" } + } + }, + "Video.Ratings": { + "type": "object", + "additionalProperties": { "$ref": "Video.Rating" } + }, + "Video.Ratings.Set": { + "type": "object", + "additionalProperties": { "type": [ "null", { "$ref": "Video.Rating", "required": true } ] } + }, + "Media.UniqueID": { + "type": "object", + "additionalProperties": { "$ref": "Global.String.NotEmpty" } + }, + "Media.UniqueID.Set": { + "type": "object", + "additionalProperties": { "type": [ "null", { "$ref": "Global.String.NotEmpty", "required": true } ] } + }, + "Library.Fields.Source": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "file", "paths" ] } + }, + "Library.Details.Source": { + "extends": "Item.Details.Base", + "properties": { + "sourceid": { "$ref": "Library.Id", "required": true }, + "file": { "type": "string", "description": "The url encoded multipath string combining all paths of the source ", "required": true }, + "paths": { "$ref": "Array.String", "description": "The individual paths of the media source" } + } + }, + "Library.Fields.Genre": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title", "thumbnail", "sourceid" ] } + }, + "Library.Details.Genre": { + "extends": "Item.Details.Base", + "properties": { + "genreid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "thumbnail": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer", "description": "The ids of sources with songs of the genre" } + } + }, + "Library.Fields.Tag": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title" ] } + }, + "Library.Details.Tag": { + "extends": "Item.Details.Base", + "properties": { + "tagid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + }, + "Audio.Fields.Role": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "title" ] } + }, + "Audio.Details.Role": { + "extends": "Item.Details.Base", + "properties": { + "roleid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + }, + "Audio.Fields.Artist": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the (song)genreid/genre, roleid/role or sourceid fields will result in increased response times", + "enum": [ "instrument", "style", "mood", "born", "formed", + "description", "genre", "died", "disbanded", + "yearsactive", "musicbrainzartistid", "fanart", + "thumbnail", "compilationartist", "dateadded", + "roles", "songgenres", "isalbumartist", + "sortname", "type", "gender", "disambiguation", "art", "sourceid", + "datemodified", "datenew" ] + } + }, + "Audio.Fields.Album": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the songgenres, artistid and/or sourceid fields will result in increased response times", + "enum": [ "title", "description", "artist", "genre", + "theme", "mood", "style", "type", "albumlabel", + "rating", "votes", "userrating","year", "musicbrainzalbumid", + "musicbrainzalbumartistid", "fanart", "thumbnail", + "playcount", "artistid", "displayartist", + "compilation", "releasetype", "dateadded", + "sortartist", "musicbrainzreleasegroupid", "songgenres", "art", + "lastplayed", "sourceid","isboxset", "totaldiscs", + "releasedate", "originaldate", "albumstatus", "datemodified", "datenew", + "albumduration"] + } + }, + "Audio.Fields.Song": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the genreid, artistid, albumartistid and/or sourceid fields will result in increased response times", + "enum": [ "title", "artist", "albumartist", "genre", "year", + "rating", "album", "track", "duration", "comment", + "lyrics", "musicbrainztrackid", "musicbrainzartistid", + "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "thumbnail", "file", "albumid", + "lastplayed", "disc", "genreid", "artistid", "displayartist", + "albumartistid", "albumreleasetype", "dateadded", + "votes", "userrating", "mood", "contributors", + "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist", + "sortartist", "art", "sourceid", "disctitle", "releasedate", "originaldate", + "bpm", "samplerate", "bitrate", "channels", "datemodified", "datenew" ] + } + }, + "Audio.Album.ReleaseType": { + "type": "string", + "enum": [ "album", "single" ], + "default": "album" + }, + "Audio.Contributors": { + "type": "array", + "items": { "type": "object", + "description": "The artist and the role they contribute to a song", + "properties": { + "name": { "type": "string", "required": true }, + "role": { "type": "string", "required": true }, + "roleid": { "$ref": "Library.Id", "required": true }, + "artistid": { "$ref": "Library.Id", "required": true } + }, + "additionalProperties": false + } + }, + "Audio.Artist.Roles": { + "type": "array", + "items": { "type": "object", + "description": "The various roles contributed by an artist to one or more songs", + "properties": { + "roleid": { "$ref": "Library.Id", "required": true }, + "role": { "type": "string", "required": true } + }, + "additionalProperties": false + } + }, + "Audio.Details.Genres": { + "type": "array", + "items": { "type": "object", + "properties": { + "genreid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" } + } + } + }, + "Audio.Details.Base": { + "extends": "Media.Details.Base", + "properties": { + "genre": { "$ref": "Array.String" }, + "dateadded": { "type": "string" }, + "art": { "$ref": "Media.Artwork" } + } + }, + "Audio.Details.Media": { + "extends": "Audio.Details.Base", + "properties": { + "title": { "type": "string" }, + "artist": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "musicbrainzalbumartistid": { "$ref": "Array.String" }, + "artistid": { "$ref": "Array.Integer" }, + "displayartist": { "type" : "string" }, + "votes": { "type": "integer" }, + "userrating": { "type": "integer" }, + "sortartist": { "type" : "string" }, + "releasedate": { "type" : "string" }, + "originaldate": { "type" : "string" } + } + }, + "Audio.Details.Artist": { + "extends": "Audio.Details.Base", + "properties": { + "artistid": { "$ref": "Library.Id", "required": true }, + "artist": { "type": "string", "required": true }, + "instrument": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "born": { "type": "string" }, + "formed": { "type": "string" }, + "description": { "type": "string" }, + "died": { "type": "string" }, + "disbanded": { "type": "string" }, + "yearsactive": { "$ref": "Array.String" }, + "compilationartist": { "type": "boolean" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "roles": {"$ref": "Audio.Artist.Roles"}, + "songgenres": {"$ref": "Audio.Details.Genres"}, + "isalbumartist": { "type": "boolean" }, + "sortname": { "type": "string" }, + "type": { "type": "string" }, + "gender": { "type": "string" }, + "disambiguation": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer" } + } + }, + "Audio.Details.Album": { + "extends": "Audio.Details.Media", + "properties": { + "albumid": { "$ref": "Library.Id", "required": true }, + "description": { "type": "string" }, + "theme": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "type": { "type": "string" }, + "albumlabel": { "type": "string" }, + "playcount": { "type": "integer" }, + "compilation": { "type": "boolean" }, + "releasetype": { "$ref": "Audio.Album.ReleaseType" }, + "musicbrainzreleasegroupid": { "type": "string" }, + "musicbrainzalbumid": { "type": "string" }, + "songgenres": {"$ref": "Audio.Details.Genres"}, + "lastplayed": { "type": "string" }, + "sourceid": { "$ref": "Array.Integer" }, + "isboxset" : { "type": "boolean" }, + "totaldiscs": { "type": "integer" }, + "albumstatus": { "type": "string" }, + "albumduration": { "type": "integer" } + } + }, + "Audio.Details.Song": { + "extends": "Audio.Details.Media", + "properties": { + "songid": { "$ref": "Library.Id", "required": true }, + "file": { "type": "string" }, + "albumartist": { "$ref": "Array.String" }, + "album": { "type": "string" }, + "track": { "type": "integer" }, + "duration": { "type": "integer" }, + "comment": { "type": "string" }, + "lyrics": { "type": "string" }, + "playcount": { "type": "integer" }, + "musicbrainztrackid": { "type": "string" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "albumid": { "$ref": "Library.Id" }, + "lastplayed": { "type": "string" }, + "disc": { "type": "integer" }, + "albumartistid": { "$ref": "Array.Integer" }, + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "mood": { "type": "string"}, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"}, + "genreid": { "$ref": "Array.Integer"}, + "sourceid": { "$ref": "Array.Integer" }, + "disctitle": { "type": "string" }, + "bpm": { "type": "Integer" }, + "samplerate": { "type": "Integer" }, + "bitrate": { "type": "Integer"}, + "channels": { "type": "Integer"} + } + }, + "Audio.Property.Name": { + "type": "string", + "enum": [ "missingartistid", "librarylastupdated", "librarylastcleaned", "artistlinksupdated", + "songslastadded", "albumslastadded", "artistslastadded", "genreslastadded", + "songsmodified", "albumsmodified", "artistsmodified"] + }, + "Audio.Property.Value": { + "type": "object", + "properties": { + "missingartistid": { "$ref": "Library.Id" }, + "librarylastupdated": { "type": "string" }, + "librarylastcleaned": { "type": "string" }, + "artistlinksupdated": { "type": "string" }, + "songslastadded": { "type": "string" }, + "albumslastadded": { "type": "string" }, + "artistslastadded": { "type": "string" }, + "genreslastadded": { "type": "string" }, + "songsmodified": { "type": "string" }, + "albumsmodified": { "type": "string" }, + "artistsmodified": { "type": "string" } + } + }, + "Video.Fields.Movie": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, showlink, streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "genre", "year", "rating", "director", "trailer", + "tagline", "plot", "plotoutline", "originaltitle", "lastplayed", + "playcount", "writer", "studio", "mpaa", "cast", "country", + "imdbnumber", "runtime", "set", "showlink", "streamdetails", + "top250", "votes", "fanart", "thumbnail", "file", "sorttitle", + "resume", "setid", "dateadded", "tag", "art", "userrating", + "ratings", "premiered", "uniqueid" ] + } + }, + "Video.Fields.MovieSet": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "playcount", "fanart", "thumbnail", "art", "plot" ] + } + }, + "Video.Fields.TVShow": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "genre", "year", "rating", "plot", + "studio", "mpaa", "cast", "playcount", "episode", + "imdbnumber", "premiered", "votes", "lastplayed", + "fanart", "thumbnail", "file", "originaltitle", + "sorttitle", "episodeguide", "season", "watchedepisodes", + "dateadded", "tag", "art", "userrating", "ratings", + "runtime", "uniqueid" ] + } + }, + "Video.Fields.Season": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "season", "showtitle", "playcount", "episode", "fanart", "thumbnail", "tvshowid", + "watchedepisodes", "art", "userrating", "title" ] + } + }, + "Video.Fields.Episode": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the cast, ratings, streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "plot", "votes", "rating", "writer", + "firstaired", "playcount", "runtime", "director", + "productioncode", "season", "episode", "originaltitle", + "showtitle", "cast", "streamdetails", "lastplayed", "fanart", + "thumbnail", "file", "resume", "tvshowid", "dateadded", + "uniqueid", "art", "specialsortseason", "specialsortepisode", "userrating", + "seasonid", "ratings" ] + } + }, + "Video.Fields.MusicVideo": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "description": "Requesting the streamdetails, uniqueid and/or tag field will result in increased response times", + "enum": [ "title", "playcount", "runtime", "director", + "studio", "year", "plot", "album", "artist", + "genre", "track", "streamdetails", "lastplayed", + "fanart", "thumbnail", "file", "resume", "dateadded", + "tag", "art", "rating", "userrating", "premiered", "uniqueid" ] + } + }, + "Video.Cast": { + "type": "array", + "items": { "type": "object", + "properties": { + "name": { "type": "string", "required": true }, + "role": { "type": "string", "required": true }, + "order": { "type": "integer", "required": true }, + "thumbnail": { "type": "string" } + }, + "additionalProperties": false + } + }, + "Video.Streams": { + "type": "object", + "properties": { + "audio": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "codec": { "type": "string" }, + "language": { "type": "string" }, + "channels": { "type": "integer" } + }, + "additionalProperties": false + } + }, + "video": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "codec": { "type": "string" }, + "aspect": { "type": "number" }, + "width": { "type": "integer" }, + "height": { "type": "integer" }, + "duration": { "type": "integer" }, + "hdrtype": { "type": "string"} + }, + "additionalProperties": false + } + }, + "subtitle": { "type": "array", "minItems": 1, + "items": { "type": "object", + "properties": { + "language": { "type": "string" } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + }, + "Video.Resume": { + "type": "object", + "properties": { + "position": { "type": "number", "minimum": 0.0 }, + "total": { "type": "number", "minimum": 0.0 } + }, + "additionalProperties": false + }, + "Video.Details.Base": { + "extends": "Media.Details.Base", + "properties": { + "playcount": { "type": "integer" }, + "art": { "$ref": "Media.Artwork" } + } + }, + "Video.Details.Media": { + "extends": "Video.Details.Base", + "properties": { + "title": { "type": "string" } + } + }, + "Video.Details.Item": { + "extends": "Video.Details.Media", + "properties": { + "file": { "type": "string" }, + "plot": { "type": "string" }, + "lastplayed": { "type": "string" }, + "dateadded": { "type": "string" } + } + }, + "Video.Details.File": { + "extends": "Video.Details.Item", + "properties": { + "runtime": { "type": "integer", "description": "Runtime in seconds" }, + "director": { "$ref": "Array.String" }, + "streamdetails": { "$ref": "Video.Streams" }, + "resume": { "$ref": "Video.Resume" } + } + }, + "Video.Details.Movie": { + "extends": "Video.Details.File", + "properties": { + "movieid": { "$ref": "Library.Id", "required": true }, + "genre": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "trailer": { "type": "string" }, + "tagline": { "type": "string" }, + "plotoutline": { "type": "string" }, + "originaltitle": { "type": "string" }, + "sorttitle": { "type": "string" }, + "writer": { "$ref": "Array.String" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "country": { "$ref": "Array.String" }, + "imdbnumber": { "type": "string" }, + "set": { "type": "string" }, + "showlink": { "$ref": "Array.String" }, + "top250": { "type": "integer" }, + "votes": { "type": "string" }, + "setid": { "$ref": "Library.Id" }, + "tag": { "$ref": "Array.String" }, + "userrating": { "type": "integer" }, + "ratings": { "type": "Video.Ratings" }, + "premiered": { "type": "string" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "Video.Details.MovieSet": { + "extends": "Video.Details.Media", + "properties": { + "setid": { "$ref": "Library.Id", "required": true }, + "plot": { "type": "string" } + } + }, + "Video.Details.MovieSet.Extended": { + "extends": "Video.Details.MovieSet", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "movies": { "type": "array", + "items": { "$ref": "Video.Details.Movie" } + } + } + }, + "Video.Details.TVShow": { + "extends": "Video.Details.Item", + "properties": { + "tvshowid": { "$ref": "Library.Id", "required": true }, + "genre": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "rating": { "type": "number" }, + "originaltitle": { "type": "string" }, + "sorttitle": { "type": "string" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "episode": { "type": "integer" }, + "watchedepisodes": { "type": "integer" }, + "imdbnumber": { "type": "string" }, + "premiered": { "type": "string" }, + "votes": { "type": "string" }, + "episodeguide": { "type": "string" }, + "season": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "userrating": { "type": "integer" }, + "ratings": { "type": "Video.Ratings" }, + "runtime": { "type": "integer", "description": "Runtime in seconds" }, + "status": { "type": "string", "description": "Returns 'returning series', 'in production', 'planned', 'cancelled' or 'ended'" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "Video.Details.Season": { + "extends": "Video.Details.Base", + "properties": { + "seasonid": { "$ref": "Library.Id", "required": true }, + "season": { "type": "integer", "required": true }, + "showtitle": { "type": "string" }, + "episode": { "type": "integer" }, + "watchedepisodes": { "type": "integer" }, + "tvshowid": { "$ref": "Library.Id" }, + "userrating": { "type": "integer" }, + "title": { "type": "string" } + } + }, + "Video.Details.Episode": { + "extends": "Video.Details.File", + "properties": { + "episodeid": { "$ref": "Library.Id", "required": true }, + "votes": { "type": "string" }, + "rating": { "type": "number" }, + "writer": { "$ref": "Array.String" }, + "firstaired": { "type": "string" }, + "productioncode": { "type": "string" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "uniqueid": { "$ref": "Media.UniqueID" }, + "originaltitle": { "type": "string" }, + "showtitle": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "tvshowid": { "$ref": "Library.Id" }, + "specialsortseason": { "type": "integer" }, + "specialsortepisode": { "type": "integer" }, + "userrating": { "type": "integer" }, + "seasonid": { "$ref": "Library.Id" }, + "ratings": { "type": "Video.Ratings" } + } + }, + "Video.Details.MusicVideo": { + "extends": "Video.Details.File", + "properties": { + "musicvideoid": { "$ref": "Library.Id", "required": true }, + "studio": { "$ref": "Array.String" }, + "year": { "type": "integer" }, + "album": { "type": "string" }, + "artist": { "$ref": "Array.String" }, + "genre": { "$ref": "Array.String" }, + "track": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "rating": { "type": "number" }, + "userrating": { "type": "integer" }, + "premiered": { "type": "string" }, + "uniqueid": { "$ref": "Media.UniqueID" } + } + }, + "PVR.Property.Name": { + "type": "string", + "enum": [ "available", "recording", "scanning" ] + }, + "PVR.Property.Value": { + "type": "object", + "properties": { + "available": { "type": "boolean" }, + "recording": { "type": "boolean" }, + "scanning": { "type": "boolean" } + } + }, + "PVR.ChannelGroup.Id": { + "type": [ + { "$ref": "Library.Id", "required": true }, + { "type": "string", "enum": [ "alltv", "allradio" ], "required": true } + ] + }, + "PVR.Fields.Broadcast": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "plot", "plotoutline", "starttime", + "endtime", "runtime", "progress", "progresspercentage", + "genre", "episodename", "episodenum", "episodepart", + "firstaired", "hastimer", "isactive", "parentalrating", + "wasactive", "thumbnail", "rating", "originaltitle", "cast", + "director", "writer", "year", "imdbnumber", "hastimerrule", + "hasrecording", "recording", "isseries", "isplayable", "clientid", + "hasreminder", "seasonnum" ] + } + }, + "PVR.Details.Broadcast": { + "extends": "Item.Details.Base", + "properties": { + "broadcastid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "plot": { "type": "string" }, + "plotoutline": { "type": "string" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "progress": { "type": "integer" }, + "progresspercentage": { "type": "number" }, + "genre": { "type": "string" }, + "episodename": { "type": "string" }, + "episodenum": { "type": "integer" }, + "episodepart": { "type": "integer" }, + "firstaired": { "type": "string" }, + "hastimer": { "type": "boolean" }, + "isactive": { "type": "boolean" }, + "parentalrating": { "type": "integer" }, + "wasactive": { "type": "boolean" }, + "thumbnail": { "type": "string" }, + "rating": { "type": "integer" }, + "originaltitle": { "type": "string" }, + "cast": { "type": "string" }, + "director": { "type": "string" }, + "writer": { "type": "string" }, + "year": { "type": "integer" }, + "imdbnumber": { "type": "integer" }, + "hastimerrule": { "type": "boolean" }, + "hasrecording": { "type": "boolean" }, + "recording": { "type": "string" }, + "isseries": { "type": "boolean" }, + "isplayable": { "type": "boolean", "description": "Deprecated - Use GetBroadcastIsPlayable instead" }, + "clientid": { "$ref": "Library.Id" }, + "hasreminder": { "type": "boolean" }, + "seasonnum": { "type": "integer" } + } + }, + "PVR.Fields.Channel": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "thumbnail", "channeltype", "hidden", "locked", "channel", "lastplayed", + "broadcastnow", "broadcastnext", "uniqueid", "icon", "channelnumber", + "subchannelnumber", "isrecording", "hasarchive", "clientid" ] + } + }, + "PVR.Details.Channel": { + "extends": "Item.Details.Base", + "properties": { + "channelid": { "$ref": "Library.Id", "required": true }, + "channel": { "type": "string" }, + "channeltype": { "$ref": "PVR.Channel.Type" }, + "hidden": { "type": "boolean" }, + "locked": { "type": "boolean" }, + "thumbnail": { "type": "string" }, + "lastplayed": { "type": "string" }, + "broadcastnow": { "$ref": "PVR.Details.Broadcast" }, + "broadcastnext": { "$ref": "PVR.Details.Broadcast" }, + "uniqueid": { "type": "integer", "required": true }, + "icon": { "type": "string" }, + "channelnumber": { "type": "integer" }, + "subchannelnumber": { "type": "integer" }, + "isrecording": { "type": "boolean" }, + "hasarchive": { "type": "boolean" }, + "clientid": { "$ref": "Library.Id" } + } + }, + "PVR.Details.ChannelGroup": { + "extends": "Item.Details.Base", + "properties": { + "channelgroupid": { "$ref": "Library.Id", "required": true }, + "channeltype": { "$ref": "PVR.Channel.Type", "required": true } + } + }, + "PVR.Details.ChannelGroup.Extended": { + "extends": "PVR.Details.ChannelGroup", + "properties": { + "limits": { "$ref": "List.LimitsReturned", "required": true }, + "channels": { "type": "array", + "items": { "$ref": "PVR.Details.Channel" } + } + } + }, + "PVR.Fields.Client": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "addonid", "supportstv", "supportsradio", "supportsepg", + "supportsrecordings", "supportstimers", "supportschannelgroups", + "supportschannelscan" ] + } + }, + "PVR.Details.Client": { + "extends": "Item.Details.Base", + "properties": { + "clientid": { "$ref": "Library.Id", "required": true }, + "addonid": { "type": "string" }, + "supportstv": { "type": "boolean" }, + "supportsradio": { "type": "boolean" }, + "supportsepg": { "type": "boolean" }, + "supportsrecordings": { "type": "boolean" }, + "supportstimers": { "type": "boolean" }, + "supportschannelgroups": { "type": "boolean" }, + "supportschannelscan": { "type": "boolean" } + } + }, + "PVR.TimerState": { + "type": "string", + "enum": [ "unknown", "new", "scheduled", "recording", "completed", + "aborted", "cancelled", "conflict_ok", "conflict_notok", + "error", "disabled" ] + }, + "PVR.Fields.Timer": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "summary", "channelid", "isradio", "istimerrule", "ismanual", + "starttime", "endtime", "runtime", "lifetime", "firstday", + "weekdays", "priority", "startmargin", "endmargin", "state", + "file", "directory", "preventduplicateepisodes", "startanytime", + "endanytime", "epgsearchstring", "fulltextepgsearch", "recordinggroup", + "maxrecordings", "epguid", "isreadonly", "isreminder", "clientid", "broadcastid" ] + } + }, + "PVR.Details.Timer": { + "extends": "Item.Details.Base", + "properties": { + "timerid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "summary": { "type": "string" }, + "channelid": { "$ref": "Library.Id" }, + "isradio": { "type": "boolean" }, + "istimerrule": { "type": "boolean" }, + "ismanual": { "type": "boolean" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "lifetime": { "type": "integer" }, + "firstday": { "type": "string" }, + "weekdays": { "type": "array", + "items": { "$ref": "Global.Weekday" }, + "uniqueItems": true + }, + "priority": { "type": "integer" }, + "startmargin": { "type": "integer" }, + "endmargin": { "type": "integer" }, + "state": { "$ref": "PVR.TimerState" }, + "file": { "type": "string" }, + "directory": { "type": "string" }, + "preventduplicateepisodes": { "type": "integer" }, + "startanytime": { "type": "boolean" }, + "endanytime": { "type": "boolean" }, + "epgsearchstring": { "type": "string" }, + "fulltextepgsearch": { "type": "boolean" }, + "recordinggroup": { "type": "integer" }, + "maxrecordings": { "type": "integer" }, + "epguid": { "type": "integer" }, + "isreadonly": { "type": "boolean" }, + "isreminder": { "type": "boolean" }, + "clientid": { "$ref": "Library.Id" }, + "broadcastid": { "$ref": "Library.Id" } + } + }, + "PVR.Fields.Recording": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "plot", "plotoutline", "genre", "playcount", + "resume", "channel", "starttime","endtime", "runtime", + "lifetime", "icon", "art", "streamurl", "file", + "directory", "radio", "isdeleted", "epgeventid", "channeluid", + "season", "episode", "showtitle", "clientid" ] + } + }, + "PVR.Details.Recording": { + "extends": "Item.Details.Base", + "properties": { + "recordingid": { "$ref": "Library.Id", "required": true }, + "title": { "type": "string" }, + "plot": { "type": "string" }, + "plotoutline": { "type": "string" }, + "genre": { "type": "string" }, + "playcount": { "type": "integer" }, + "resume": { "$ref": "Video.Resume" }, + "channel": { "type": "string" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" }, + "runtime": { "type": "integer" }, + "lifetime": { "type": "integer" }, + "icon": { "type": "string" }, + "art": { "$ref": "Media.Artwork" }, + "streamurl": { "type": "string" }, + "file": { "type": "string" }, + "directory": { "type": "string" }, + "radio": { "type": "boolean" }, + "isdeleted": { "type": "boolean" }, + "epgeventid": { "type": "integer" }, + "channeluid": { "type": "integer" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "showtitle": { "type": "string" }, + "clientid": { "$ref": "Library.Id" } + } + }, + "Textures.Details.Size": { + "type": "object", + "properties": { + "size": { "type": "integer", "description": "Size of the texture (1 == largest)" }, + "width": { "type": "integer", "description": "Width of texture" }, + "height": { "type": "integer", "description": "Height of texture" }, + "usecount": { "type": "integer", "description": "Number of uses" }, + "lastused": { "type": "string", "description": "Date of last use" } + } + }, + "Textures.Fields.Texture": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "url", "cachedurl", "lasthashcheck", "imagehash", "sizes" ] + } + }, + "Textures.Details.Texture": { + "type": "object", + "properties": { + "textureid": { "$ref": "Library.Id", "required": "true" }, + "url": { "type": "string", "description": "Original source URL" }, + "cachedurl": { "type": "string", "description": "Cached URL on disk" }, + "lasthashcheck": { "type": "string", "description": "Last time source was checked for changes" }, + "imagehash": { "type": "string", "description": "Hash of image" }, + "sizes": { "type": "array", "items": { "$ref": "Textures.Details.Size" } } + } + }, + "Profiles.Password": { + "type": "object", + "properties": { + "value": { "type": "string", "required": true, "description": "Password" }, + "encryption": { "type": "string", "description": "Password Encryption", "default": "md5", "enum": [ "none", "md5" ] } + } + }, + "Profiles.Fields.Profile": { + "extends": "Item.Fields.Base", + "items": { "type": "string", "enum": [ "thumbnail", "lockmode" ] } + }, + "Profiles.Details.Profile": { + "extends": "Item.Details.Base", + "properties": { + "thumbnail": { "type": "string" }, + "lockmode": { "type": "integer" } + } + }, + "List.Filter.Rule": { + "type": "object", + "properties": { + "operator": { "$ref": "List.Filter.Operators", "required": true }, + "value": { + "type": [ + { "type": "string", "required": true }, + { "type": "array", "items": { "type": "string" }, "required": true } + ], "required": true + } + } + }, + "List.Filter.Rule.Movies": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Movies", "required": true } + } + }, + "List.Filter.Rule.TVShows": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.TVShows", "required": true } + } + }, + "List.Filter.Rule.Episodes": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Episodes", "required": true } + } + }, + "List.Filter.Rule.MusicVideos": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.MusicVideos", "required": true } + } + }, + "List.Filter.Rule.Artists": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Artists", "required": true } + } + }, + "List.Filter.Rule.Albums": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Albums", "required": true } + } + }, + "List.Filter.Rule.Songs": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Songs", "required": true } + } + }, + "List.Filter.Rule.Textures": { + "extends": "List.Filter.Rule", + "properties": { + "field": { "$ref": "List.Filter.Fields.Textures", "required": true } + } + }, + "List.Filter.Movies": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Movies" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Movies" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Movies" } + ] + }, + "List.Filter.TVShows": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.TVShows" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.TVShows" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.TVShows" } + ] + }, + "List.Filter.Episodes": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Episodes" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Episodes" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Episodes" } + ] + }, + "List.Filter.MusicVideos": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.MusicVideos" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.MusicVideos" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.MusicVideos" } + ] + }, + "List.Filter.Artists": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Artists" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Artists" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Artists" } + ] + }, + "List.Filter.Albums": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Albums" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Albums" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Albums" } + ] + }, + "List.Filter.Songs": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Songs" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Songs" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Songs" } + ] + }, + "List.Filter.Textures": { + "type": [ + { "type": "object", + "properties": { + "and": { "type": "array", + "items": { "$ref": "List.Filter.Textures" }, + "minItems": 1, "required": true + } + } + }, + { "type": "object", + "properties": { + "or": { "type": "array", + "items": { "$ref": "List.Filter.Textures" }, + "minItems": 1, "required": true + } + } + }, + { "$ref": "List.Filter.Rule.Textures" } + ] + }, + "List.Item.Base": { + "extends": [ "Video.Details.File", "Audio.Details.Media" ], + "properties": { + "id": { "$ref": "Library.Id" }, + "type": { "type": "string", "enum": [ "unknown", "movie", "episode", "musicvideo", "song", "picture", "channel", "recording" ] }, + "albumartist": { "$ref": "Array.String" }, + "album": { "type": "string" }, + "track": { "type": "integer" }, + "duration": { "type": "integer" }, + "comment": { "type": "string" }, + "lyrics": { "type": "string" }, + "musicbrainztrackid": { "type": "string" }, + "musicbrainzartistid": { "$ref": "Array.String" }, + "trailer": { "type": "string" }, + "tagline": { "type": "string" }, + "plotoutline": { "type": "string" }, + "originaltitle": { "type": "string" }, + "writer": { "$ref": "Array.String" }, + "studio": { "$ref": "Array.String" }, + "mpaa": { "type": "string" }, + "cast": { "$ref": "Video.Cast" }, + "country": { "$ref": "Array.String" }, + "imdbnumber": { "type": "string" }, + "premiered": { "type": "string" }, + "productioncode": { "type": "string" }, + "set": { "type": "string" }, + "showlink": { "$ref": "Array.String" }, + "top250": { "type": "integer" }, + "votes": { "type": "string" }, + "firstaired": { "type": "string" }, + "season": { "type": "integer" }, + "episode": { "type": "integer" }, + "showtitle": { "type": "string" }, + "albumid": { "$ref": "Library.Id" }, + "setid": { "$ref": "Library.Id" }, + "tvshowid": { "$ref": "Library.Id" }, + "watchedepisodes": { "type": "integer" }, + "disc": { "type": "integer" }, + "tag": { "$ref": "Array.String" }, + "albumartistid": { "$ref": "Array.Integer" }, + "uniqueid": { "$ref": "Media.UniqueID" }, + "episodeguide": { "type": "string" }, + "sorttitle": { "type": "string" }, + "description": { "type": "string" }, + "theme": { "$ref": "Array.String" }, + "mood": { "$ref": "Array.String" }, + "style": { "$ref": "Array.String" }, + "albumlabel": { "type": "string" }, + "specialsortseason": { "type": "integer" }, + "specialsortepisode": { "type": "integer" }, + "compilation": { "type": "boolean" }, + "releasetype": { "$ref": "Audio.Album.ReleaseType" }, + "albumreleasetype": { "$ref": "Audio.Album.ReleaseType" }, + "contributors": { "$ref": "Audio.Contributors" }, + "displaycomposer": { "type": "string"}, + "displayconductor": { "type": "string"}, + "displayorchestra": { "type": "string"}, + "displaylyricist": { "type": "string"}, + "mediapath": { "type": "string", "description": "Media source path that identifies the item"}, + "dynpath": { "type": "string", "description": "An experimental property for debug purposes, often same as mediapath but when different gives the actual file playing that should also be in file property"}, + "isboxset": { "type": "boolean" }, + "totaldiscs": { "type": "integer" }, + "disctitle": { "type": "string" }, + "releasedate": { "type": "string" }, + "originaldate": { "type": "string" }, + "bpm": { "type": "integer" }, + "bitrate": { "type": "integer" }, + "samplerate": { "type": "integer" }, + "channels": { "type": "integer"}, + "albumstatus": { "type": "string" }, + "customproperties": { "$ref": "Item.CustomProperties" } + } + }, + "List.Fields.All": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "artist", "albumartist", "genre", "year", "rating", + "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", + "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "director", "trailer", "tagline", "plot", + "plotoutline", "originaltitle", "lastplayed", "writer", "studio", + "mpaa", "cast", "country", "imdbnumber", "premiered", "productioncode", + "runtime", "set", "showlink", "streamdetails", "top250", "votes", + "firstaired", "season", "episode", "showtitle", "thumbnail", "file", + "resume", "artistid", "albumid", "tvshowid", "setid", "watchedepisodes", + "disc", "tag", "art", "genreid", "displayartist", "albumartistid", + "description", "theme", "mood", "style", "albumlabel", "sorttitle", + "episodeguide", "uniqueid", "dateadded", "channel", "channeltype", "hidden", + "locked", "channelnumber", "subchannelnumber", "starttime", "endtime", + "specialsortseason", "specialsortepisode", "compilation", "releasetype", + "albumreleasetype", "contributors", "displaycomposer", "displayconductor", + "displayorchestra", "displaylyricist", "userrating", "votes", "sortartist", + "musicbrainzreleasegroupid", "mediapath", "dynpath", "isboxset", "totaldiscs", + "disctitle", "releasedate", "originaldate", "bpm", "bitrate", "samplerate", + "channels", "albumstatus", "datemodified", "datenew", "customproperties", + "albumduration"] + } + }, + "List.Item.All": { + "extends": "List.Item.Base", + "properties": { + "channel": { "type": "string" }, + "channeltype": { "$ref": "PVR.Channel.Type" }, + "hidden": { "type": "boolean" }, + "locked": { "type": "boolean" }, + "channelnumber": { "type": "integer" }, + "subchannelnumber": { "type": "integer" }, + "starttime": { "type": "string" }, + "endtime": { "type": "string" } + } + }, + "List.Fields.Files": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "title", "artist", "albumartist", "genre", "year", "rating", + "album", "track", "duration", "comment", "lyrics", "musicbrainztrackid", + "musicbrainzartistid", "musicbrainzalbumid", "musicbrainzalbumartistid", + "playcount", "fanart", "director", "trailer", "tagline", "plot", + "plotoutline", "originaltitle", "lastplayed", "writer", "studio", + "mpaa", "cast", "country", "imdbnumber", "premiered", "productioncode", + "runtime", "set", "showlink", "streamdetails", "top250", "votes", + "firstaired", "season", "episode", "showtitle", "thumbnail", "file", + "resume", "artistid", "albumid", "tvshowid", "setid", "watchedepisodes", + "disc", "tag", "art", "genreid", "displayartist", "albumartistid", + "description", "theme", "mood", "style", "albumlabel", "sorttitle", + "episodeguide", "uniqueid", "dateadded", "size", "lastmodified", "mimetype", + "specialsortseason", "specialsortepisode", "sortartist", "musicbrainzreleasegroupid", + "isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm", + "bitrate", "samplerate", "channels", "datemodified", "datenew", "customproperties", + "albumduration", "userrating"] + } + }, + "List.Item.File": { + "extends": "List.Item.Base", + "properties": { + "file": { "type": "string", "required": true }, + "filetype": { "type": "string", "enum": [ "file", "directory" ], "required": true }, + "size": { "type": "integer", "description": "Size of the file in bytes" }, + "lastmodified": { "type": "string" }, + "mimetype": { "type": "string" } + } + }, + "List.Items.Sources": { + "type": "array", + "items": { + "extends": "Item.Details.Base", + "properties": { + "file": { "type": "string", "required": true } + } + } + }, + "Addon.Content": { + "type": "string", + "enum": [ "unknown", "video", "audio", "image", "executable" ], + "default": "unknown" + }, + "Addon.Fields": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "name", "version", "summary", "description", "path", "author", "thumbnail", "disclaimer", "fanart", + "dependencies", "broken", "extrainfo", "rating", "enabled", "installed", "deprecated" ] + } + }, + "Addon.Details": { + "extends": "Item.Details.Base", + "properties": { + "addonid": { "type": "string", "required": true }, + "type": { "$ref": "Addon.Types", "required": true }, + "name": { "type": "string" }, + "version": { "type": "string" }, + "summary": { "type": "string" }, + "description": { "type": "string" }, + "path": { "type": "string" }, + "author": { "type": "string" }, + "thumbnail": { "type": "string" }, + "disclaimer": { "type": "string" }, + "fanart": { "type": "string" }, + "dependencies": { "type": "array", + "items": { "type": "object", + "properties": { + "addonid": { "type": "string", "required": true }, + "version": { "type": "string", "required": true }, + "optional": { "type": "boolean", "required": true } + } + } + }, + "broken": { "type": [ "boolean", "string" ] }, + "extrainfo": { "type": "array", + "items": { "type": "object", + "properties": { + "key": { "type": "string", "required": true }, + "value": { "type": "string", "required": true } + } + } + }, + "rating": { "type": "integer" }, + "enabled": { "type": "boolean" }, + "installed": { "type": "boolean" }, + "deprecated": { "type": [ "boolean", "string" ] } + } + }, + "GUI.Stereoscopy.Mode": { + "type": "object", + "properties": { + "mode": { "type": "string", "required": true, "enum": [ "off", "split_vertical", "split_horizontal", "row_interleaved", "hardware_based", "anaglyph_cyan_red", "anaglyph_green_magenta", "anaglyph_yellow_blue", "monoscopic" ] }, + "label": { "type": "string", "required": true } + } + }, + "GUI.Property.Name": { + "type": "string", + "enum": [ "currentwindow", "currentcontrol", "skin", "fullscreen", "stereoscopicmode" ] + }, + "GUI.Property.Value": { + "type": "object", + "properties": { + "currentwindow": { "type": "object", + "properties": { + "id": { "type": "integer", "required": true }, + "label": { "type": "string", "required": true } + } + }, + "currentcontrol": { "type": "object", + "properties": { + "label": { "type": "string", "required": true } + } + }, + "skin": { "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "name": { "type": "string" } + } + }, + "fullscreen": { "type": "boolean" }, + "stereoscopicmode": { "$ref": "GUI.Stereoscopy.Mode" } + } + }, + "System.Property.Name": { + "type": "string", + "enum": [ "canshutdown", "cansuspend", "canhibernate", "canreboot" ] + }, + "System.Property.Value": { + "type": "object", + "properties": { + "canshutdown": { "type": "boolean" }, + "cansuspend": { "type": "boolean" }, + "canhibernate": { "type": "boolean" }, + "canreboot": { "type": "boolean" } + } + }, + "Application.Property.Name": { + "type": "string", + "enum": [ "volume", "muted", "name", "version", "volume", "sorttokens", "language" ] + }, + "Application.Property.Value": { + "type": "object", + "properties": { + "volume": { "type": "integer", "minimum": 0, "maximum": 100 }, + "muted": { "type": "boolean" }, + "name": { "type": "string", "minLength": 1 }, + "version": { "type": "object", + "properties": { + "major": { "type": "integer", "minimum": 0, "required": true }, + "minor": { "type": "integer", "minimum": 0, "required": true }, + "revision": { "type": [ "string", "integer" ] }, + "tag": { "type": "string", "enum": [ "prealpha", "alpha", "beta", "releasecandidate", "stable" ], "required": true }, + "tagversion": { "type": "string" } + } + }, + "sorttokens": { "$ref": "Array.String", "description": "Articles ignored during sorting when ignorearticle is enabled." }, + "language": { "type": "string", "minLength": 1, "description": "Current language code and region e.g. en_GB" } + } + }, + "Favourite.Fields.Favourite": { + "extends": "Item.Fields.Base", + "items": { "type": "string", + "enum": [ "window", "windowparameter", "thumbnail", "path" ] + } + }, + "Favourite.Type": { + "type": "string", + "enum": [ "media", "window", "script", "androidapp", "unknown" ] + }, + "Favourite.Details.Favourite": { + "type": "object", + "properties": { + "title": { "type": "string", "required": true }, + "type": { "$ref": "Favourite.Type", "required": true }, + "path": { "type": "string" }, + "window": { "type": "string" }, + "windowparameter": { "type": "string" }, + "thumbnail": { "type": "string" } + }, + "additionalProperties": false + }, + "Setting.Type": { + "type": "string", + "enum": [ + "boolean", "integer", "number", "string", "action", "list", + "path", "addon", "date", "time" + ] + }, + "Setting.Level": { + "type": "string", + "enum": [ "basic", "standard", "advanced", "expert" ] + }, + "Setting.Value": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "integer", "required": true }, + { "type": "number", "required": true }, + { "type": "string", "required": true } + ] + }, + "Setting.Value.List": { + "type": "array", + "items": { "$ref": "Setting.Value" } + }, + "Setting.Value.Extended": { + "type": [ + { "type": "boolean", "required": true }, + { "type": "integer", "required": true }, + { "type": "number", "required": true }, + { "type": "string", "required": true }, + { "$ref": "Setting.Value.List", "required": true } + ] + }, + "Setting.Details.ControlBase": { + "type": "object", + "properties": { + "type": { "type": "string", "required": true }, + "format": { "type": "string", "required": true }, + "delayed": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlCheckmark": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "toggle" ] }, + "format": { "type": "string", "required": true, "enum": [ "boolean" ] } + } + }, + "Setting.Details.ControlSpinner": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "spinner" ] }, + "formatlabel": { "type": "string" }, + "minimumlabel": { "type": "string" } + } + }, + "Setting.Details.ControlHeading": { + "extends": "Setting.Details.ControlBase", + "properties": { + "heading": { "type": "string" } + } + }, + "Setting.Details.ControlEdit": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "edit" ] }, + "hidden": { "type": "boolean", "required": true }, + "verifynewvalue": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlButton": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "button" ] } + } + }, + "Setting.Details.ControlList": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "list" ] }, + "multiselect": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlSlider": { + "extends": "Setting.Details.ControlHeading", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "slider" ] }, + "formatlabel": { "type": "string", "required": true }, + "popup": { "type": "boolean", "required": true } + } + }, + "Setting.Details.ControlRange": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "range" ] }, + "formatlabel": { "type": "string", "required": true }, + "formatvalue": { "type": "string", "required": true } + } + }, + "Setting.Details.ControlLabel": { + "extends": "Setting.Details.ControlBase", + "properties": { + "type": { "type": "string", "required": true, "enum": [ "label" ] }, + "format": { "type": "string", "required": true, "enum": [ "string" ] } + } + }, + "Setting.Details.Control": { + "type": [ + { "$ref": "Setting.Details.ControlCheckmark", "required": true }, + { "$ref": "Setting.Details.ControlSpinner", "required": true }, + { "$ref": "Setting.Details.ControlEdit", "required": true }, + { "$ref": "Setting.Details.ControlButton", "required": true }, + { "$ref": "Setting.Details.ControlList", "required": true }, + { "$ref": "Setting.Details.ControlSlider", "required": true }, + { "$ref": "Setting.Details.ControlRange", "required": true }, + { "$ref": "Setting.Details.ControlLabel", "required": true } + ] + }, + "Setting.Details.Base": { + "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "label": { "type": "string", "required": true }, + "help": { "type": "string" } + } + }, + "Setting.Details.SettingBase": { + "extends": "Setting.Details.Base", + "properties": { + "type": { "$ref": "Setting.Type", "required": true }, + "enabled": { "type": "boolean", "required": true }, + "level": { "$ref": "Setting.Level", "required": true }, + "parent": { "type": "string" }, + "control": { "$ref": "Setting.Details.Control" } + }, + "additionalProperties": false + }, + "Setting.Details.SettingBool": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "boolean", "required": true }, + "default": { "type": "boolean", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingInt": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "integer", "required": true }, + "default": { "type": "integer", "required": true }, + "minimum": { "type": "integer" }, + "step": { "type": "integer" }, + "maximum": { "type": "integer" }, + "options": { "type": "array", + "items": { "type": "object", + "properties": { + "label": { "type": "string", "required": true }, + "value": { "type": "integer", "required": true } + } + } + } + }, + "additionalProperties": false + }, + "Setting.Details.SettingNumber": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "number", "required": true }, + "default": { "type": "number", "required": true }, + "minimum": { "type": "number", "required": true }, + "step": { "type": "number", "required": true }, + "maximum": { "type": "number", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingString": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "type": "string", "required": true }, + "default": { "type": "string", "required": true }, + "allowempty": { "type": "boolean", "required": true }, + "options": { "type": "array", + "items": { "type": "object", + "properties": { + "label": { "type": "string", "required": true }, + "value": { "type": "string", "required": true } + } + } + } + } + }, + "Setting.Details.SettingAction": { + "extends": "Setting.Details.SettingBase", + "properties": { + "data": { "type": "string", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingList": { + "extends": "Setting.Details.SettingBase", + "properties": { + "value": { "$ref": "Setting.Value.List", "required": true }, + "default": { "$ref": "Setting.Value.List", "required": true }, + "elementtype": { "$ref": "Setting.Type", "required": true }, + "definition": { "$ref": "Setting.Details.Setting", "required": true }, + "delimiter": { "type": "string", "required": true }, + "minimumItems": { "type": "integer" }, + "maximumItems": { "type": "integer" } + }, + "additionalProperties": false + }, + "Setting.Details.SettingPath": { + "extends": "Setting.Details.SettingString", + "properties": { + "writable": { "type": "boolean", "required": true }, + "sources": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + }, + "Setting.Details.SettingAddon": { + "extends": "Setting.Details.SettingString", + "properties": { + "addontype": { "$ref": "Addon.Types", "required": true } + }, + "additionalProperties": false + }, + "Setting.Details.SettingDate": { + "extends": "Setting.Details.SettingString", + "additionalProperties": false + }, + "Setting.Details.SettingTime": { + "extends": "Setting.Details.SettingString", + "additionalProperties": false + }, + "Setting.Details.Setting": { + "type": [ + { "$ref": "Setting.Details.SettingBool", "required": true }, + { "$ref": "Setting.Details.SettingInt", "required": true }, + { "$ref": "Setting.Details.SettingNumber", "required": true }, + { "$ref": "Setting.Details.SettingString", "required": true }, + { "$ref": "Setting.Details.SettingAction", "required": true }, + { "$ref": "Setting.Details.SettingList", "required": true }, + { "$ref": "Setting.Details.SettingPath", "required": true }, + { "$ref": "Setting.Details.SettingAddon", "required": true }, + { "$ref": "Setting.Details.SettingDate", "required": true }, + { "$ref": "Setting.Details.SettingTime", "required": true } + ] + }, + "Setting.Details.Group": { + "type": "object", + "properties": { + "id": { "type": "string", "required": true, "minLength": 1 }, + "settings": { + "type": "array", + "items": { "$ref": "Setting.Details.Setting" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "Setting.Details.Category": { + "extends": "Setting.Details.Base", + "properties": { + "groups": { + "type": "array", + "items": { "$ref": "Setting.Details.Group" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + }, + "Setting.Details.Section": { + "extends": "Setting.Details.Base", + "properties": { + "categories": { + "type": "array", + "items": { "$ref": "Setting.Details.Category" }, + "minItems": 1, + "uniqueItems": true + } + }, + "additionalProperties": false + } +} diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt new file mode 100644 index 0000000..40bfd87 --- /dev/null +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -0,0 +1 @@ +JSONRPC_VERSION 13.0.0 diff --git a/xbmc/interfaces/legacy/Addon.cpp b/xbmc/interfaces/legacy/Addon.cpp new file mode 100644 index 0000000..4b4db1e --- /dev/null +++ b/xbmc/interfaces/legacy/Addon.cpp @@ -0,0 +1,243 @@ +/* + * 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 "Addon.h" + +#include "GUIUserMessages.h" +#include "LanguageHook.h" +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/gui/GUIDialogAddonSettings.h" +#include "addons/settings/AddonSettings.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "utils/StringUtils.h" + +using namespace ADDON; + +namespace XBMCAddon +{ + namespace xbmcaddon + { + String Addon::getDefaultId() { return languageHook == NULL ? emptyString : languageHook->GetAddonId(); } + + String Addon::getAddonVersion() { return languageHook == NULL ? emptyString : languageHook->GetAddonVersion(); } + + bool Addon::UpdateSettingInActiveDialog(const char* id, const String& value) + { + ADDON::AddonPtr addon(pAddon); + if (!CServiceBroker::GetGUI()->GetWindowManager().IsWindowActive(WINDOW_DIALOG_ADDON_SETTINGS)) + return false; + + CGUIDialogAddonSettings* dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogAddonSettings>(WINDOW_DIALOG_ADDON_SETTINGS); + if (dialog->GetCurrentAddonID() != addon->ID()) + return false; + + CGUIMessage message(GUI_MSG_SETTING_UPDATED, 0, 0); + std::vector<std::string> params; + params.emplace_back(id); + params.push_back(value); + message.SetStringParams(params); + message.SetParam1(ADDON_SETTINGS_ID); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message, WINDOW_DIALOG_ADDON_SETTINGS); + + return true; + } + + Addon::Addon(const char* cid) + { + String id(cid ? cid : emptyString); + + // if the id wasn't passed then get the id from + // the global dictionary + if (id.empty()) + id = getDefaultId(); + + // if we still don't have an id then bail + if (id.empty()) + throw AddonException("No valid addon id could be obtained. None was passed and the script " + "wasn't executed in a normal Kodi manner."); + + if (!CServiceBroker::GetAddonMgr().GetAddon(id, pAddon, OnlyEnabled::CHOICE_YES)) + throw AddonException("Unknown addon id '%s'.", id.c_str()); + + CServiceBroker::GetAddonMgr().AddToUpdateableAddons(pAddon); + } + + Addon::~Addon() + { + CServiceBroker::GetAddonMgr().RemoveFromUpdateableAddons(pAddon); + } + + String Addon::getLocalizedString(int id) + { + return g_localizeStrings.GetAddonString(pAddon->ID(), id); + } + + Settings* Addon::getSettings() + { + return new Settings(pAddon->GetSettings()); + } + + String Addon::getSetting(const char* id) + { + return pAddon->GetSetting(id); + } + + bool Addon::getSettingBool(const char* id) + { + bool value = false; + if (!pAddon->GetSettingBool(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + return value; + } + + int Addon::getSettingInt(const char* id) + { + int value = 0; + if (!pAddon->GetSettingInt(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + return value; + } + + double Addon::getSettingNumber(const char* id) + { + double value = 0.0; + if (!pAddon->GetSettingNumber(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + return value; + } + + String Addon::getSettingString(const char* id) + { + std::string value; + if (!pAddon->GetSettingString(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + return value; + } + + void Addon::setSetting(const char* id, const String& value) + { + DelayedCallGuard dcguard(languageHook); + ADDON::AddonPtr addon(pAddon); + if (!UpdateSettingInActiveDialog(id, value)) + { + addon->UpdateSetting(id, value); + addon->SaveSettings(); + } + } + + bool Addon::setSettingBool(const char* id, bool value) + { + DelayedCallGuard dcguard(languageHook); + ADDON::AddonPtr addon(pAddon); + if (UpdateSettingInActiveDialog(id, value ? "true" : "false")) + return true; + + if (!addon->UpdateSettingBool(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + addon->SaveSettings(); + + return true; + } + + bool Addon::setSettingInt(const char* id, int value) + { + DelayedCallGuard dcguard(languageHook); + ADDON::AddonPtr addon(pAddon); + if (UpdateSettingInActiveDialog(id, std::to_string(value))) + return true; + + if (!addon->UpdateSettingInt(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + addon->SaveSettings(); + + return true; + } + + bool Addon::setSettingNumber(const char* id, double value) + { + DelayedCallGuard dcguard(languageHook); + ADDON::AddonPtr addon(pAddon); + if (UpdateSettingInActiveDialog(id, StringUtils::Format("{:f}", value))) + return true; + + if (!addon->UpdateSettingNumber(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + addon->SaveSettings(); + + return true; + } + + bool Addon::setSettingString(const char* id, const String& value) + { + DelayedCallGuard dcguard(languageHook); + ADDON::AddonPtr addon(pAddon); + if (UpdateSettingInActiveDialog(id, value)) + return true; + + if (!addon->UpdateSettingString(id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type"); + + addon->SaveSettings(); + + return true; + } + + void Addon::openSettings() + { + DelayedCallGuard dcguard(languageHook); + // show settings dialog + ADDON::AddonPtr addon(pAddon); + CGUIDialogAddonSettings::ShowForAddon(addon); + } + + String Addon::getAddonInfo(const char* id) + { + if (StringUtils::CompareNoCase(id, "author") == 0) + return pAddon->Author(); + else if (StringUtils::CompareNoCase(id, "changelog") == 0) + return pAddon->ChangeLog(); + else if (StringUtils::CompareNoCase(id, "description") == 0) + return pAddon->Description(); + else if (StringUtils::CompareNoCase(id, "disclaimer") == 0) + return pAddon->Disclaimer(); + else if (StringUtils::CompareNoCase(id, "fanart") == 0) + return pAddon->FanArt(); + else if (StringUtils::CompareNoCase(id, "icon") == 0) + return pAddon->Icon(); + else if (StringUtils::CompareNoCase(id, "id") == 0) + return pAddon->ID(); + else if (StringUtils::CompareNoCase(id, "name") == 0) + return pAddon->Name(); + else if (StringUtils::CompareNoCase(id, "path") == 0) + return pAddon->Path(); + else if (StringUtils::CompareNoCase(id, "profile") == 0) + return pAddon->Profile(); + else if (StringUtils::CompareNoCase(id, "stars") == 0) + return StringUtils::Format("-1"); + else if (StringUtils::CompareNoCase(id, "summary") == 0) + return pAddon->Summary(); + else if (StringUtils::CompareNoCase(id, "type") == 0) + return ADDON::CAddonInfo::TranslateType(pAddon->Type()); + else if (StringUtils::CompareNoCase(id, "version") == 0) + return pAddon->Version().asString(); + else + throw AddonException("'%s' is an invalid Id", id); + } + } +} diff --git a/xbmc/interfaces/legacy/Addon.h b/xbmc/interfaces/legacy/Addon.h new file mode 100644 index 0000000..97c598f --- /dev/null +++ b/xbmc/interfaces/legacy/Addon.h @@ -0,0 +1,483 @@ +/* + * 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 "AddonClass.h" +#include "AddonString.h" +#include "Exception.h" +#include "Settings.h" +#include "addons/IAddon.h" + +namespace XBMCAddon +{ + namespace xbmcaddon + { + XBMCCOMMONS_STANDARD_EXCEPTION(AddonException); + + /// + /// \addtogroup python_xbmcaddon + /// @{ + /// @brief **Kodi's addon class.** + /// + /// Offers classes and functions that manipulate the add-on settings, + /// information and localization. + /// + ///------------------------------------------------------------------------- + /// + /// \python_class{ xbmcaddon.Addon([id]) } + /// + /// Creates a new AddOn class. + /// + /// @param id [opt] string - id of the addon as + /// specified in [addon.xml](http://kodi.wiki/view/Addon.xml) + /// + /// @note Specifying the addon id is not needed.\n + /// Important however is that the addon folder has the same name as the AddOn + /// id provided in [addon.xml](http://kodi.wiki/view/Addon.xml).\n + /// You can optionally specify the addon id from another installed addon to + /// retrieve settings from it. + /// + /// + ///------------------------------------------------------------------------- + /// @python_v13 + /// **id** is optional as it will be auto detected for this add-on instance. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon = xbmcaddon.Addon() + /// self.Addon = xbmcaddon.Addon('script.foo.bar') + /// .. + /// ~~~~~~~~~~~~~ + /// + class Addon : public AddonClass + { + ADDON::AddonPtr pAddon; + + String getDefaultId(); + + String getAddonVersion(); + + bool UpdateSettingInActiveDialog(const char* id, const String& value); + + public: + explicit Addon(const char* id = NULL); + ~Addon() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getLocalizedString(id) } + /// Returns an addon's localized 'string'. + /// + /// @param id integer - id# for string you want to + /// localize. + /// @return Localized 'string' + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 + /// **id** is optional as it will be auto detected for this add-on instance. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// locstr = self.Addon.getLocalizedString(32000) + /// .. + /// ~~~~~~~~~~~~~ + /// + getLocalizedString(...); +#else + String getLocalizedString(int id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSettings() } + /// Returns a wrapper around the addon's settings. + /// + /// @return @ref python_settings wrapper + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings = self.Addon.getSettings() + /// .. + /// ~~~~~~~~~~~~~ + /// + getSettings(...); +#else + Settings* getSettings(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSetting(id) } + /// Returns the value of a setting as string. + /// + /// @param id string - id of the setting that the module + /// needs to access. + /// @return Setting as a string + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 + /// **id** is optional as it will be auto detected for this add-on instance. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// apikey = self.Addon.getSetting('apikey') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSetting(...); +#else + String getSetting(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSettingBool(id) } + /// Returns the value of a setting as a boolean. + /// + /// @param id string - id of the setting that the module + /// needs to access. + /// @return Setting as a boolean + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.getBool()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// enabled = self.Addon.getSettingBool('enabled') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSettingBool(...); +#else + bool getSettingBool(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSettingInt(id) } + /// Returns the value of a setting as an integer. + /// + /// @param id string - id of the setting that the module + /// needs to access. + /// @return Setting as an integer + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.getInt()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// max = self.Addon.getSettingInt('max') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSettingInt(...); +#else + int getSettingInt(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSettingNumber(id) } + /// Returns the value of a setting as a floating point number. + /// + /// @param id string - id of the setting that the module + /// needs to access. + /// @return Setting as a floating point number + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.getNumber()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// max = self.Addon.getSettingNumber('max') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSettingNumber(...); +#else + double getSettingNumber(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).getSettingString(id) } + /// Returns the value of a setting as a string. + /// + /// @param id string - id of the setting that the module + /// needs to access. + /// @return Setting as a string + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.getString()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// apikey = self.Addon.getSettingString('apikey') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSettingString(...); +#else + String getSettingString(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).setSetting(id, value) } + /// Sets a script setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value string - value of the setting. + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 + /// **id** is optional as it will be auto detected for this add-on instance. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.setSetting(id='username', value='teamkodi') + /// .. + /// ~~~~~~~~~~~~~ + /// + setSetting(...); +#else + void setSetting(const char* id, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).setSettingBool(id, value) } + /// Sets a script setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value boolean - value of the setting. + /// @return True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.setBool()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.setSettingBool(id='enabled', value=True) + /// .. + /// ~~~~~~~~~~~~~ + /// + setSettingBool(...); +#else + bool setSettingBool(const char* id, bool value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).setSettingInt(id, value) } + /// Sets a script setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value integer - value of the setting. + /// @return True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.setInt()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.setSettingInt(id='max', value=5) + /// .. + /// ~~~~~~~~~~~~~ + /// + setSettingInt(...); +#else + bool setSettingInt(const char* id, int value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).setSettingNumber(id, value) } + /// Sets a script setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value float - value of the setting. + /// @return True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.setNumber()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.setSettingNumber(id='max', value=5.5) + /// .. + /// ~~~~~~~~~~~~~ + /// + setSettingNumber(...); +#else + bool setSettingNumber(const char* id, double value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).setSettingString(id, value) } + /// Sets a script setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value string or unicode - value of the setting. + /// @return True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 + /// New function added. + /// @python_v20 Deprecated. Use **Settings.setString()** instead. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.setSettingString(id='username', value='teamkodi') + /// .. + /// ~~~~~~~~~~~~~ + /// + setSettingString(...); +#else + bool setSettingString(const char* id, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// @brief \python_func{ xbmcaddon.Addon([id]).openSettings() } + /// Opens this scripts settings dialog. + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.Addon.openSettings() + /// .. + /// ~~~~~~~~~~~~~ + /// + openSettings(); +#else + void openSettings(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcaddon + /// \anchor python_xbmcaddon_Addon + /// @brief \python_func{ xbmcaddon.Addon([id]).getAddonInfo(id) } + /// Returns the value of an addon property as a string. + /// + /// @param id string - id of the property that the + /// module needs to access. + /// @par Choices for the property are + /// | | | | | + /// |:-----------:|:-----------:|:-----------:|:-----------:| + /// | author | changelog | description | disclaimer | + /// | fanart | icon | id | name | + /// | path | profile | stars | summary | + /// | type | version | | | + /// @return AddOn property as a string + /// + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// version = self.Addon.getAddonInfo('version') + /// .. + /// ~~~~~~~~~~~~~ + /// + getAddonInfo(...); +#else + String getAddonInfo(const char* id); +#endif + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/AddonCallback.cpp b/xbmc/interfaces/legacy/AddonCallback.cpp new file mode 100644 index 0000000..c52ce80 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonCallback.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +#include "AddonCallback.h" + +namespace XBMCAddon +{ + // need a place to put the vtab + AddonCallback::~AddonCallback() = default; + + void AddonCallback::invokeCallback(Callback* callback) + { + if (callback) + { + if (hasHandler()) + handler->invokeCallback(callback); + else + callback->executeCallback(); + } + } +} + diff --git a/xbmc/interfaces/legacy/AddonCallback.h b/xbmc/interfaces/legacy/AddonCallback.h new file mode 100644 index 0000000..dee5074 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonCallback.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonClass.h" +#include "CallbackFunction.h" +#include "CallbackHandler.h" +#include "LanguageHook.h" + +namespace XBMCAddon +{ + + /** + * This class is the superclass for all API classes that are expected + * to be able to handle cross-language polymorphism. + */ + class AddonCallback : public AddonClass + { + protected: + AddonClass::Ref<CallbackHandler> handler; + + bool hasHandler() { return handler.isNotNull(); } + + inline AddonCallback() : handler(NULL) + { + // if there is a LanguageHook, it should be set already. + if (languageHook != NULL) + setHandler(languageHook->GetCallbackHandler()); + } + public: + + ~AddonCallback() override; + + inline void setHandler(CallbackHandler* _handler) { handler = _handler; } + void invokeCallback(Callback* callback); + }; +} diff --git a/xbmc/interfaces/legacy/AddonClass.cpp b/xbmc/interfaces/legacy/AddonClass.cpp new file mode 100644 index 0000000..ffdf3d4 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonClass.cpp @@ -0,0 +1,90 @@ +/* + * 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 "AddonClass.h" +#ifdef XBMC_ADDON_DEBUG_MEMORY +#include "utils/log.h" +#endif +#include "LanguageHook.h" +#include "AddonUtils.h" + +using namespace XBMCAddonUtils; + +namespace XBMCAddon +{ + // need a place to put the vtab + AddonClass::~AddonClass() + { + m_isDeallocating= true; + + if (languageHook != NULL) + languageHook->Release(); + +#ifdef XBMC_ADDON_DEBUG_MEMORY + isDeleted = false; +#endif + } + + AddonClass::AddonClass() : refs(0L), + languageHook(NULL) + { +#ifdef XBMC_ADDON_DEBUG_MEMORY + isDeleted = false; +#endif + + // check to see if we have a language hook that was prepared for this instantiation + languageHook = LanguageHook::GetLanguageHook(); + if (languageHook != NULL) + { + languageHook->Acquire(); + + // here we assume the language hook was set for the single instantiation of + // this AddonClass (actually - its subclass - but whatever). So we + // will now reset the Tls. This avoids issues if the constructor of the + // subclass throws an exception. + LanguageHook::ClearLanguageHook(); + } + } + +#ifdef XBMC_ADDON_DEBUG_MEMORY + void AddonClass::Release() const + { + if (isDeleted) + CLog::Log(LOGERROR, "NEWADDON REFCNT Releasing dead class {} 0x{:x}", GetClassname(), + (long)(((void*)this))); + + long ct = --refs; +#ifdef LOG_LIFECYCLE_EVENTS + CLog::Log(LOGDEBUG, "NEWADDON REFCNT decrementing to {} on {} 0x{:x}", refs.load(), + GetClassname(), (long)(((void*)this))); +#endif + if(ct == 0) + { + const_cast<AddonClass*>(this)->isDeleted = true; + // we're faking a delete but not doing it so call the destructor explicitly + this->~AddonClass(); + } + } + + void AddonClass::Acquire() const + { + if (isDeleted) + CLog::Log(LOGERROR, "NEWADDON REFCNT Acquiring dead class {} 0x{:x}", GetClassname(), + (long)(((void*)this))); + +#ifdef LOG_LIFECYCLE_EVENTS + CLog::Log(LOGDEBUG, "NEWADDON REFCNT incrementing to {} on {} 0x{:x}", ++refs, GetClassname(), + (long)(((void*)this))); +#else + ++refs; +#endif + } +#endif +} + + diff --git a/xbmc/interfaces/legacy/AddonClass.h b/xbmc/interfaces/legacy/AddonClass.h new file mode 100644 index 0000000..f5de375 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonClass.h @@ -0,0 +1,209 @@ +/* + * 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 + +/** + * Defining LOG_LIFECYCLE_EVENTS will log all instantiations, deletions + * and also reference countings (increments and decrements) that take + * place on any Addon* class. + * + * Comment out (or uncomment out) to change the setting. + */ +//#define LOG_LIFECYCLE_EVENTS + +/** + * Defining XBMC_ADDON_DEBUG_MEMORY will make the Acquire and Release + * methods virtual allow the developer to overload them in a sub-class + * and set breakpoints to aid in debugging. It will also cause the + * reference counting mechanism to never actually delete any AddonClass + * instance allowing for the tracking of more references to (supposedly) + * deallocated classes. + * + * Comment out (or uncomment out) to change the setting. + */ +//#define XBMC_ADDON_DEBUG_MEMORY + +#include "AddonString.h" + +#include <mutex> +#ifdef XBMC_ADDON_DEBUG_MEMORY +#include "utils/log.h" +#endif +#include "AddonUtils.h" + +#include <atomic> +#include <typeindex> + +namespace XBMCAddon +{ + class LanguageHook; + + /** + * This class is the superclass for all reference counted classes in the api. + * It provides a means for the bindings to handle all api objects generically. + * + * It also provides some means for debugging "lifecycle" events (see the above + * description of LOG_LIFECYCLE_EVENTS). + * + * If a scripting language bindings require specific handling there is a + * hook to add in these language specifics that can be set here. + */ + class AddonClass : public CCriticalSection + { + private: + mutable std::atomic<long> refs; + bool m_isDeallocating = false; + + // no copying + inline AddonClass(const AddonClass&) = delete; + +#ifdef XBMC_ADDON_DEBUG_MEMORY + bool isDeleted; +#endif + + protected: + LanguageHook* languageHook; + + /** + * This method is meant to be called from the destructor of the + * lowest level class. + * + * It's virtual because it's a convenient place to receive messages that + * we're about to go be deleted but prior to any real tear-down. + * + * Any overloading classes need to remember to pass the call up the chain. + */ + virtual void deallocating() + { + std::unique_lock<CCriticalSection> lock(*this); + m_isDeallocating = true; + } + + /** + * This is meant to be called during static initialization and so isn't + * synchronized. + */ + static short getNextClassIndex(); + + public: + AddonClass(); + virtual ~AddonClass(); + + inline const char* GetClassname() const { return typeid(*this).name(); } + inline LanguageHook* GetLanguageHook() { return languageHook; } + + /** + * This method should be called while holding a Synchronize + * on the object. It will prevent the deallocation during + * the time it's held. + */ + bool isDeallocating() { XBMC_TRACE; return m_isDeallocating; } + + static short getNumAddonClasses(); + +#ifdef XBMC_ADDON_DEBUG_MEMORY + virtual +#else + inline +#endif + void Release() const +#ifndef XBMC_ADDON_DEBUG_MEMORY + { + long ct = --refs; +#ifdef LOG_LIFECYCLE_EVENTS + CLog::Log(LOGDEBUG, "NEWADDON REFCNT decrementing to {} on {} 0x{:x}", ct, GetClassname(), + (long)(((void*)this))); +#endif + if(ct == 0) + delete this; + } +#else + ; +#endif + + +#ifdef XBMC_ADDON_DEBUG_MEMORY + virtual +#else + inline +#endif + void Acquire() const +#ifndef XBMC_ADDON_DEBUG_MEMORY + { +#ifdef LOG_LIFECYCLE_EVENTS + CLog::Log(LOGDEBUG, "NEWADDON REFCNT incrementing to {} on {} 0x{:x}", ++refs, GetClassname(), + (long)(((void*)this))); +#else + ++refs; +#endif + } +#else + ; +#endif + +#define refcheck + /** + * This class is a smart pointer for a Referenced class. + */ + template <class T> class Ref + { + T * ac; + public: + inline Ref() : ac(NULL) {} + inline Ref(const T* _ac) : ac(const_cast<T*>(_ac)) { if (ac) ac->Acquire(); refcheck; } + + // copy semantics + inline Ref(Ref<T> const & oref) : ac(const_cast<T*>(oref.get())) { if (ac) ac->Acquire(); refcheck; } + template<class O> inline Ref(Ref<O> const & oref) : ac(static_cast<T*>(oref.get())) { if (ac) ac->Acquire(); refcheck; } + + /** + * operator= should work with either another smart pointer or a pointer since it will + * be able to convert a pointer to a smart pointer using one of the above constructors. + * + * Note: There is a trick here. The temporary variable is necessary because otherwise the + * following code will fail: + * + * Ref<T> ptr = new T; + * ptr = ptr; + * + * What happens without the tmp is the dereference is called first so the object ends up + * deleted and then the reference happens on a deleted object. The order is reversed + * in the following. + * + * Note: Operator= is ambiguous if you define both an operator=(Ref<T>&) and an operator=(T*). I'm + * opting for the route the boost took here figuring it has more history behind it. + */ + inline Ref<T>& operator=(Ref<T> const & oref) + { T* tmp = ac; ac = const_cast<T*>(oref.get()); if (ac) ac->Acquire(); if (tmp) tmp->Release(); refcheck; return *this; } + + inline T* operator->() const { refcheck; return ac; } + + /** + * This operator doubles as the value in a boolean expression. + */ + inline operator T*() const { refcheck; return ac; } + inline T* get() const { refcheck; return ac; } + inline T& getRef() const { refcheck; return *ac; } + + inline ~Ref() { refcheck; if (ac) ac->Release(); } + inline bool isNull() const { refcheck; return ac == NULL; } + inline bool isNotNull() const { refcheck; return ac != NULL; } + inline bool isSet() const { refcheck; return ac != NULL; } + inline bool operator!() const { refcheck; return ac == NULL; } + inline bool operator==(const AddonClass::Ref<T>& oref) const { refcheck; return ac == oref.ac; } + inline bool operator<(const AddonClass::Ref<T>& oref) const { refcheck; return ac < oref.ac; } // std::set semantics + + // This is there only for boost compatibility + template<class O> inline void reset(Ref<O> const & oref) { refcheck; (*this) = static_cast<T*>(oref.get()); refcheck; } + template<class O> inline void reset(O * oref) { refcheck; (*this) = static_cast<T*>(oref); refcheck; } + inline void reset() { refcheck; if (ac) ac->Release(); ac = NULL; } + }; + + }; +} diff --git a/xbmc/interfaces/legacy/AddonString.h b/xbmc/interfaces/legacy/AddonString.h new file mode 100644 index 0000000..7b1ef41 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonString.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include <string> + +namespace XBMCAddon +{ + typedef std::string String; + + extern String emptyString; +} + + + diff --git a/xbmc/interfaces/legacy/AddonUtils.cpp b/xbmc/interfaces/legacy/AddonUtils.cpp new file mode 100644 index 0000000..ad0f0e0 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonUtils.cpp @@ -0,0 +1,123 @@ +/* + * 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 "AddonUtils.h" + +#include "LanguageHook.h" +#include "addons/Skin.h" +#include "application/Application.h" +#include "utils/XBMCTinyXML.h" +#ifdef ENABLE_XBMC_TRACE_API +#include "utils/log.h" +#endif + +namespace XBMCAddonUtils +{ + GuiLock::GuiLock(XBMCAddon::LanguageHook* languageHook, bool offScreen) + : m_languageHook(languageHook), m_offScreen(offScreen) + { + if (!m_languageHook) + m_languageHook = XBMCAddon::LanguageHook::GetLanguageHook(); + if (m_languageHook) + m_languageHook->DelayedCallOpen(); + + if (!m_offScreen) + g_application.LockFrameMoveGuard(); + } + + GuiLock::~GuiLock() + { + if (!m_offScreen) + g_application.UnlockFrameMoveGuard(); + + if (m_languageHook) + m_languageHook->DelayedCallClose(); + } + + static char defaultImage[1024]; + + const char *getDefaultImage(const char* cControlType, const char* cTextureType) + { + // create an xml block so that we can resolve our defaults + // <control type="type"> + // <description /> + // </control> + TiXmlElement control("control"); + control.SetAttribute("type", cControlType); + TiXmlElement filler("description"); + control.InsertEndChild(filler); + g_SkinInfo->ResolveIncludes(&control); + + // ok, now check for our texture type + TiXmlElement *pTexture = control.FirstChildElement(cTextureType); + if (pTexture) + { + // found our textureType + TiXmlNode *pNode = pTexture->FirstChild(); + if (pNode && pNode->Value()[0] != '-') + { + strncpy(defaultImage, pNode->Value(), sizeof(defaultImage)); + defaultImage[sizeof(defaultImage) - 1] = '\0'; + return defaultImage; + } + } + return ""; + } + +#ifdef ENABLE_XBMC_TRACE_API + static thread_local TraceGuard* tlParent; + + static char** getSpacesArray(int size) + { + char** ret = new char*[size]; + for (int i = 0; i < size; i++) + { + ret[i] = new char[i + 1]; + + int j; + for (j = 0; j < i; j++) + ret[i][j] = ' '; + ret[i][j] = 0; + } + return ret; + } + + static char** spaces = getSpacesArray(256); + + const char* TraceGuard::getSpaces() { return spaces[depth]; } + + TraceGuard::TraceGuard(const char* _function) :function(_function) + { + parent = tlParent; + depth = parent == NULL ? 0 : parent->depth + 1; + + tlParent = this; + + CLog::Log(LOGDEBUG, "{}NEWADDON Entering {}", spaces[depth], function); + } + + TraceGuard::TraceGuard() :function(NULL) + { + parent = tlParent; + depth = parent == NULL ? 0 : parent->depth + 1; + tlParent = this; + // silent + } + + TraceGuard::~TraceGuard() + { + if (function) + CLog::Log(LOGDEBUG, "{}NEWADDON Leaving {}", spaces[depth], function); + + // need to pop the stack + tlParent = this->parent; + } +#endif + + +} diff --git a/xbmc/interfaces/legacy/AddonUtils.h b/xbmc/interfaces/legacy/AddonUtils.h new file mode 100644 index 0000000..2bf7de5 --- /dev/null +++ b/xbmc/interfaces/legacy/AddonUtils.h @@ -0,0 +1,95 @@ +/* + * 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 + +/* + * addon.h + * + * Created on: Aug 21, 2010 + * Author: jim + */ + +//#define ENABLE_XBMC_TRACE_API + +#include "threads/CriticalSection.h" + +#include <memory> +#include <mutex> +#include <vector> + +#ifdef TARGET_WINDOWS +#define __PRETTY_FUNCTION__ __FUNCTION__ +#endif + +/** + * This file contains the public definitions for the Addon api. It's meant to be used + * by those writing language bindings. + */ + +namespace XBMCAddon +{ +class LanguageHook; +} + +namespace XBMCAddonUtils +{ + class GuiLock + { + public: + GuiLock(XBMCAddon::LanguageHook* languageHook, bool offScreen); + ~GuiLock(); + + protected: + XBMCAddon::LanguageHook* m_languageHook = nullptr; + bool m_offScreen = false; + }; + + class InvertSingleLockGuard + { + std::unique_lock<CCriticalSection>& lock; + + public: + explicit InvertSingleLockGuard(std::unique_lock<CCriticalSection>& _lock) : lock(_lock) + { + lock.unlock(); + } + ~InvertSingleLockGuard() { lock.lock(); } + }; + + + /* + * Looks in references.xml for image name + * If none exist return default image name + */ + const char *getDefaultImage(const char* cControlType, const char* cTextureType); + +#ifdef ENABLE_XBMC_TRACE_API + class TraceGuard + { + const char* function; + public: + TraceGuard* parent; + int depth; + + const char* getSpaces(); + + explicit TraceGuard(const char* _function); + TraceGuard(); + ~TraceGuard(); + }; +#endif +} + +#ifdef ENABLE_XBMC_TRACE_API +#define XBMC_TRACE XBMCAddonUtils::TraceGuard _tg(__PRETTY_FUNCTION__) +#else +#define XBMC_TRACE +#endif + + diff --git a/xbmc/interfaces/legacy/Alternative.h b/xbmc/interfaces/legacy/Alternative.h new file mode 100644 index 0000000..a715688 --- /dev/null +++ b/xbmc/interfaces/legacy/Alternative.h @@ -0,0 +1,70 @@ +/* + * 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 "Exception.h" + +namespace XBMCAddon +{ + enum WhichAlternative { none, first, second }; + + template<typename T1, typename T2> class Alternative + { + public: + private: + WhichAlternative pos = none; + T1 d1; + T2 d2; + + public: + Alternative() = default; + + inline WhichAlternative which() const { return pos; } + + inline T1& former() + { + if (pos == second)// first and none is ok + throw WrongTypeException("Access of XBMCAddon::Alternative as incorrect type"); + if (pos == none) + d1 = T1(); + pos = first; + return d1; + } + + inline const T1& former() const + { + if (pos != first) + throw WrongTypeException("Access of XBMCAddon::Alternative as incorrect type"); + return d1; + } + + inline T2& later() + { + if (pos == first) + throw WrongTypeException("Access of XBMCAddon::Alternative as incorrect type"); + if (pos == none) + d2 = T2(); + pos = second; + return d2; + } + + inline const T2& later() const + { + if (pos != second) + throw WrongTypeException("Access of XBMCAddon::Alternative as incorrect type"); + return d2; + } + + inline operator T1& () { return former(); } + inline operator const T1& () const { return former(); } + inline operator T2& () { return later(); } + inline operator const T2& () const { return later(); } + }; +} + diff --git a/xbmc/interfaces/legacy/CMakeLists.txt b/xbmc/interfaces/legacy/CMakeLists.txt new file mode 100644 index 0000000..d92fc78 --- /dev/null +++ b/xbmc/interfaces/legacy/CMakeLists.txt @@ -0,0 +1,76 @@ +set(SOURCES AddonCallback.cpp + AddonClass.cpp + Addon.cpp + AddonUtils.cpp + CallbackFunction.cpp + CallbackHandler.cpp + Control.cpp + Dialog.cpp + DrmCryptoSession.cpp + File.cpp + InfoTagGame.cpp + InfoTagMusic.cpp + InfoTagPicture.cpp + InfoTagRadioRDS.cpp + InfoTagVideo.cpp + Keyboard.cpp + LanguageHook.cpp + ListItem.cpp + ModuleXbmc.cpp + ModuleXbmcgui.cpp + ModuleXbmcplugin.cpp + ModuleXbmcvfs.cpp + Monitor.cpp + Player.cpp + PlayList.cpp + Settings.cpp + String.cpp + Window.cpp + WindowDialog.cpp + WindowDialogMixin.cpp + WindowXML.cpp) + +set(HEADERS Addon.h + AddonCallback.h + AddonClass.h + AddonString.h + AddonUtils.h + Alternative.h + aojsonrpc.h + CallbackFunction.h + CallbackHandler.h + Control.h + Dialog.h + Dictionary.h + DrmCryptoSession.h + Exception.h + File.h + InfoTagGame.h + InfoTagMusic.h + InfoTagPicture.h + InfoTagRadioRDS.h + InfoTagVideo.h + Keyboard.h + LanguageHook.h + List.h + ListItem.h + ModuleXbmc.h + ModuleXbmcgui.h + ModuleXbmcplugin.h + ModuleXbmcvfs.h + Monitor.h + Player.h + PlayList.h + RenderCapture.h + Settings.h + Stat.h + swighelper.h + Tuple.h + Window.h + WindowDialog.h + WindowDialogMixin.h + WindowException.h + WindowInterceptor.h + WindowXML.h) + +core_add_library(legacy_interface) diff --git a/xbmc/interfaces/legacy/CallbackFunction.cpp b/xbmc/interfaces/legacy/CallbackFunction.cpp new file mode 100644 index 0000000..fdd6cf8 --- /dev/null +++ b/xbmc/interfaces/legacy/CallbackFunction.cpp @@ -0,0 +1,14 @@ +/* + * 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 "CallbackFunction.h" + +namespace XBMCAddon +{ + Callback::~Callback() { XBMC_TRACE; deallocating(); } +} diff --git a/xbmc/interfaces/legacy/CallbackFunction.h b/xbmc/interfaces/legacy/CallbackFunction.h new file mode 100644 index 0000000..90360d5 --- /dev/null +++ b/xbmc/interfaces/legacy/CallbackFunction.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonClass.h" + +namespace XBMCAddon +{ + /** + * <p>This is the parent class for the class templates that hold + * a callback. A callback is essentially a templatized + * functor (functoid?) for a call to a member function.</p> + * + * <p>This class combined with the attending CallbackHandlers should make + * sure that the AddonClass isn't in the midst of deallocating when + * the callback executes. In this way the Callback class acts as + * a weak reference.</p> + */ + class Callback : public AddonClass + { + protected: + AddonClass* addonClassObject; + explicit Callback(AddonClass* _object) : addonClassObject(_object) { XBMC_TRACE; } + + public: + virtual void executeCallback() = 0; + ~Callback() override; + + AddonClass* getObject() { XBMC_TRACE; return addonClassObject; } + }; + + struct cb_null_type {}; + + // stub type template to be partial specialized + template<typename M = cb_null_type, typename T1 = cb_null_type, + typename T2 = cb_null_type, typename T3 = cb_null_type, + typename T4 = cb_null_type, typename Extraneous = cb_null_type> + class CallbackFunction {}; + + /** + * This is the template to carry a callback to a member function + * that returns 'void' (has no return) and takes no parameters. + */ + template<class M> class CallbackFunction<M, cb_null_type, cb_null_type, cb_null_type, cb_null_type, cb_null_type> : public Callback + { + public: + typedef void (M::*MemberFunction)(); + + protected: + MemberFunction meth; + M* obj; + + public: + CallbackFunction(M* object, MemberFunction method) : + Callback(object), meth(method), obj(object) { XBMC_TRACE; } + + ~CallbackFunction() override { XBMC_TRACE; deallocating(); } + + void executeCallback() override { XBMC_TRACE; ((*obj).*(meth))(); } + }; + + /** + * This is the template to carry a callback to a member function + * that returns 'void' (has no return) and takes one parameter. + */ + template<class M, typename P1> class CallbackFunction<M,P1, cb_null_type, cb_null_type, cb_null_type, cb_null_type> : public Callback + { + public: + typedef void (M::*MemberFunction)(P1); + + protected: + MemberFunction meth; + M* obj; + P1 param; + + public: + CallbackFunction(M* object, MemberFunction method, P1 parameter) : + Callback(object), meth(method), obj(object), + param(parameter) { XBMC_TRACE; } + + ~CallbackFunction() override { XBMC_TRACE; deallocating(); } + + void executeCallback() override { XBMC_TRACE; ((*obj).*(meth))(param); } + }; + + /** + * This is the template to carry a callback to a member function + * that returns 'void' (has no return) and takes one parameter + * that can be held in an AddonClass::Ref + */ + template<class M, typename P1> class CallbackFunction<M,AddonClass::Ref<P1>, cb_null_type, cb_null_type, cb_null_type, cb_null_type> : public Callback + { + public: + typedef void (M::*MemberFunction)(P1*); + + protected: + MemberFunction meth; + M* obj; + AddonClass::Ref<P1> param; + + public: + CallbackFunction(M* object, MemberFunction method, P1* parameter) : + Callback(object), meth(method), obj(object), + param(parameter) { XBMC_TRACE; } + + ~CallbackFunction() override { XBMC_TRACE; deallocating(); } + + void executeCallback() override { XBMC_TRACE; ((*obj).*(meth))(param); } + }; + + + /** + * This is the template to carry a callback to a member function + * that returns 'void' (has no return) and takes two parameters. + */ + template<class M, typename P1, typename P2> class CallbackFunction<M,P1,P2, cb_null_type, cb_null_type, cb_null_type> : public Callback + { + public: + typedef void (M::*MemberFunction)(P1,P2); + + protected: + MemberFunction meth; + M* obj; + P1 param1; + P2 param2; + + public: + CallbackFunction(M* object, MemberFunction method, P1 parameter, P2 parameter2) : + Callback(object), meth(method), obj(object), + param1(parameter), param2(parameter2) { XBMC_TRACE; } + + ~CallbackFunction() override { XBMC_TRACE; deallocating(); } + + void executeCallback() override { XBMC_TRACE; ((*obj).*(meth))(param1,param2); } + }; + + + /** + * This is the template to carry a callback to a member function + * that returns 'void' (has no return) and takes three parameters. + */ + template<class M, typename P1, typename P2, typename P3> class CallbackFunction<M,P1,P2,P3, cb_null_type, cb_null_type> : public Callback + { + public: + typedef void (M::*MemberFunction)(P1,P2,P3); + + protected: + MemberFunction meth; + M* obj; + P1 param1; + P2 param2; + P3 param3; + + public: + CallbackFunction(M* object, MemberFunction method, P1 parameter, P2 parameter2, P3 parameter3) : + Callback(object), meth(method), obj(object), + param1(parameter), param2(parameter2), param3(parameter3) { XBMC_TRACE; } + + ~CallbackFunction() override { XBMC_TRACE; deallocating(); } + + void executeCallback() override { XBMC_TRACE; ((*obj).*(meth))(param1,param2,param3); } + }; +} + + diff --git a/xbmc/interfaces/legacy/CallbackHandler.cpp b/xbmc/interfaces/legacy/CallbackHandler.cpp new file mode 100644 index 0000000..f567168 --- /dev/null +++ b/xbmc/interfaces/legacy/CallbackHandler.cpp @@ -0,0 +1,148 @@ +/* + * 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 "CallbackHandler.h" + +#include "AddonUtils.h" +#include "commons/Exception.h" +#include "utils/log.h" + +#include <mutex> +#include <vector> + +namespace XBMCAddon +{ + class AsyncCallbackMessage : public AddonClass + { + public: + AddonClass::Ref<Callback> cb; + AddonClass::Ref<RetardedAsyncCallbackHandler> handler; + AsyncCallbackMessage(Callback* _cb, RetardedAsyncCallbackHandler* _handler) : + cb(_cb), handler(_handler) { XBMC_TRACE; } + }; + + //******************************************************************** + // This holds the callback messages which will be executed. It doesn't + // seem to work correctly with the Ref object so we'll go with Ref*'s + typedef std::vector<AddonClass::Ref<AsyncCallbackMessage> > CallbackQueue; + //******************************************************************** + + static CCriticalSection critSection; + static CallbackQueue g_callQueue; + + void RetardedAsyncCallbackHandler::invokeCallback(Callback* cb) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(critSection); + g_callQueue.push_back(new AsyncCallbackMessage(cb,this)); + } + + RetardedAsyncCallbackHandler::~RetardedAsyncCallbackHandler() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(critSection); + + // find any messages that might be there because of me ... and remove them + CallbackQueue::iterator iter = g_callQueue.begin(); + while (iter != g_callQueue.end()) + { + if ((*iter)->handler.get() == this) // then this message is because of me + { + g_callQueue.erase(iter); + iter = g_callQueue.begin(); + } + else + ++iter; + } + } + + void RetardedAsyncCallbackHandler::makePendingCalls() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(critSection); + CallbackQueue::iterator iter = g_callQueue.begin(); + while (iter != g_callQueue.end()) + { + AddonClass::Ref<AsyncCallbackMessage> p(*iter); + + // only call when we are in the right thread state + if(p->handler->isStateOk(p->cb->getObject())) + { + // remove it from the queue. No matter what we're done with + // this. Even if it doesn't execute for some reason. + g_callQueue.erase(iter); + + // we need to release the critSection lock prior to grabbing the + // lock on the object. Not doing so results in deadlocks. We no + // longer are accessing the g_callQueue so it's fine to do this now + { + XBMCAddonUtils::InvertSingleLockGuard unlock(lock); + + // make sure the object is not deallocating + + // we need to grab the object lock to see if the object of the call + // is deallocating. holding this lock should prevent it from + // deallocating during the execution of this call. +#ifdef ENABLE_XBMC_TRACE_API + CLog::Log(LOGDEBUG, "{}NEWADDON executing callback 0x{:x}", _tg.getSpaces(), + (long)(p->cb.get())); +#endif + AddonClass* obj = (p->cb->getObject()); + AddonClass::Ref<AddonClass> ref(obj); + std::unique_lock<CCriticalSection> lock2(*obj); + if (!p->cb->getObject()->isDeallocating()) + { + try + { + // need to make the call + p->cb->executeCallback(); + } + catch (XbmcCommons::Exception& e) { e.LogThrowMessage(); } + catch (...) + { + CLog::Log(LOGERROR, "Unknown exception while executing callback {:#x}", + reinterpret_cast<int64_t>(p->cb.get())); + } + } + } + + // since the state of the iterator may have been corrupted by + // the changing state of the list from another thread during + // the releasing fo the lock in the immediately preceeding + // codeblock, we need to reset it before continuing the loop + iter = g_callQueue.begin(); + } + else // if we're not in the right thread for this callback... + ++iter; + } + } + + void RetardedAsyncCallbackHandler::clearPendingCalls(void* userData) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(critSection); + CallbackQueue::iterator iter = g_callQueue.begin(); + while (iter != g_callQueue.end()) + { + AddonClass::Ref<AsyncCallbackMessage> p(*iter); + + if(p->handler->shouldRemoveCallback(p->cb->getObject(),userData)) + { +#ifdef ENABLE_XBMC_TRACE_API + CLog::Log(LOGDEBUG, + "{}NEWADDON removing callback 0x{:x} for PyThreadState 0x{:x} from queue", + _tg.getSpaces(), (long)(p->cb.get()), (long)userData); +#endif + iter = g_callQueue.erase(iter); + } + else + ++iter; + } + } +} + diff --git a/xbmc/interfaces/legacy/CallbackHandler.h b/xbmc/interfaces/legacy/CallbackHandler.h new file mode 100644 index 0000000..223c74a --- /dev/null +++ b/xbmc/interfaces/legacy/CallbackHandler.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonClass.h" +#include "CallbackFunction.h" + +namespace XBMCAddon +{ + /** + * This is the abstraction representing different ways to handle + * the execution of callbacks. Different language bindings may + * have different requirements. + */ + class CallbackHandler : public AddonClass + { + protected: + inline CallbackHandler() = default; + + public: + virtual void invokeCallback(Callback* cb) = 0; + }; + + /** + * This class is primarily for Python support (hence the "Retarded" + * prefix). Python (et. al. Retarded languages) require that + * the context within which a callback executes is under the control + * of the language. Therefore, this handler is used to queue + * messages over to a language controlled thread for eventual + * execution. + * + * @todo Allow a cross thread synchronous execution. + * Fix the stupid means of calling the clearPendingCalls by passing + * userData which is specific to the handler/language type. + */ + class RetardedAsyncCallbackHandler : public CallbackHandler + { + protected: + inline RetardedAsyncCallbackHandler() = default; + public: + + ~RetardedAsyncCallbackHandler() override; + + void invokeCallback(Callback* cb) override; + static void makePendingCalls(); + static void clearPendingCalls(void* userData); + + virtual bool isStateOk(AddonClass* obj) = 0; + virtual bool shouldRemoveCallback(AddonClass* obj, void* userData) = 0; + }; + +} diff --git a/xbmc/interfaces/legacy/Control.cpp b/xbmc/interfaces/legacy/Control.cpp new file mode 100644 index 0000000..62dbac6 --- /dev/null +++ b/xbmc/interfaces/legacy/Control.cpp @@ -0,0 +1,1435 @@ +/* + * 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 "Control.h" + +#include "AddonUtils.h" +#include "LanguageHook.h" +#include "ServiceBroker.h" +#include "WindowException.h" +#include "guilib/GUIButtonControl.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIControlFactory.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIFadeLabelControl.h" +#include "guilib/GUIFontManager.h" +#include "guilib/GUIImage.h" +#include "guilib/GUILabel.h" +#include "guilib/GUILabelControl.h" +#include "guilib/GUIListContainer.h" +#include "guilib/GUIProgressControl.h" +#include "guilib/GUIRadioButtonControl.h" +#include "guilib/GUISliderControl.h" +#include "guilib/GUITextBox.h" +#include "guilib/GUIWindowManager.h" +#include "listproviders/StaticProvider.h" +#include "utils/StringUtils.h" +#include "utils/XBMCTinyXML.h" + +using namespace KODI; + +namespace XBMCAddon +{ + namespace xbmcgui + { + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlFadeLabel::ControlFadeLabel(long x, long y, long width, long height, + const char* font, const char* _textColor, + long _alignment) : + strFont("font13"), textColor(0xffffffff), align(_alignment) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + if (font) + strFont = font; + + if (_textColor) + sscanf(_textColor, "%x", &textColor); + + pGUIControl = NULL; + } + + void ControlFadeLabel::addLabel(const String& label) + { + CGUIMessage msg(GUI_MSG_LABEL_ADD, iParentId, iControlId); + msg.SetLabel(label); + + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + void ControlFadeLabel::reset() + { + CGUIMessage msg(GUI_MSG_LABEL_RESET, iParentId, iControlId); + + vecLabels.clear(); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + CGUIControl* ControlFadeLabel::Create() + { + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = label.focusedColor = textColor; + label.align = align; + pGUIControl = new CGUIFadeLabelControl( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight, + label, + true, + 0, + true, + false); + pGUIControl->SetVisible(m_visible); + + CGUIMessage msg(GUI_MSG_LABEL_RESET, iParentId, iControlId); + pGUIControl->OnMessage(msg); + + return pGUIControl; + } + + void ControlFadeLabel::setScrolling(bool scroll) + { + static_cast<CGUIFadeLabelControl*>(pGUIControl)->SetScrolling(scroll); + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlTextBox::ControlTextBox(long x, long y, long width, long height, + const char* font, const char* _textColor) : + strFont("font13"), textColor(0xffffffff) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + if (font) + strFont = font; + + if (_textColor) + sscanf(_textColor, "%x", &textColor); + } + + void ControlTextBox::setText(const String& text) + { + if (pGUIControl) + { + // create message + CGUIMessage msg(GUI_MSG_LABEL_SET, iParentId, iControlId); + msg.SetLabel(text); + + // send message + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + else + { + m_label = text; + } + } + + String ControlTextBox::getText() + { + if (pGUIControl == nullptr) + return m_label; + + XBMCAddonUtils::GuiLock lock(languageHook, false); + return static_cast<CGUITextBox*>(pGUIControl)->GetDescription(); + } + + void ControlTextBox::reset() + { + if (pGUIControl) + { + // create message + CGUIMessage msg(GUI_MSG_LABEL_RESET, iParentId, iControlId); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + m_label.clear(); + } + + void ControlTextBox::scroll(long position) + { + if (pGUIControl) + static_cast<CGUITextBox*>(pGUIControl)->Scroll((int)position); + } + + void ControlTextBox::autoScroll(int delay, int time, int repeat) + { + if (pGUIControl) + static_cast<CGUITextBox*>(pGUIControl)->SetAutoScrolling(delay, time, repeat); + } + + CGUIControl* ControlTextBox::Create() + { + // create textbox + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = label.focusedColor = textColor; + + pGUIControl = new CGUITextBox(iParentId, iControlId, + (float)dwPosX, (float)dwPosY, (float)dwWidth, (float)dwHeight, + label); + pGUIControl->SetVisible(m_visible); + + // set label + CGUIMessage msg(GUI_MSG_LABEL_SET, iParentId, iControlId); + msg.SetLabel(m_label); + pGUIControl->OnMessage(msg); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlButton::ControlButton(long x, + long y, + long width, + long height, + const String& label, + const char* focusTexture, + const char* noFocusTexture, + long _textOffsetX, + long _textOffsetY, + long alignment, + const char* font, + const char* _textColor, + const char* _disabledColor, + long angle, + const char* _shadowColor, + const char* _focusedColor) + : textOffsetX(_textOffsetX), + textOffsetY(_textOffsetY), + align(alignment), + strFont("font13"), + textColor(0xffffffff), + disabledColor(0x60ffffff), + iAngle(angle), + focusedColor(0xffffffff), + strText(label) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + // if texture is supplied use it, else get default ones + strTextureFocus = focusTexture ? focusTexture : + XBMCAddonUtils::getDefaultImage("button", "texturefocus"); + strTextureNoFocus = noFocusTexture ? noFocusTexture : + XBMCAddonUtils::getDefaultImage("button", "texturenofocus"); + + if (font) strFont = font; + if (_textColor) sscanf( _textColor, "%x", &textColor ); + if (_disabledColor) sscanf( _disabledColor, "%x", &disabledColor ); + if (_shadowColor) sscanf( _shadowColor, "%x", &shadowColor ); + if (_focusedColor) sscanf( _focusedColor, "%x", &focusedColor ); + } + + void ControlButton::setLabel(const String& label, + const char* font, + const char* _textColor, + const char* _disabledColor, + const char* _shadowColor, + const char* _focusedColor, + const String& label2) + { + if (!label.empty()) strText = label; + if (!label2.empty()) strText2 = label2; + if (font) strFont = font; + if (_textColor) sscanf(_textColor, "%x", &textColor); + if (_disabledColor) sscanf( _disabledColor, "%x", &disabledColor ); + if (_shadowColor) sscanf(_shadowColor, "%x", &shadowColor); + if (_focusedColor) sscanf(_focusedColor, "%x", &focusedColor); + + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + static_cast<CGUIButtonControl*>(pGUIControl)->PythonSetLabel(strFont, strText, textColor, shadowColor, focusedColor); + static_cast<CGUIButtonControl*>(pGUIControl)->SetLabel2(strText2); + static_cast<CGUIButtonControl*>(pGUIControl)->PythonSetDisabledColor(disabledColor); + } + } + + void ControlButton::setDisabledColor(const char* color) + { + if (color) sscanf(color, "%x", &disabledColor); + + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + static_cast<CGUIButtonControl*>(pGUIControl)->PythonSetDisabledColor(disabledColor); + } + } + + String ControlButton::getLabel() + { + if (pGUIControl == nullptr) + return strText; + + XBMCAddonUtils::GuiLock lock(languageHook, false); + return static_cast<CGUIButtonControl*>(pGUIControl)->GetLabel(); + } + + String ControlButton::getLabel2() + { + if (pGUIControl == nullptr) + return strText2; + + XBMCAddonUtils::GuiLock lock(languageHook, false); + return static_cast<CGUIButtonControl*>(pGUIControl)->GetLabel2(); + } + + CGUIControl* ControlButton::Create() + { + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = textColor; + label.disabledColor = disabledColor; + label.shadowColor = shadowColor; + label.focusedColor = focusedColor; + label.align = align; + label.offsetX = (float)textOffsetX; + label.offsetY = (float)textOffsetY; + label.angle = (float)-iAngle; + pGUIControl = new CGUIButtonControl( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight, + CTextureInfo(strTextureFocus), + CTextureInfo(strTextureNoFocus), + label); + pGUIControl->SetVisible(m_visible); + + CGUIButtonControl* pGuiButtonControl = + static_cast<CGUIButtonControl*>(pGUIControl); + + pGuiButtonControl->SetLabel(strText); + pGuiButtonControl->SetLabel2(strText2); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlImage::ControlImage(long x, long y, long width, long height, + const char* filename, long aRatio, + const char* _colorDiffuse): + aspectRatio(aRatio), colorDiffuse(0) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + // check if filename exists + strFileName = filename; + if (_colorDiffuse) + sscanf(_colorDiffuse, "%x", &colorDiffuse); + } + + void ControlImage::setImage(const char* imageFilename, const bool useCache) + { + strFileName = imageFilename; + + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + static_cast<CGUIImage*>(pGUIControl)->SetFileName(strFileName, false, useCache); + } + + void ControlImage::setColorDiffuse(const char* cColorDiffuse) + { + if (cColorDiffuse) sscanf(cColorDiffuse, "%x", &colorDiffuse); + else colorDiffuse = 0; + + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + static_cast<CGUIImage*>(pGUIControl)->SetColorDiffuse(GUILIB::GUIINFO::CGUIInfoColor(colorDiffuse)); + } + + CGUIControl* ControlImage::Create() + { + pGUIControl = new CGUIImage(iParentId, iControlId, + (float)dwPosX, (float)dwPosY, (float)dwWidth, (float)dwHeight, + CTextureInfo(strFileName)); + pGUIControl->SetVisible(m_visible); + + if (pGUIControl && aspectRatio <= CAspectRatio::AR_KEEP) + static_cast<CGUIImage*>(pGUIControl)->SetAspectRatio((CAspectRatio::ASPECT_RATIO)aspectRatio); + + if (pGUIControl && colorDiffuse) + static_cast<CGUIImage*>(pGUIControl)->SetColorDiffuse(GUILIB::GUIINFO::CGUIInfoColor(colorDiffuse)); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlProgress::ControlProgress(long x, long y, long width, long height, + const char* texturebg, + const char* textureleft, + const char* texturemid, + const char* textureright, + const char* textureoverlay) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + // if texture is supplied use it, else get default ones + strTextureBg = texturebg ? texturebg : + XBMCAddonUtils::getDefaultImage("progress", "texturebg"); + strTextureLeft = textureleft ? textureleft : + XBMCAddonUtils::getDefaultImage("progress", "lefttexture"); + strTextureMid = texturemid ? texturemid : + XBMCAddonUtils::getDefaultImage("progress", "midtexture"); + strTextureRight = textureright ? textureright : + XBMCAddonUtils::getDefaultImage("progress", "righttexture"); + strTextureOverlay = textureoverlay ? textureoverlay : + XBMCAddonUtils::getDefaultImage("progress", "overlaytexture"); + } + + void ControlProgress::setPercent(float pct) + { + if (pGUIControl) + static_cast<CGUIProgressControl*>(pGUIControl)->SetPercentage(pct); + } + + float ControlProgress::getPercent() + { + return pGUIControl ? static_cast<CGUIProgressControl*>(pGUIControl)->GetPercentage() : 0.0f; + } + + CGUIControl* ControlProgress::Create() + { + pGUIControl = new CGUIProgressControl(iParentId, iControlId, + (float)dwPosX, (float)dwPosY, + (float)dwWidth,(float)dwHeight, + CTextureInfo(strTextureBg), CTextureInfo(strTextureLeft), + CTextureInfo(strTextureMid), CTextureInfo(strTextureRight), + CTextureInfo(strTextureOverlay)); + pGUIControl->SetVisible(m_visible); + + if (pGUIControl && colorDiffuse) + static_cast<CGUIProgressControl*>(pGUIControl)->SetColorDiffuse(GUILIB::GUIINFO::CGUIInfoColor(colorDiffuse)); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlSlider::ControlSlider(long x, long y, long width, long height, + const char* textureback, + const char* texture, + const char* texturefocus, int orientation) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + iOrientation = orientation; + + // if texture is supplied use it, else get default ones + strTextureBack = textureback ? textureback : + XBMCAddonUtils::getDefaultImage("slider", "texturesliderbar"); + strTexture = texture ? texture : + XBMCAddonUtils::getDefaultImage("slider", "textureslidernib"); + strTextureFoc = texturefocus ? texturefocus : + XBMCAddonUtils::getDefaultImage("slider", "textureslidernibfocus"); + } + + float ControlSlider::getPercent() + { + return pGUIControl ? static_cast<CGUISliderControl*>(pGUIControl)->GetPercentage() : 0.0f; + } + + void ControlSlider::setPercent(float pct) + { + if (pGUIControl) + static_cast<CGUISliderControl*>(pGUIControl)->SetPercentage(pct); + } + + int ControlSlider::getInt() + { + return (pGUIControl) ? static_cast<CGUISliderControl*>(pGUIControl)->GetIntValue() : 0; + } + + void ControlSlider::setInt(int value, int min, int delta, int max) + { + if (pGUIControl) + { + static_cast<CGUISliderControl*>(pGUIControl)->SetType(SLIDER_CONTROL_TYPE_INT); + static_cast<CGUISliderControl*>(pGUIControl)->SetRange(min, max); + static_cast<CGUISliderControl*>(pGUIControl)->SetIntInterval(delta); + static_cast<CGUISliderControl*>(pGUIControl)->SetIntValue(value); + } + } + + float ControlSlider::getFloat() + { + return (pGUIControl) ? static_cast<CGUISliderControl*>(pGUIControl)->GetFloatValue() : 0.0f; + } + + void ControlSlider::setFloat(float value, float min, float delta, float max) + { + if (pGUIControl) + { + static_cast<CGUISliderControl*>(pGUIControl)->SetType(SLIDER_CONTROL_TYPE_FLOAT); + static_cast<CGUISliderControl*>(pGUIControl)->SetFloatRange(min, max); + static_cast<CGUISliderControl*>(pGUIControl)->SetFloatInterval(delta); + static_cast<CGUISliderControl*>(pGUIControl)->SetFloatValue(value); + } + } + + CGUIControl* ControlSlider::Create () + { + pGUIControl = new CGUISliderControl(iParentId, iControlId,(float)dwPosX, (float)dwPosY, + (float)dwWidth,(float)dwHeight, + CTextureInfo(strTextureBack),CTextureInfo(strTexture), + CTextureInfo(strTextureFoc), 0, ORIENTATION(iOrientation)); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlGroup::ControlGroup(long x, long y, long width, long height) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + } + + CGUIControl* ControlGroup::Create() + { + pGUIControl = new CGUIControlGroup(iParentId, + iControlId, + (float) dwPosX, + (float) dwPosY, + (float) dwWidth, + (float) dwHeight); + pGUIControl->SetVisible(m_visible); + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + ControlRadioButton::ControlRadioButton(long x, long y, long width, long height, const String& label, + const char* focusOnTexture, const char* noFocusOnTexture, + const char* focusOffTexture, const char* noFocusOffTexture, + const char* focusTexture, const char* noFocusTexture, + long _textOffsetX, long _textOffsetY, + long alignment, const char* font, const char* _textColor, + const char* _disabledColor, long angle, + const char* _shadowColor, const char* _focusedColor, + const char* disabledOnTexture, const char* disabledOffTexture) : + strFont("font13"), textColor(0xffffffff), disabledColor(0x60ffffff), + textOffsetX(_textOffsetX), textOffsetY(_textOffsetY), align(alignment), iAngle(angle), + shadowColor(0), focusedColor(0xffffffff) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + strText = label; + + // if texture is supplied use it, else get default ones + strTextureFocus = focusTexture ? focusTexture : + XBMCAddonUtils::getDefaultImage("button", "texturefocus"); + strTextureNoFocus = noFocusTexture ? noFocusTexture : + XBMCAddonUtils::getDefaultImage("button", "texturenofocus"); + + if (focusOnTexture && noFocusOnTexture) + { + strTextureRadioOnFocus = focusOnTexture; + strTextureRadioOnNoFocus = noFocusOnTexture; + } + else + { + strTextureRadioOnFocus = + XBMCAddonUtils::getDefaultImage("radiobutton", "textureradioonfocus"); + strTextureRadioOnNoFocus = + XBMCAddonUtils::getDefaultImage("radiobutton", "textureradioonnofocus"); + } + + if (focusOffTexture && noFocusOffTexture) + { + strTextureRadioOffFocus = focusOffTexture; + strTextureRadioOffNoFocus = noFocusOffTexture; + } + else + { + strTextureRadioOffFocus = + XBMCAddonUtils::getDefaultImage("radiobutton", "textureradioofffocus"); + strTextureRadioOffNoFocus = + XBMCAddonUtils::getDefaultImage("radiobutton", "textureradiooffnofocus"); + } + + if (font) strFont = font; + if (_textColor) sscanf( _textColor, "%x", &textColor ); + if (_disabledColor) sscanf( _disabledColor, "%x", &disabledColor ); + if (_shadowColor) sscanf( _shadowColor, "%x", &shadowColor ); + if (_focusedColor) sscanf( _focusedColor, "%x", &focusedColor ); + } + + void ControlRadioButton::setSelected(bool selected) + { + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + static_cast<CGUIRadioButtonControl*>(pGUIControl)->SetSelected(selected); + } + } + + bool ControlRadioButton::isSelected() + { + bool isSelected = false; + + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + isSelected = static_cast<CGUIRadioButtonControl*>(pGUIControl)->IsSelected(); + } + return isSelected; + } + + void ControlRadioButton::setLabel(const String& label, + const char* font, + const char* _textColor, + const char* _disabledColor, + const char* _shadowColor, + const char* _focusedColor, + const String& label2) + { + if (!label.empty()) strText = label; + if (font) strFont = font; + if (_textColor) sscanf(_textColor, "%x", &textColor); + if (_disabledColor) sscanf( _disabledColor, "%x", &disabledColor ); + if (_shadowColor) sscanf(_shadowColor, "%x", &shadowColor); + if (_focusedColor) sscanf(_focusedColor, "%x", &focusedColor); + + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + static_cast<CGUIRadioButtonControl*>(pGUIControl)->PythonSetLabel(strFont, strText, textColor, shadowColor, focusedColor); + static_cast<CGUIRadioButtonControl*>(pGUIControl)->PythonSetDisabledColor(disabledColor); + } + } + + void ControlRadioButton::setRadioDimension(long x, long y, long width, long height) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + if (pGUIControl) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + static_cast<CGUIRadioButtonControl*>(pGUIControl)->SetRadioDimensions((float)dwPosX, (float)dwPosY, (float)dwWidth, (float)dwHeight); + } + } + + CGUIControl* ControlRadioButton::Create() + { + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = textColor; + label.disabledColor = disabledColor; + label.shadowColor = shadowColor; + label.focusedColor = focusedColor; + label.align = align; + label.offsetX = (float)textOffsetX; + label.offsetY = (float)textOffsetY; + label.angle = (float)-iAngle; + pGUIControl = new CGUIRadioButtonControl( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight, + CTextureInfo(strTextureFocus), + CTextureInfo(strTextureNoFocus), + label, + CTextureInfo(strTextureRadioOnFocus), + CTextureInfo(strTextureRadioOnNoFocus), + CTextureInfo(strTextureRadioOffFocus), + CTextureInfo(strTextureRadioOffNoFocus), + CTextureInfo(strTextureRadioOnDisabled), + CTextureInfo(strTextureRadioOffDisabled)); + pGUIControl->SetVisible(m_visible); + + CGUIRadioButtonControl* pGuiButtonControl = + static_cast<CGUIRadioButtonControl*>(pGUIControl); + + pGuiButtonControl->SetLabel(strText); + + return pGUIControl; + } + + // ============================================================ + + // ============================================================ + // ============================================================ + Control::~Control() { deallocating(); } + + CGUIControl* Control::Create() + { + throw WindowException("Object is a Control, but can't be added to a window"); + } + + std::vector<int> Control::getPosition() + { + std::vector<int> ret(2); + ret[0] = dwPosX; + ret[1] = dwPosY; + return ret; + } + + void Control::setEnabled(bool enabled) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + pGUIControl->SetEnabled(enabled); + } + + void Control::setVisible(bool visible) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl != nullptr) + { + pGUIControl->SetVisible(visible); + } + else + { + m_visible = visible; + } + } + + bool Control::isVisible() + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock(languageHook, false); + if (pGUIControl) + return pGUIControl->IsVisible(); + else + return false; + } + + void Control::setVisibleCondition(const char* visible, bool allowHiddenFocus) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + + if (pGUIControl) + pGUIControl->SetVisibleCondition(visible, allowHiddenFocus ? "true" : "false"); + } + + void Control::setEnableCondition(const char* enable) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + + if (pGUIControl) + pGUIControl->SetEnableCondition(enable); + } + + void Control::setAnimations(const std::vector< Tuple<String,String> >& eventAttr) + { + CXBMCTinyXML xmlDoc; + TiXmlElement xmlRootElement("control"); + TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement); + if (!pRoot) + throw WindowException("TiXmlNode creation error"); + + std::vector<CAnimation> animations; + + for (unsigned int anim = 0; anim < eventAttr.size(); anim++) + { + const Tuple<String,String>& pTuple = eventAttr[anim]; + + if (pTuple.GetNumValuesSet() != 2) + throw WindowException("Error unpacking tuple found in list"); + + const String& cEvent = pTuple.first(); + const String& cAttr = pTuple.second(); + + TiXmlElement pNode("animation"); + std::vector<std::string> attrs = StringUtils::Split(cAttr, " "); + for (const auto& i : attrs) + { + std::vector<std::string> attrs2 = StringUtils::Split(i, "="); + if (attrs2.size() == 2) + pNode.SetAttribute(attrs2[0], attrs2[1]); + } + TiXmlText value(cEvent.c_str()); + pNode.InsertEndChild(value); + pRoot->InsertEndChild(pNode); + } + + const CRect animRect((float)dwPosX, (float)dwPosY, (float)dwPosX + dwWidth, (float)dwPosY + dwHeight); + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + { + CGUIControlFactory::GetAnimations(pRoot, animRect, iParentId, animations); + pGUIControl->SetAnimations(animations); + } + } + + void Control::setPosition(long x, long y) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + dwPosX = x; + dwPosY = y; + if (pGUIControl) + pGUIControl->SetPosition((float)dwPosX, (float)dwPosY); + } + + void Control::setWidth(long width) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + dwWidth = width; + if (pGUIControl) + pGUIControl->SetWidth((float)dwWidth); + } + + void Control::setHeight(long height) + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + dwHeight = height; + if (pGUIControl) + pGUIControl->SetHeight((float)dwHeight); + } + + void Control::setNavigation(const Control* up, const Control* down, + const Control* left, const Control* right) + { + if(iControlId == 0) + throw WindowException("Control has to be added to a window first"); + + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + { + pGUIControl->SetAction(ACTION_MOVE_UP, CGUIAction(up->iControlId)); + pGUIControl->SetAction(ACTION_MOVE_DOWN, CGUIAction(down->iControlId)); + pGUIControl->SetAction(ACTION_MOVE_LEFT, CGUIAction(left->iControlId)); + pGUIControl->SetAction(ACTION_MOVE_RIGHT, CGUIAction(right->iControlId)); + } + } + } + + void Control::controlUp(const Control* control) + { + if(iControlId == 0) + throw WindowException("Control has to be added to a window first"); + + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + pGUIControl->SetAction(ACTION_MOVE_UP, CGUIAction(control->iControlId)); + } + } + + void Control::controlDown(const Control* control) + { + if(iControlId == 0) + throw WindowException("Control has to be added to a window first"); + + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + pGUIControl->SetAction(ACTION_MOVE_DOWN, CGUIAction(control->iControlId)); + } + } + + void Control::controlLeft(const Control* control) + { + if(iControlId == 0) + throw WindowException("Control has to be added to a window first"); + + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + pGUIControl->SetAction(ACTION_MOVE_LEFT, CGUIAction(control->iControlId)); + } + } + + void Control::controlRight(const Control* control) + { + if(iControlId == 0) + throw WindowException("Control has to be added to a window first"); + + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + if (pGUIControl) + pGUIControl->SetAction(ACTION_MOVE_RIGHT, CGUIAction(control->iControlId)); + } + } + + // ============================================================ + // ControlSpin + // ============================================================ + ControlSpin::ControlSpin() + { + // default values for spin control + color = 0xffffffff; + dwPosX = 0; + dwPosY = 0; + dwWidth = 16; + dwHeight = 16; + + // get default images + strTextureUp = XBMCAddonUtils::getDefaultImage("listcontrol", "textureup"); + strTextureDown = XBMCAddonUtils::getDefaultImage("listcontrol", "texturedown"); + strTextureUpFocus = XBMCAddonUtils::getDefaultImage("listcontrol", "textureupfocus"); + strTextureDownFocus = XBMCAddonUtils::getDefaultImage("listcontrol", "texturedownfocus"); + strTextureUpDisabled = XBMCAddonUtils::getDefaultImage("listcontrol", "textureupdisabled"); + strTextureDownDisabled = XBMCAddonUtils::getDefaultImage("listcontrol", "texturedowndisabled"); + } + + void ControlSpin::setTextures(const char* up, const char* down, + const char* upFocus, + const char* downFocus, + const char* upDisabled, + const char* downDisabled) + { + strTextureUp = up; + strTextureDown = down; + strTextureUpFocus = upFocus; + strTextureDownFocus = downFocus; + strTextureUpDisabled = upDisabled; + strTextureDownDisabled = downDisabled; + /* + PyXBMCGUILock(); + if (self->pGUIControl) + { + CGUISpinControl* pControl = (CGUISpinControl*)self->pGUIControl; + pControl->se + PyXBMCGUIUnlock(); + */ + } + + ControlSpin::~ControlSpin() = default; + // ============================================================ + + // ============================================================ + // ControlLabel + // ============================================================ + ControlLabel::ControlLabel(long x, long y, long width, long height, + const String& label, + const char* font, const char* p_textColor, + const char* p_disabledColor, + long p_alignment, + bool hasPath, long angle) : + strFont("font13"), + textColor(0xffffffff), disabledColor(0x60ffffff), + align(p_alignment), bHasPath(hasPath), iAngle(angle) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + strText = label; + if (font) + strFont = font; + + if (p_textColor) + sscanf(p_textColor, "%x", &textColor); + + if (p_disabledColor) + sscanf( p_disabledColor, "%x", &disabledColor ); + } + + ControlLabel::~ControlLabel() = default; + + CGUIControl* ControlLabel::Create() + { + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = label.focusedColor = textColor; + label.disabledColor = disabledColor; + label.align = align; + label.angle = (float)-iAngle; + pGUIControl = new CGUILabelControl( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight, + label, + false, + bHasPath); + pGUIControl->SetVisible(m_visible); + static_cast<CGUILabelControl*>(pGUIControl)->SetLabel(strText); + return pGUIControl; + } + + void ControlLabel::setLabel(const String& label, const char* font, + const char* textColor, const char* disabledColor, + const char* shadowColor, const char* focusedColor, + const String& label2) + { + strText = label; + CGUIMessage msg(GUI_MSG_LABEL_SET, iParentId, iControlId); + msg.SetLabel(strText); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + String ControlLabel::getLabel() + { + return strText; + } + // ============================================================ + + // ============================================================ + // ControlEdit + // ============================================================ + ControlEdit::ControlEdit(long x, long y, long width, long height, const String& label, + const char* font, const char* _textColor, + const char* _disabledColor, + long _alignment, const char* focusTexture, + const char* noFocusTexture) : + strFont("font13"), textColor(0xffffffff), disabledColor(0x60ffffff), + align(_alignment) + + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + strTextureFocus = focusTexture ? focusTexture : + XBMCAddonUtils::getDefaultImage("edit", "texturefocus"); + + strTextureNoFocus = noFocusTexture ? noFocusTexture : + XBMCAddonUtils::getDefaultImage("edit", "texturenofocus"); + + if (!label.empty()) + { + strText = label; + } + if (font) strFont = font; + if (_textColor) sscanf( _textColor, "%x", &textColor ); + if (_disabledColor) sscanf( _disabledColor, "%x", &disabledColor ); + } + + CGUIControl* ControlEdit::Create() + { + CLabelInfo label; + label.font = g_fontManager.GetFont(strFont); + label.textColor = label.focusedColor = textColor; + label.disabledColor = disabledColor; + label.align = align; + pGUIControl = new CGUIEditControl( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight, + CTextureInfo(strTextureFocus), + CTextureInfo(strTextureNoFocus), + label, + strText); + pGUIControl->SetVisible(m_visible); + + // set label + CGUIMessage msg(GUI_MSG_LABEL_SET, iParentId, iControlId); + msg.SetLabel(strText); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + + return pGUIControl; + } + + void ControlEdit::setLabel(const String& label, const char* font, + const char* textColor, const char* disabledColor, + const char* shadowColor, const char* focusedColor, + const String& label2) + { + strText = label; + CGUIMessage msg(GUI_MSG_LABEL_SET, iParentId, iControlId); + msg.SetLabel(strText); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + String ControlEdit::getLabel() + { + return strText; + } + + void ControlEdit::setText(const String& text) + { + // create message + CGUIMessage msg(GUI_MSG_LABEL2_SET, iParentId, iControlId); + msg.SetLabel(text); + + // send message + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + String ControlEdit::getText() + { + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, iParentId, iControlId); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg, iParentId); + + return msg.GetLabel(); + } + + void ControlEdit::setType(int type, const String& heading) + { + if (pGUIControl) + { + XBMCAddonUtils::GuiLock(languageHook, false); + static_cast<CGUIEditControl*>(pGUIControl)->SetInputType(static_cast<CGUIEditControl::INPUT_TYPE>(type), CVariant{heading}); + } + } + + // ============================================================ + // ControlList + // ============================================================ + ControlList::ControlList(long x, long y, long width, long height, const char* font, + const char* ctextColor, const char* cbuttonTexture, + const char* cbuttonFocusTexture, + const char* cselectedColor, + long _imageWidth, long _imageHeight, long _itemTextXOffset, + long _itemTextYOffset, long _itemHeight, long _space, long _alignmentY) : + strFont("font13"), + textColor(0xe0f0f0f0), selectedColor(0xffffffff), + imageHeight(_imageHeight), imageWidth(_imageWidth), + itemHeight(_itemHeight), space(_space), + itemTextOffsetX(_itemTextXOffset),itemTextOffsetY(_itemTextYOffset), + alignmentY(_alignmentY) + { + dwPosX = x; + dwPosY = y; + dwWidth = width; + dwHeight = height; + + // create a python spin control + pControlSpin = new ControlSpin(); + + // initialize default values + if (font) + strFont = font; + + if (ctextColor) + sscanf( ctextColor, "%x", &textColor ); + + if (cselectedColor) + sscanf( cselectedColor, "%x", &selectedColor ); + + strTextureButton = cbuttonTexture ? cbuttonTexture : + XBMCAddonUtils::getDefaultImage("listcontrol", "texturenofocus"); + + strTextureButtonFocus = cbuttonFocusTexture ? cbuttonFocusTexture : + XBMCAddonUtils::getDefaultImage("listcontrol", "texturefocus"); + + // default values for spin control + pControlSpin->dwPosX = dwWidth - 35; + pControlSpin->dwPosY = dwHeight - 15; + } + + ControlList::~ControlList() = default; + + CGUIControl* ControlList::Create() + { + CLabelInfo label; + label.align = alignmentY; + label.font = g_fontManager.GetFont(strFont); + label.textColor = label.focusedColor = textColor; + //label.shadowColor = shadowColor; + label.selectedColor = selectedColor; + label.offsetX = (float)itemTextOffsetX; + label.offsetY = (float)itemTextOffsetY; + // Second label should have the same font, alignment, and colours as the first, but + // the offsets should be 0. + CLabelInfo label2 = label; + label2.offsetX = label2.offsetY = 0; + label2.align |= XBFONT_RIGHT; + + pGUIControl = new CGUIListContainer( + iParentId, + iControlId, + (float)dwPosX, + (float)dwPosY, + (float)dwWidth, + (float)dwHeight - pControlSpin->dwHeight - 5, + label, label2, + CTextureInfo(strTextureButton), + CTextureInfo(strTextureButtonFocus), + (float)itemHeight, + (float)imageWidth, (float)imageHeight, + (float)space); + pGUIControl->SetVisible(m_visible); + return pGUIControl; + } + + void ControlList::addItem(const Alternative<String, const XBMCAddon::xbmcgui::ListItem* > & item, bool sendMessage) + { + XBMC_TRACE; + + if (item.which() == first) + internAddListItem(ListItem::fromString(item.former()),sendMessage); + else + internAddListItem(item.later(),sendMessage); + } + + void ControlList::addItems(const std::vector<Alternative<String, const XBMCAddon::xbmcgui::ListItem* > > & items) + { + XBMC_TRACE; + + for (const auto& iter : items) + addItem(iter, false); + sendLabelBind(vecItems.size()); + } + + void ControlList::internAddListItem(const AddonClass::Ref<ListItem>& pListItem, + bool sendMessage) + { + if (pListItem.isNull()) + throw WindowException("NULL ListItem passed to ControlList::addListItem"); + + // add item to objects vector + vecItems.push_back(pListItem); + + // send all of the items ... this is what it did before. + if (sendMessage) + sendLabelBind(vecItems.size()); + } + + void ControlList::sendLabelBind(int tail) + { + // construct a CFileItemList to pass 'em on to the list + CGUIListItemPtr items(new CFileItemList()); + for (unsigned int i = vecItems.size() - tail; i < vecItems.size(); i++) + static_cast<CFileItemList*>(items.get())->Add(vecItems[i]->item); + + CGUIMessage msg(GUI_MSG_LABEL_BIND, iParentId, iControlId, 0, 0, items); + msg.SetPointer(items.get()); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + void ControlList::selectItem(long item) + { + // create message + CGUIMessage msg(GUI_MSG_ITEM_SELECT, iParentId, iControlId, item); + + // send message + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + } + + void ControlList::removeItem(int index) + { + if (index < 0 || index >= (int)vecItems.size()) + throw WindowException("Index out of range"); + + vecItems.erase(vecItems.begin() + index); + + sendLabelBind(vecItems.size()); + } + + void ControlList::reset() + { + CGUIMessage msg(GUI_MSG_LABEL_RESET, iParentId, iControlId); + + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iParentId); + + // delete all items from vector + // delete all ListItem from vector + vecItems.clear(); // this should delete all of the objects + } + + Control* ControlList::getSpinControl() + { + return pControlSpin; + } + + long ControlList::getSelectedPosition() + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + + // create message + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, iParentId, iControlId); + long pos = -1; + + // send message + if (!vecItems.empty() && pGUIControl) + { + pGUIControl->OnMessage(msg); + pos = msg.GetParam1(); + } + + return pos; + } + + XBMCAddon::xbmcgui::ListItem* ControlList::getSelectedItem() + { + DelayedCallGuard dcguard(languageHook); + XBMCAddonUtils::GuiLock lock(languageHook, false); + + // create message + CGUIMessage msg(GUI_MSG_ITEM_SELECTED, iParentId, iControlId); + AddonClass::Ref<ListItem> pListItem = NULL; + + // send message + if (!vecItems.empty() && pGUIControl) + { + pGUIControl->OnMessage(msg); + if (msg.GetParam1() >= 0 && (size_t)msg.GetParam1() < vecItems.size()) + pListItem = vecItems[msg.GetParam1()]; + } + + return pListItem.get(); + } + + void ControlList::setImageDimensions(long imageWidth,long imageHeight) + { + CLog::Log(LOGWARNING,"ControlList::setImageDimensions was called but ... it currently isn't defined to do anything."); + /* + PyXBMCGUILock(); + if (self->pGUIControl) + { + CGUIListControl* pListControl = (CGUIListControl*) self->pGUIControl; + pListControl->SetImageDimensions((float)self->dwImageWidth, (float)self->dwImageHeight ); + } + PyXBMCGUIUnlock(); + */ + } + + void ControlList::setItemHeight(long height) + { + CLog::Log(LOGWARNING,"ControlList::setItemHeight was called but ... it currently isn't defined to do anything."); + /* + PyXBMCGUILock(); + if (self->pGUIControl) + { + CGUIListControl* pListControl = (CGUIListControl*) self->pGUIControl; + pListControl->SetItemHeight((float)self->dwItemHeight); + } + PyXBMCGUIUnlock(); + */ + } + + void ControlList::setSpace(int space) + { + CLog::Log(LOGWARNING,"ControlList::setSpace was called but ... it currently isn't defined to do anything."); + /* + PyXBMCGUILock(); + if (self->pGUIControl) + { + CGUIListControl* pListControl = (CGUIListControl*) self->pGUIControl; + pListControl->SetSpaceBetweenItems((float)self->dwSpace); + } + PyXBMCGUIUnlock(); + */ + } + + void ControlList::setPageControlVisible(bool visible) + { + CLog::Log(LOGWARNING,"ControlList::setPageControlVisible was called but ... it currently isn't defined to do anything."); + + // char isOn = true; + + /* + PyXBMCGUILock(); + if (self->pGUIControl) + { + ((CGUIListControl*)self->pGUIControl)->SetPageControlVisible((bool)isOn ); + } + PyXBMCGUIUnlock(); + */ + } + + long ControlList::size() + { + return (long)vecItems.size(); + } + + long ControlList::getItemHeight() + { + return (long)itemHeight; + } + + long ControlList::getSpace() + { + return (long)space; + } + + XBMCAddon::xbmcgui::ListItem* ControlList::getListItem(int index) + { + if (index < 0 || index >= (int)vecItems.size()) + throw WindowException("Index out of range"); + + AddonClass::Ref<ListItem> pListItem = vecItems[index]; + return pListItem.get(); + } + + void ControlList::setStaticContent(const ListItemList* pitems) + { + const ListItemList& vecItems = *pitems; + + std::vector<CGUIStaticItemPtr> items; + + for (unsigned int item = 0; item < vecItems.size(); item++) + { + ListItem* pItem = vecItems[item]; + + // NOTE: This code has likely not worked fully correctly for some time + // In particular, the click behaviour won't be working. + CGUIStaticItemPtr newItem(new CGUIStaticItem(*pItem->item)); + items.push_back(newItem); + } + + // set static list + std::unique_ptr<IListProvider> provider = std::make_unique<CStaticListProvider>(items); + static_cast<CGUIBaseContainer*>(pGUIControl)->SetListProvider(std::move(provider)); + } + + // ============================================================ + + } +} diff --git a/xbmc/interfaces/legacy/Control.h b/xbmc/interfaces/legacy/Control.h new file mode 100644 index 0000000..7441310 --- /dev/null +++ b/xbmc/interfaces/legacy/Control.h @@ -0,0 +1,2960 @@ +/* + * 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 "Alternative.h" +#include "ListItem.h" +#include "Tuple.h" +#include "guilib/GUIControl.h" +#include "guilib/GUIFont.h" +#include "input/Key.h" +#include "swighelper.h" +#include "utils/ColorUtils.h" + +#include <vector> + + +// hardcoded offsets for button controls (and controls that use button controls) +// ideally they should be dynamically read in as with all the other properties. +#define CONTROL_TEXT_OFFSET_X 10 +#define CONTROL_TEXT_OFFSET_Y 2 + +namespace XBMCAddon +{ + namespace xbmcgui + { + + /// \defgroup python_xbmcgui_control Control + /// \ingroup python_xbmcgui + /// @{ + /// @brief **Code based skin access.** + /// + /// Offers classes and functions that manipulate the add-on gui controls. + /// + ///------------------------------------------------------------------------- + /// + /// \python_class{ Control() } + /// + /// **Code based skin access.** + /// + /// Kodi is noted as having a very flexible and robust framework for its + /// GUI, making theme-skinning and personal customization very accessible. + /// Users can create their own skin (or modify an existing skin) and share + /// it with others. + /// + /// Kodi includes a new GUI library written from scratch. This library + /// allows you to skin/change everything you see in Kodi, from the images, + /// the sizes and positions of all controls, colours, fonts, and text, + /// through to altering navigation and even adding new functionality. The + /// skin system is quite complex, and this portion of the manual is dedicated + /// to providing in depth information on how it all works, along with tips + /// to make the experience a little more pleasant. + /// + ///------------------------------------------------------------------------- + // + class Control : public AddonClass + { + protected: + Control() = default; + + public: + ~Control() override; + +#ifndef SWIG + virtual CGUIControl* Create(); +#endif + + // currently we only accept messages from a button or controllist with a select action + virtual bool canAcceptMessages(int actionId) { return false; } + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getId() } + /// Returns the control's current id as an integer. + /// + /// @return int - Current id + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// id = self.button.getId() + /// ... + /// ~~~~~~~~~~~~~ + /// + getId() +#else + virtual int getId() { return iControlId; } +#endif + + inline bool operator==(const Control& other) const { return iControlId == other.iControlId; } + inline bool operator>(const Control& other) const { return iControlId > other.iControlId; } + inline bool operator<(const Control& other) const { return iControlId < other.iControlId; } + + // hack this because it returns a tuple +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getPosition() } + /// Returns the control's current position as a x,y integer tuple. + /// + /// @return Current position as a x,y integer tuple + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// pos = self.button.getPosition() + /// ... + /// ~~~~~~~~~~~~~ + /// + getPosition(); +#else + virtual std::vector<int> getPosition(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getX() } + /// Returns the control's current X position. + /// + /// @return int - Current X position + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// posX = self.button.getX() + /// ... + /// ~~~~~~~~~~~~~ + /// + getX(); +#else + int getX() { return dwPosX; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getY() } + /// Returns the control's current Y position. + /// + /// @return int - Current Y position + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// posY = self.button.getY() + /// ... + /// ~~~~~~~~~~~~~ + /// + getY(); +#else + int getY() { return dwPosY; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getHeight() } + /// Returns the control's current height as an integer. + /// + /// @return int - Current height + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// height = self.button.getHeight() + /// ... + /// ~~~~~~~~~~~~~ + /// + getHeight(); +#else + virtual int getHeight() { return dwHeight; } +#endif + + // getWidth() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ getWidth() } + /// Returns the control's current width as an integer. + /// + /// @return int - Current width + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// width = self.button.getWidth() + /// ... + /// ~~~~~~~~~~~~~ + /// + getWidth(); +#else + virtual int getWidth() { return dwWidth; } +#endif + + // setEnabled() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setEnabled(enabled) } + /// Sets the control's enabled/disabled state. + /// + /// @param enabled bool - True=enabled / False=disabled. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.setEnabled(False) + /// ... + /// ~~~~~~~~~~~~~ + /// + setEnabled(...); +#else + virtual void setEnabled(bool enabled); +#endif + + // setVisible() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setVisible(visible) } + /// Sets the control's visible/hidden state. + /// \anchor python_xbmcgui_control_setVisible + /// + /// @param visible bool - True=visible / False=hidden. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 You can now define the visible state of a control before it being + /// added to a window. This value will be taken into account when the control is later + /// added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.setVisible(False) + /// ... + /// ~~~~~~~~~~~~~ + /// + setVisible(...); +#else + virtual void setVisible(bool visible); +#endif + + // isVisible() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ isVisible() } + /// Get the control's visible/hidden state with respect to the container/window + /// + /// @note If a given control is set visible (c.f. \ref python_xbmcgui_control_setVisible "setVisible()" + /// but was not yet added to a window, this method will return `False` (the control is not visible yet since + /// it was not added to the window). + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// if self.button.isVisible(): + /// ... + /// ~~~~~~~~~~~~~ + /// + isVisible(...); +#else + virtual bool isVisible(); +#endif + + // setVisibleCondition() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setVisibleCondition(visible[,allowHiddenFocus]) } + /// Sets the control's visible condition. + /// + /// Allows Kodi to control the visible status of the control. + /// + /// [List of Conditions](http://kodi.wiki/view/List_of_Boolean_Conditions) + /// + /// @param visible string - Visible condition + /// @param allowHiddenFocus [opt] bool - True=gains focus even if + /// hidden + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setVisibleCondition(visible[,allowHiddenFocus]) + /// self.button.setVisibleCondition('[Control.IsVisible(41) + !Control.IsVisible(12)]', True) + /// ... + /// ~~~~~~~~~~~~~ + /// + setVisibleCondition(...); +#else + virtual void setVisibleCondition(const char* visible, bool allowHiddenFocus = false); +#endif + + // setEnableCondition() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setEnableCondition(enable) } + /// Sets the control's enabled condition. + /// + /// Allows Kodi to control the enabled status of the control. + /// + /// [List of Conditions](http://kodi.wiki/view/List_of_Boolean_Conditions) + /// + /// @param enable string - Enable condition. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setEnableCondition(enable) + /// self.button.setEnableCondition('System.InternetState') + /// ... + /// ~~~~~~~~~~~~~ + /// + setEnableCondition(...); +#else + virtual void setEnableCondition(const char* enable); +#endif + + // setAnimations() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setAnimations([(event, attr,)*]) } + /// Sets the control's animations. + /// + /// <b>[(event,attr,)*]</b>: list - A list of tuples consisting of event + /// and attributes pairs. + /// + /// [Animating your skin](http://kodi.wiki/view/Animating_Your_Skin) + /// + /// @param event string - The event to animate. + /// @param attr string - The whole attribute string + /// separated by spaces. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setAnimations([(event, attr,)*]) + /// self.button.setAnimations([('focus', 'effect=zoom end=90,247,220,56 time=0',)]) + /// ... + /// ~~~~~~~~~~~~~ + /// + setAnimations(...); +#else + virtual void setAnimations(const std::vector< Tuple<String,String> >& eventAttr); +#endif + + // setPosition() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setPosition(x, y) } + /// Sets the controls position. + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// + /// @note You may use negative integers. (e.g sliding a control into view) + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.setPosition(100, 250) + /// ... + /// ~~~~~~~~~~~~~ + /// + setPosition(...); +#else + virtual void setPosition(long x, long y); +#endif + + // setWidth() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setWidth(width) } + /// Sets the controls width. + /// + /// @param width integer - width of control. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.image.setWidth(100) + /// ... + /// ~~~~~~~~~~~~~ + /// + setWidth(...); +#else + virtual void setWidth(long width); +#endif + + // setHeight() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setHeight(height) } + /// Sets the controls height. + /// + /// @param height integer - height of control. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.image.setHeight(100) + /// ... + /// ~~~~~~~~~~~~~ + /// + setHeight(...); +#else + virtual void setHeight(long height); +#endif + + // setNavigation() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ setNavigation(up, down, left, right) } + /// Sets the controls navigation. + /// + /// @param up control object - control to navigate to on up. + /// @param down control object - control to navigate to on down. + /// @param left control object - control to navigate to on left. + /// @param right control object - control to navigate to on right. + /// @throw TypeError if one of the supplied arguments is not a + /// control type. + /// @throw ReferenceError if one of the controls is not added to a + /// window. + /// + /// @note Same as controlUp(), controlDown(), controlLeft(), controlRight(). + /// Set to self to disable navigation for that direction. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.setNavigation(self.button1, self.button2, self.button3, self.button4) + /// ... + /// ~~~~~~~~~~~~~ + // + setNavigation(...); +#else + virtual void setNavigation(const Control* up, const Control* down, + const Control* left, const Control* right); +#endif + + // controlUp() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ controlUp(control) } + /// Sets the controls up navigation. + /// + /// @param control control object - control to navigate to on up. + /// @throw TypeError if one of the supplied arguments is not a + /// control type. + /// @throw ReferenceError if one of the controls is not added to a + /// window. + /// + /// + /// @note You can also use setNavigation(). Set to self to disable navigation. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.controlUp(self.button1) + /// ... + /// ~~~~~~~~~~~~~ + /// + controlUp(...); +#else + virtual void controlUp(const Control* up); +#endif + + // controlDown() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ controlDown(control) } + /// Sets the controls down navigation. + /// + /// @param control control object - control to navigate to on down. + /// @throw TypeError if one of the supplied arguments is not a + /// control type. + /// @throw ReferenceError if one of the controls is not added to a + /// window. + /// + /// + /// @note You can also use setNavigation(). Set to self to disable navigation. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.controlDown(self.button1) + /// ... + /// ~~~~~~~~~~~~~ + /// + controlDown(...); +#else + virtual void controlDown(const Control* control); +#endif + + // controlLeft() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ controlLeft(control) } + /// Sets the controls left navigation. + /// + /// @param control control object - control to navigate to on left. + /// @throw TypeError if one of the supplied arguments is not a + /// control type. + /// @throw ReferenceError if one of the controls is not added to a + /// window. + /// + /// + /// @note You can also use setNavigation(). Set to self to disable navigation. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.controlLeft(self.button1) + /// ... + /// ~~~~~~~~~~~~~ + /// + controlLeft(...); +#else + virtual void controlLeft(const Control* control); +#endif + + // controlRight() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control + /// @brief \python_func{ controlRight(control) } + /// Sets the controls right navigation. + /// + /// @param control control object - control to navigate to on right. + /// @throw TypeError if one of the supplied arguments is not a + /// control type. + /// @throw ReferenceError if one of the controls is not added to a + /// window. + /// + /// + /// @note You can also use setNavigation(). Set to self to disable navigation. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.button.controlRight(self.button1) + /// ... + /// ~~~~~~~~~~~~~ + /// + controlRight(...); +#else + virtual void controlRight(const Control* control); +#endif + +#ifndef SWIG + int iControlId = 0; + int iParentId = 0; + int dwPosX = 0; + int dwPosY = 0; + int dwWidth = 0; + int dwHeight = 0; + int iControlUp = 0; + int iControlDown = 0; + int iControlLeft = 0; + int iControlRight = 0; + std::string m_label{}; + bool m_visible{true}; + CGUIControl* pGUIControl = nullptr; +#endif + + }; + /// @} + + /// \defgroup python_xbmcgui_control_spin Subclass - ControlSpin + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used for cycling up/down controls.** + /// + /// Offers classes and functions that manipulate the add-on gui controls. + /// + ///------------------------------------------------------------------------- + /// + /// \python_class{ ControlSpin() } + /// + /// **Code based skin access.** + /// + /// The spin control is used for when a list of options can be chosen (such + /// as a page up/down control). You can choose the position, size, and look + /// of the spin control. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @warning **Not working yet**. + /// You can't create this object, it is returned by objects like ControlTextBox and ControlList. + /// + /// + ///------------------------------------------------------------------------- + /// + /// + class ControlSpin : public Control + { + public: + ~ControlSpin() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_spin + /// @brief \python_func{ setTextures(up, down, upFocus, downFocus) } + /// Sets textures for this control. + /// + /// Texture are image files that are used for example in the skin + /// + /// @warning **Not working yet**. + /// + /// @param up label - for the up arrow + /// when it doesn't have focus. + /// @param down label - for the down button + /// when it is not focused. + /// @param upFocus label - for the up button + /// when it has focus. + /// @param downFocus label - for the down button + /// when it has focus. + /// @param upDisabled label - for the up arrow + /// when the button is disabled. + /// @param downDisabled label - for the up arrow + /// when the button is disabled. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setTextures(up, down, upFocus, downFocus, upDisabled, downDisabled) + /// + /// ... + /// ~~~~~~~~~~~~~ + /// + setTextures(...); +#else + virtual void setTextures(const char* up, const char* down, + const char* upFocus, + const char* downFocus, + const char* upDisabled, const char* downDisabled); +#endif + +#ifndef SWIG + UTILS::COLOR::Color color; + std::string strTextureUp; + std::string strTextureDown; + std::string strTextureUpFocus; + std::string strTextureDownFocus; + std::string strTextureUpDisabled; + std::string strTextureDownDisabled; +#endif + + private: + ControlSpin(); + + friend class Window; + friend class ControlList; + + }; + /// @} + + /// \defgroup python_xbmcgui_control_label Subclass - ControlLabel + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to show some lines of text.** + /// + /// \python_class{ ControlLabel(x, y, width, height, label[, font, textColor, + /// disabledColor, alignment, hasPath, angle]) } + /// + /// The label control is used for displaying text in Kodi. You can choose + /// the font, size, colour, location and contents of the text to be + /// displayed. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param label string or unicode - text string. + /// @param font [opt] string - font used for label + /// text. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled + /// label's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// label's label. (e.g. '0xFFFF3300') + /// @param alignment [opt] integer - alignment of label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// @param hasPath [opt] bool - True=stores a + /// path / False=no path + /// @param angle [opt] integer - angle of control. + /// (<b>+</b> rotates CCW, <b>-</b> rotates C) + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # ControlLabel(x, y, width, height, label[, font, textColor, + /// # disabledColor, alignment, hasPath, angle]) + /// self.label = xbmcgui.ControlLabel(100, 250, 125, 75, 'Status', angle=45) + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlLabel : public Control + { + public: + ControlLabel(long x, long y, long width, long height, const String& label, + const char* font = NULL, const char* textColor = NULL, + const char* disabledColor = NULL, + long alignment = XBFONT_LEFT, + bool hasPath = false, long angle = 0); + + ~ControlLabel() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_label + /// @brief \python_func{ getLabel() } + /// Returns the text value for this label. + /// + /// @return This label + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// label = self.label.getLabel() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel(); +#else + virtual String getLabel(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_label + /// @brief \python_func{ setLabel(label[, font, textColor, disabledColor, shadowColor, focusedColor, label2]) } + /// Sets text for this label. + /// + /// @param label string or unicode - text string. + /// @param font [opt] string - font used for label text. + /// (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled + /// label's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// label's label. (e.g. '0xFFFF3300') + /// @param shadowColor [opt] hexstring - color of button's + /// label's shadow. (e.g. '0xFF000000') + /// @param focusedColor [opt] hexstring - color of focused + /// button's label. (e.g. '0xFF00FFFF') + /// @param label2 [opt] string or unicode - text string. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.label.setLabel('Status') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel(...); +#else + virtual void setLabel(const String& label = emptyString, + const char* font = NULL, + const char* textColor = NULL, + const char* disabledColor = NULL, + const char* shadowColor = NULL, + const char* focusedColor = NULL, + const String& label2 = emptyString); +#endif + +#ifndef SWIG + ControlLabel() = default; + + std::string strFont; + std::string strText; + UTILS::COLOR::Color textColor; + UTILS::COLOR::Color disabledColor; + uint32_t align; + bool bHasPath = false; + int iAngle = 0; + + CGUIControl* Create() override; + +#endif + }; + /// @} + + // ControlEdit class + /// \defgroup python_xbmcgui_control_edit Subclass - ControlEdit + /// \ingroup python_xbmcgui_control + /// @{ + /// **Used as an input control for the osd keyboard and other input fields.** + /// + /// \python_class{ ControlEdit(x, y, width, height, label[, font, textColor, + /// disabledColor, alignment, focusTexture, noFocusTexture]) } + /// + /// The edit control allows a user to input text in Kodi. You can choose the + /// font, size, colour, location and header of the text to be displayed. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param label string or unicode - text string. + /// @param font [opt] string - font used for label text. + /// (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled + /// label's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// label's label. (e.g. '0xFFFF3300') + /// @param alignment [opt] integer - alignment of label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// @param focusTexture [opt] string - filename for focus texture. + /// @param noFocusTexture [opt] string - filename for no focus texture. + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword.\n + /// After you create the control, you need to add it to the window with + /// addControl().\n + /// + /// + /// + ///------------------------------------------------------------------------- + /// @python_v18 Deprecated **isPassword** + /// @python_v19 Removed **isPassword** + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.edit = xbmcgui.ControlEdit(100, 250, 125, 75, 'Status') + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlEdit : public Control + { + public: + ControlEdit(long x, long y, long width, long height, const String& label, + const char* font = NULL, const char* textColor = NULL, + const char* disabledColor = NULL, + long _alignment = XBFONT_LEFT, const char* focusTexture = NULL, + const char* noFocusTexture = NULL); + + + // setLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_edit + /// @brief \python_func{ setLabel(label[, font, textColor, disabledColor, shadowColor, focusedColor, label2]) } + /// Sets text heading for this edit control. + /// + /// @param label string or unicode - text string. + /// @param font [opt] string - font used for label text. + /// (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled + /// label's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// label's label. (e.g. '0xFFFF3300') + /// @param shadowColor [opt] hexstring - color of button's + /// label's shadow. (e.g. '0xFF000000') + /// @param focusedColor [opt] hexstring - color of focused + /// button's label. (e.g. '0xFF00FFFF') + /// @param label2 [opt] string or unicode - text string. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.edit.setLabel('Status') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel(...); +#else + virtual void setLabel(const String& label = emptyString, + const char* font = NULL, + const char* textColor = NULL, + const char* disabledColor = NULL, + const char* shadowColor = NULL, + const char* focusedColor = NULL, + const String& label2 = emptyString); +#endif + + // getLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_edit + /// @brief \python_func{ getLabel() } + /// Returns the text heading for this edit control. + /// + /// @return Heading text + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// label = self.edit.getLabel() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel(); +#else + virtual String getLabel(); +#endif + + // setText() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_edit + /// @brief \python_func{ setText(value) } + /// Sets text value for this edit control. + /// + /// @param value string or unicode - text string. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.edit.setText('online') + /// ... + /// ~~~~~~~~~~~~~ + /// + setText(...); +#else + virtual void setText(const String& text); +#endif + + // getText() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_edit + /// @brief \python_func{ getText() } + /// Returns the text value for this edit control. + /// + /// @return Text value of control + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// value = self.edit.getText() + /// ... + /// ~~~~~~~~~~~~~ + /// + getText(); +#else + virtual String getText(); +#endif + +#ifndef SWIG + ControlEdit() = default; + + std::string strFont; + std::string strText; + std::string strTextureFocus; + std::string strTextureNoFocus; + UTILS::COLOR::Color textColor; + UTILS::COLOR::Color disabledColor; + uint32_t align; + + CGUIControl* Create() override; +#endif + + // setType() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_edit + /// @brief \python_func{ setType(type, heading) } + /// Sets the type of this edit control. + /// + /// @param type integer - type of the edit control. + /// | Param | Definition | + /// |-----------------------------------------------|:--------------------------------------------| + /// | xbmcgui.INPUT_TYPE_TEXT | (standard keyboard) + /// | xbmcgui.INPUT_TYPE_NUMBER | (format: #) + /// | xbmcgui.INPUT_TYPE_DATE | (format: DD/MM/YYYY) + /// | xbmcgui.INPUT_TYPE_TIME | (format: HH:MM) + /// | xbmcgui.INPUT_TYPE_IPADDRESS | (format: #.#.#.#) + /// | xbmcgui.INPUT_TYPE_PASSWORD | (input is masked) + /// | xbmcgui.INPUT_TYPE_PASSWORD_MD5 | (input is masked, return md5 hash of input) + /// | xbmcgui.INPUT_TYPE_SECONDS | (format: SS or MM:SS or HH:MM:SS or MM min) + /// | xbmcgui.INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW | (numeric input is masked) + /// @param heading string or unicode - heading that will be used for to numeric or + /// keyboard dialog when the edit control is clicked. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// @python_v19 New option added to mask numeric input. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.edit.setType(xbmcgui.INPUT_TYPE_TIME, 'Please enter the time') + /// ... + /// ~~~~~~~~~~~~~ + /// + setType(...); +#else + virtual void setType(int type, const String& heading); +#endif + }; + /// @} + + // ControlList class + /// \defgroup python_xbmcgui_control_list Subclass - ControlList + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used for a scrolling lists of items. Replaces the list control.** + /// + /// \python_class{ ControlList(x, y, width, height[, font, textColor, buttonTexture, buttonFocusTexture, + /// selectedColor, imageWidth, imageHeight, itemTextXOffset, itemTextYOffset, + /// itemHeight, space, alignmentY, shadowColor]) } + /// + /// The list container is one of several containers used to display items + /// from file lists in various ways. The list container is very + /// flexible - it's only restriction is that it is a list - i.e. a single + /// column or row of items. The layout of the items is very flexible and + /// is up to the skinner. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param font [opt] string - font used for items label. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of items label. (e.g. '0xFFFFFFFF') + /// @param buttonTexture [opt] string - filename for focus texture. + /// @param buttonFocusTexture [opt] string - filename for no focus texture. + /// @param selectedColor [opt] integer - x offset of label. + /// @param imageWidth [opt] integer - width of items icon or thumbnail. + /// @param imageHeight [opt] integer - height of items icon or thumbnail. + /// @param itemTextXOffset [opt] integer - x offset of items label. + /// @param itemTextYOffset [opt] integer - y offset of items label. + /// @param itemHeight [opt] integer - height of items. + /// @param space [opt] integer - space between items. + /// @param alignmentY [opt] integer - Y-axis alignment of items label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// @param shadowColor [opt] hexstring - color of items + /// label's shadow. (e.g. '0xFF000000') + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window + /// with addControl(). + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.cList = xbmcgui.ControlList(100, 250, 200, 250, 'font14', space=5) + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlList : public Control + { + void internAddListItem(const AddonClass::Ref<ListItem>& listitem, bool sendMessage); + + public: + ControlList(long x, long y, long width, long height, const char* font = NULL, + const char* textColor = NULL, const char* buttonTexture = NULL, + const char* buttonFocusTexture = NULL, + const char* selectedColor = NULL, + long _imageWidth=10, long _imageHeight=10, long _itemTextXOffset = CONTROL_TEXT_OFFSET_X, + long _itemTextYOffset = CONTROL_TEXT_OFFSET_Y, long _itemHeight = 27, long _space = 2, + long _alignmentY = XBFONT_CENTER_Y); + + ~ControlList() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ addItem(item) } + /// Add a new item to this list control. + /// + /// @param item string, unicode or ListItem - item to add. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.addItem('Reboot Kodi') + /// ... + /// ~~~~~~~~~~~~~ + /// + addItem(...); +#else + virtual void addItem(const Alternative<String, const XBMCAddon::xbmcgui::ListItem* > & item, bool sendMessage = true); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ addItems(items) } + /// Adds a list of listitems or strings to this list control. + /// + /// @param items List - list of strings, unicode objects or ListItems to add. + /// + /// @note You can use the above as keywords for arguments. + /// + /// Large lists benefit considerably, than using the standard addItem() + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.addItems(items=listitems) + /// ... + /// ~~~~~~~~~~~~~ + /// + addItems(...); +#else + virtual void addItems(const std::vector<Alternative<String, const XBMCAddon::xbmcgui::ListItem* > > & items); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ selectItem(item) } + /// Select an item by index number. + /// + /// @param item integer - index number of the item to select. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.selectItem(12) + /// ... + /// ~~~~~~~~~~~~~ + /// + selectItem(...); +#else + virtual void selectItem(long item); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ removeItem(index) } + /// Remove an item by index number. + /// + /// @param index integer - index number of the item to remove. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.removeItem(12) + /// ... + /// ~~~~~~~~~~~~~ + /// + removeItem(...); +#else + virtual void removeItem(int index); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ reset() } + /// Clear all ListItems in this control list. + /// + /// @warning Calling `reset()` will destroy any `ListItem` objects in the + /// `ControlList` if not hold by any other class. Make sure you + /// you don't call `addItems()` with the previous `ListItem` references + /// after calling `reset()`. If you need to preserve the `ListItem` objects after + /// `reset()` make sure you store them as members of your `WindowXML` class (see examples). + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Examples:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.reset() + /// ... + /// ~~~~~~~~~~~~~ + /// + /// The below example shows you how you can reset the `ControlList` but this time avoiding `ListItem` object + /// destruction. The example assumes `self` as a `WindowXMLDialog` instance containing a `ControlList` + /// with id = 800. The class preserves the `ListItem` objects in a class member variable. + /// + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # Get all the ListItem objects in the control + /// self.list_control = self.getControl(800) # ControlList object + /// self.listitems = [self.list_control.getListItem(item) for item in range(0, self.list_control.size())] + /// # Reset the ControlList control + /// self.list_control.reset() + /// # + /// # do something with your ListItem objects here (e.g. sorting.) + /// # ... + /// # + /// # Add them again to the ControlList + /// self.list_control.addItems(self.listitems) + /// ... + /// ~~~~~~~~~~~~~ + /// + reset(); +#else + virtual void reset(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getSpinControl() } + /// Returns the associated ControlSpin object. + /// + /// @warning Not working completely yet\n + /// After adding this control list to a window it is not possible to change + /// the settings of this spin control. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// ctl = cList.getSpinControl() + /// ... + /// ~~~~~~~~~~~~~ + /// + getSpinControl(); +#else + virtual Control* getSpinControl(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getSelectedPosition() } + /// Returns the position of the selected item as an integer. + /// + /// @note Returns -1 for empty lists. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// pos = cList.getSelectedPosition() + /// ... + /// ~~~~~~~~~~~~~ + /// + getSelectedPosition(); +#else + virtual long getSelectedPosition(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getSelectedItem() } + /// Returns the selected item as a ListItem object. + /// + /// @return The selected item + /// + /// + /// @note Same as getSelectedPosition(), but instead of an integer a ListItem object + /// is returned. Returns None for empty lists.\n + /// See windowexample.py on how to use this. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// item = cList.getSelectedItem() + /// ... + /// ~~~~~~~~~~~~~ + /// + getSelectedItem(); +#else + virtual XBMCAddon::xbmcgui::ListItem* getSelectedItem(); +#endif + + // setImageDimensions() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ setImageDimensions(imageWidth, imageHeight) } + /// Sets the width/height of items icon or thumbnail. + /// + /// @param imageWidth [opt] integer - width of items icon or thumbnail. + /// @param imageHeight [opt] integer - height of items icon or thumbnail. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.setImageDimensions(18, 18) + /// ... + /// ~~~~~~~~~~~~~ + /// + setImageDimensions(...); +#else + virtual void setImageDimensions(long imageWidth,long imageHeight); +#endif + + // setItemHeight() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @brief \python_func{ setItemHeight(itemHeight) } + /// Sets the height of items. + /// + /// @param itemHeight integer - height of items. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.setItemHeight(25) + /// ... + /// ~~~~~~~~~~~~~ + /// + setItemHeight(...); +#else + virtual void setItemHeight(long height); +#endif + + // setSpace() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ setSpace(space) } + /// Sets the space between items. + /// + /// @param space [opt] integer - space between items. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.setSpace(5) + /// ... + /// ~~~~~~~~~~~~~ + /// + setSpace(...); +#else + virtual void setSpace(int space); +#endif + + // setPageControlVisible() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ setPageControlVisible(visible) } + /// Sets the spin control's visible/hidden state. + /// + /// @param visible boolean - True=visible / False=hidden. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.setPageControlVisible(True) + /// ... + /// ~~~~~~~~~~~~~ + /// + setPageControlVisible(...); +#else + virtual void setPageControlVisible(bool visible); +#endif + + // size() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ size() } + /// Returns the total number of items in this list control as an integer. + /// + /// @return Total number of items + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cnt = cList.size() + /// ... + /// ~~~~~~~~~~~~~ + /// + size(); +#else + virtual long size(); +#endif + + // getItemHeight() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getItemHeight() } + /// Returns the control's current item height as an integer. + /// + /// @return Current item heigh + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// item_height = self.cList.getItemHeight() + /// ... + /// ~~~~~~~~~~~~~ + /// + getItemHeight(); +#else + virtual long getItemHeight(); +#endif + + // getSpace() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getSpace() } + /// Returns the control's space between items as an integer. + /// + /// @return Space between items + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// gap = self.cList.getSpace() + /// ... + /// ~~~~~~~~~~~~~ + /// + getSpace(); +#else + virtual long getSpace(); +#endif + + // getListItem() method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ getListItem(index) } + /// Returns a given ListItem in this List. + /// + /// @param index integer - index number of item to return. + /// @return List item + /// @throw ValueError if index is out of range. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem = cList.getListItem(6) + /// ... + /// ~~~~~~~~~~~~~ + /// + getListItem(...); +#else + virtual XBMCAddon::xbmcgui::ListItem* getListItem(int index); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_list + /// @brief \python_func{ setStaticContent(items) } + /// Fills a static list with a list of listitems. + /// + /// @param items List - list of listitems to add. + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// cList.setStaticContent(items=listitems) + /// ... + /// ~~~~~~~~~~~~~ + /// + setStaticContent(...); +#else + virtual void setStaticContent(const ListItemList* items); +#endif + +#ifndef SWIG + void sendLabelBind(int tail); + + bool canAcceptMessages(int actionId) override + { return ((actionId == ACTION_SELECT_ITEM) | (actionId == ACTION_MOUSE_LEFT_CLICK)); } + + // This is called from AddonWindow.cpp but shouldn't be available + // to the scripting languages. + ControlList() = default; + + std::vector<AddonClass::Ref<ListItem> > vecItems; + std::string strFont; + AddonClass::Ref<ControlSpin> pControlSpin; + + UTILS::COLOR::Color textColor; + UTILS::COLOR::Color selectedColor; + std::string strTextureButton; + std::string strTextureButtonFocus; + + int imageHeight = 0; + int imageWidth = 0; + int itemHeight = 0; + int space = 0; + + int itemTextOffsetX = 0; + int itemTextOffsetY = 0; + uint32_t alignmentY; + + CGUIControl* Create() override; +#endif + }; + /// @} + + // ControlFadeLabel class + /// + /// \defgroup python_xbmcgui_control_fadelabel Subclass - ControlFadeLabel + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to show multiple pieces of text in the same position, by + /// fading from one to the other.** + /// + /// \python_class{ ControlFadeLabel(x, y, width, height[, font, textColor, alignment]) } + /// + /// The fade label control is used for displaying multiple pieces of text + /// in the same space in Kodi. You can choose the font, size, colour, + /// location and contents of the text to be displayed. The first piece of + /// information to display fades in over 50 frames, then scrolls off to + /// the left. Once it is finished scrolling off screen, the second piece + /// of information fades in and the process repeats. A fade label control + /// is not supported in a list container. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param font [opt] string - font used for label text. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of fadelabel's labels. (e.g. '0xFFFFFFFF') + /// @param alignment [opt] integer - alignment of label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window + /// with addControl(). + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.fadelabel = xbmcgui.ControlFadeLabel(100, 250, 200, 50, textColor='0xFFFFFFFF') + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlFadeLabel : public Control + { + public: + ControlFadeLabel(long x, long y, long width, long height, + const char* font = NULL, + const char* textColor = NULL, + long _alignment = XBFONT_LEFT); + + // addLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcgui_control_fadelabel + /// @brief \python_func{ addLabel(label) } + /// Add a label to this control for scrolling. + /// + /// @param label string or unicode - text string to add. + /// + /// @note To remove added text use `reset()` for them. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.fadelabel.addLabel('This is a line of text that can scroll.') + /// ... + /// ~~~~~~~~~~~~~ + /// + addLabel(...); +#else + virtual void addLabel(const String& label); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_fadelabel + /// @brief \python_func{ setScrolling(scroll) } + /// Set scrolling. If set to false, the labels won't scroll. + /// Defaults to true. + /// + /// @param scroll boolean - True = enabled / False = disabled + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.fadelabel.setScrolling(False) + /// ... + /// ~~~~~~~~~~~~~ + /// + setScrolling(...); +#else + virtual void setScrolling(bool scroll); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_label + /// @brief \python_func{ reset() } + /// Clear this fade label. + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.fadelabel.reset() + /// ... + /// ~~~~~~~~~~~~~ + /// + reset(); +#else + virtual void reset(); +#endif + +#ifndef SWIG + std::string strFont; + UTILS::COLOR::Color textColor; + std::vector<std::string> vecLabels; + uint32_t align; + + CGUIControl* Create() override; + + ControlFadeLabel() = default; +#endif + }; + /// @} + + // ControlTextBox class + /// + /// \defgroup python_xbmcgui_control_textbox Subclass - ControlTextBox + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to show a multi-page piece of text.** + /// + /// \python_class{ ControlTextBox(x, y, width, height[, font, textColor]) } + /// + /// The text box is used for showing a large multipage piece of text in Kodi. + /// You can choose the position, size, and look of the text. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param font [opt] string - font used for text. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of textbox's text. (e.g. '0xFFFFFFFF') + /// + /// @note You can use the above as keywords for arguments and skip certain optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword.\n + /// After you create the control, you need to add it to the window with addControl(). + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # ControlTextBox(x, y, width, height[, font, textColor]) + /// self.textbox = xbmcgui.ControlTextBox(100, 250, 300, 300, textColor='0xFFFFFFFF') + /// ... + /// ~~~~~~~~~~~~~ + /// + /// As stated above, the GUI control is only created once added to a window. The example + /// below shows how a ControlTextBox can be created, added to the current window and + /// have some of its properties changed. + /// + /// **Extended example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// textbox = xbmcgui.ControlTextBox(100, 250, 300, 300, textColor='0xFFFFFFFF') + /// window = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + /// window.addControl(textbox) + /// textbox.setText("My Text Box") + /// textbox.scroll() + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlTextBox : public Control + { + public: + ControlTextBox(long x, long y, long width, long height, + const char* font = NULL, + const char* textColor = NULL); + + // SetText() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_textbox + /// @brief \python_func{ setText(text) } + /// Sets the text for this textbox. + /// \anchor python_xbmcgui_control_textbox_settext + /// + /// @param text string - text string. + /// + ///----------------------------------------------------------------------- + /// + /// @python_v19 setText can now be used before adding the control to the window (the defined + /// value is taken into consideration when the control is created) + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setText(text) + /// self.textbox.setText('This is a line of text that can wrap.') + /// ... + /// ~~~~~~~~~~~~~ + /// + setText(...); +#else + virtual void setText(const String& text); +#endif + + // getText() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_textbox + /// @brief \python_func{ getText() } + /// Returns the text value for this textbox. + /// + /// @return To get text from box + /// + ///----------------------------------------------------------------------- + /// + /// @python_v19 getText() can now be used before adding the control to the window + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getText() + /// text = self.text.getText() + /// ... + /// ~~~~~~~~~~~~~ + /// + getText(); +#else + virtual String getText(); +#endif + + // reset() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_textbox + /// @brief \python_func{ reset() } + /// Clear's this textbox. + /// + ///----------------------------------------------------------------------- + /// @python_v19 reset() will reset any text defined for this control even before you add the control to the window + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # reset() + /// self.textbox.reset() + /// ... + /// ~~~~~~~~~~~~~ + /// + reset(); +#else + virtual void reset(); +#endif + + // scroll() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_textbox + /// @brief \python_func{ scroll(id) } + /// Scrolls to the given position. + /// + /// @param id integer - position to scroll to. + /// + /// @note scroll() only works after the control is added to a window. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # scroll(position) + /// self.textbox.scroll(10) + /// ... + /// ~~~~~~~~~~~~~ + /// + scroll(...); +#else + virtual void scroll(long id); +#endif + + // autoScroll() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_textbox + /// @brief \python_func{ autoScroll(delay, time, repeat) } + /// Set autoscrolling times. + /// + /// @param delay integer - Scroll delay (in ms) + /// @param time integer - Scroll time (in ms) + /// @param repeat integer - Repeat time + /// + /// @note autoScroll only works after you add the control to a window. + /// + ///----------------------------------------------------------------------- + /// + /// @python_v15 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.textbox.autoScroll(1, 2, 1) + /// ... + /// ~~~~~~~~~~~~~ + /// + autoScroll(...); +#else + virtual void autoScroll(int delay, int time, int repeat); +#endif + +#ifndef SWIG + std::string strFont; + UTILS::COLOR::Color textColor; + + CGUIControl* Create() override; + + ControlTextBox() = default; +#endif + }; + /// @} + + // ControlImage class + /// + /// \defgroup python_xbmcgui_control_image Subclass - ControlImage + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to show an image.** + /// + /// \python_class{ ControlImage(x, y, width, height, filename[, aspectRatio, colorDiffuse]) } + /// + /// The image control is used for displaying images in Kodi. You can choose + /// the position, size, transparency and contents of the image to be + /// displayed. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param filename string - image filename. + /// @param aspectRatio [opt] integer - (values 0 = stretch + /// (default), 1 = scale up (crops), + /// 2 = scale down (black bar) + /// @param colorDiffuse hexString - (example, '0xC0FF0000' (red tint)) + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window with + /// addControl(). + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # ControlImage(x, y, width, height, filename[, aspectRatio, colorDiffuse]) + /// self.image = xbmcgui.ControlImage(100, 250, 125, 75, aspectRatio=2) + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlImage : public Control + { + public: + ControlImage(long x, long y, long width, long height, + const char* filename, long aspectRatio = 0, + const char* colorDiffuse = NULL); + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_image + /// @brief \python_func{ setImage(filename[, useCache]) } + /// Changes the image. + /// + /// @param filename string - image filename. + /// @param useCache [opt] bool - True=use cache (default) / + /// False=don't use cache. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 Added new option **useCache**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setImage(filename[, useCache]) + /// self.image.setImage('special://home/scripts/test.png') + /// self.image.setImage('special://home/scripts/test.png', False) + /// ... + /// ~~~~~~~~~~~~~ + /// + setImage(...); +#else + virtual void setImage(const char* imageFilename, const bool useCache = true); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_image + /// @brief \python_func{ setColorDiffuse(colorDiffuse) } + /// Changes the images color. + /// + /// @param colorDiffuse hexString - (example, '0xC0FF0000' + /// (red tint)) + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setColorDiffuse(colorDiffuse) + /// self.image.setColorDiffuse('0xC0FF0000') + /// ... + /// ~~~~~~~~~~~~~ + /// + setColorDiffuse(...); +#else + virtual void setColorDiffuse(const char* hexString); +#endif + +#ifndef SWIG + ControlImage() = default; + + std::string strFileName; + int aspectRatio = 0; + UTILS::COLOR::Color colorDiffuse; + + CGUIControl* Create() override; +#endif + }; + /// @} + + // ControlImage class + /// + /// \defgroup python_xbmcgui_control_progress Subclass - ControlProgress + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to show the progress of a particular operation.** + /// + /// \python_class{ ControlProgress(x, y, width, height, filename[, texturebg, textureleft, texturemid, textureright, textureoverlay]) } + /// + /// The progress control is used to show the progress of an item that may + /// take a long time, or to show how far through a movie you are. You can + /// choose the position, size, and look of the progress control. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param filename string - image filename. + /// @param texturebg [opt] string - specifies the image file + /// whichshould be displayed in the + /// background of the progress control. + /// @param textureleft [opt] string - specifies the image file + /// whichshould be displayed for the left + /// side of the progress bar. This is + /// rendered on the left side of the bar. + /// @param texturemid [opt] string - specifies the image file + /// which should be displayed for the middl + /// portion of the progress bar. This is + /// the `fill` texture used to fill up the + /// bar. It's positioned on the right of + /// the `<lefttexture>` texture, and fills + /// the gap between the `<lefttexture>` and + /// `<righttexture>` textures, depending on + /// how far progressed the item is. + /// @param textureright [opt] string - specifies the image file + /// which should be displayed for the right + /// side of the progress bar. This is + /// rendered on the right side of the bar. + /// @param textureoverlay [opt] string - specifies the image file + /// which should be displayed over the top of + /// all other images in the progress bar. It + /// is centered vertically and horizontally + /// within the space taken up by the + /// background image. + /// + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window + /// with addControl(). + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # ControlProgress(x, y, width, height, filename[, texturebg, textureleft, texturemid, textureright, textureoverlay]) + /// self.image = xbmcgui.ControlProgress(100, 250, 250, 30, 'special://home/scripts/test.png') + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlProgress : public Control + { + public: + ControlProgress(long x, long y, long width, long height, + const char* texturebg = NULL, + const char* textureleft = NULL, + const char* texturemid = NULL, + const char* textureright = NULL, + const char* textureoverlay = NULL); + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_progress + /// @brief \python_func{ setPercent(percent) } + /// Sets the percentage of the progressbar to show. + /// + /// @param percent float - percentage of the bar to show. + /// + /// + /// @note valid range for percent is 0-100 + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setPercent(percent) + /// self.progress.setPercent(60) + /// ... + /// ~~~~~~~~~~~~~ + /// + setPercent(...); +#else + virtual void setPercent(float pct); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_progress + /// @brief \python_func{ getPercent() } + /// Returns a float of the percent of the progress. + /// + /// @return Percent position + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getPercent() + /// print(self.progress.getPercent()) + /// ... + /// ~~~~~~~~~~~~~ + /// + getPercent(); +#else + virtual float getPercent(); +#endif + +#ifndef SWIG + std::string strTextureLeft; + std::string strTextureMid; + std::string strTextureRight; + std::string strTextureBg; + std::string strTextureOverlay; + int aspectRatio = 0; + UTILS::COLOR::Color colorDiffuse; + + CGUIControl* Create() override; + ControlProgress() = default; +#endif + }; + /// @} + + // ControlButton class + /// + /// \defgroup python_xbmcgui_control_button Subclass - ControlButton + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief <b>A standard push button control.</b> + /// + /// \python_class{ ControlButton(x, y, width, height, label[, focusTexture, noFocusTexture, textOffsetX, textOffsetY, + /// alignment, font, textColor, disabledColor, angle, shadowColor, focusedColor]) } + /// + /// The button control is used for creating push buttons in Kodi. You can + /// choose the position, size, and look of the button, as well as choosing + /// what action(s) should be performed when pushed. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param label string or unicode - text string. + /// @param focusTexture [opt] string - filename for focus + /// texture. + /// @param noFocusTexture [opt] string - filename for no focus + /// texture. + /// @param textOffsetX [opt] integer - x offset of label. + /// @param textOffsetY [opt] integer - y offset of label. + /// @param alignment [opt] integer - alignment of label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// @param font [opt] string - font used for label text. + /// (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled + /// button's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// button's label. (e.g. '0xFFFF3300') + /// @param angle [opt] integer - angle of control. + /// (+ rotates CCW, - rotates CW) + /// @param shadowColor [opt] hexstring - color of button's + /// label's shadow. (e.g. '0xFF000000') + /// @param focusedColor [opt] hexstring - color of focused + /// button's label. (e.g. '0xFF00FFFF') + /// + /// @note You can use the above as keywords for arguments and skip + /// certain optional arguments.\n + /// Once you use a keyword, all following arguments require + /// the keyword.\n + /// After you create the control, you need to add it to the + /// window with addControl(). + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # ControlButton(x, y, width, height, label[, focusTexture, noFocusTexture, textOffsetX, textOffsetY, + /// # alignment, font, textColor, disabledColor, angle, shadowColor, focusedColor]) + /// self.button = xbmcgui.ControlButton(100, 250, 200, 50, 'Status', font='font14') + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlButton : public Control + { + public: + ControlButton(long x, long y, long width, long height, const String& label, + const char* focusTexture = NULL, const char* noFocusTexture = NULL, + long textOffsetX = CONTROL_TEXT_OFFSET_X, + long textOffsetY = CONTROL_TEXT_OFFSET_Y, + long alignment = (XBFONT_LEFT | XBFONT_CENTER_Y), + const char* font = NULL, const char* textColor = NULL, + const char* disabledColor = NULL, long angle = 0, + const char* shadowColor = NULL, const char* focusedColor = NULL); + + // setLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_button + /// @brief \python_func{ setLabel([label, font, textColor, disabledColor, shadowColor, focusedColor, label2]) } + /// Sets this buttons text attributes. + /// + /// @param label [opt] string or unicode - text string. + /// @param font [opt] string - font used for label text. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled button's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled button's label. (e.g. '0xFFFF3300') + /// @param shadowColor [opt] hexstring - color of button's label's shadow. (e.g. '0xFF000000') + /// @param focusedColor [opt] hexstring - color of focused button's label. (e.g. '0xFFFFFF00') + /// @param label2 [opt] string or unicode - text string. + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setLabel([label, font, textColor, disabledColor, shadowColor, focusedColor]) + /// self.button.setLabel('Status', 'font14', '0xFFFFFFFF', '0xFFFF3300', '0xFF000000') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel(...); +#else + virtual void setLabel(const String& label = emptyString, + const char* font = NULL, + const char* textColor = NULL, + const char* disabledColor = NULL, + const char* shadowColor = NULL, + const char* focusedColor = NULL, + const String& label2 = emptyString); +#endif + + // setDisabledColor() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_button + /// @brief \python_func{ setDisabledColor(disabledColor) } + /// Sets this buttons disabled color. + /// + /// @param disabledColor hexstring - color of disabled button's label. (e.g. '0xFFFF3300') + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setDisabledColor(disabledColor) + /// self.button.setDisabledColor('0xFFFF3300') + /// ... + /// ~~~~~~~~~~~~~ + /// + setDisabledColor(...); +#else + virtual void setDisabledColor(const char* color); +#endif + + // getLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_button + /// @brief \python_func{ getLabel() } + /// Returns the buttons label as a unicode string. + /// + /// @return Unicode string + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getLabel() + /// label = self.button.getLabel() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel(); +#else + virtual String getLabel(); +#endif + + // getLabel2() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_button + /// @brief \python_func{ getLabel2() } + /// Returns the buttons label2 as a string. + /// + /// @return string of label 2 + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getLabel2() + /// label = self.button.getLabel2() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel2(); +#else + virtual String getLabel2(); +#endif + +#ifndef SWIG + bool canAcceptMessages(int actionId) override { return true; } + + int textOffsetX = 0; + int textOffsetY = 0; + UTILS::COLOR::Color align; + std::string strFont; + UTILS::COLOR::Color textColor; + UTILS::COLOR::Color disabledColor; + int iAngle = 0; + int shadowColor = 0; + int focusedColor = 0; + std::string strText; + std::string strText2; + std::string strTextureFocus; + std::string strTextureNoFocus; + + CGUIControl* Create() override; + + ControlButton() = default; +#endif + }; + /// @} + + // ControlGroup class + /// + /// \defgroup python_xbmcgui_control_group Subclass - ControlGroup + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used to group controls together..** + /// + /// \python_class{ ControlGroup(x, y, width, height) } + /// + /// The group control is one of the most important controls. It allows you + /// to group controls together, applying attributes to all of them at once. + /// It also remembers the last navigated button in the group, so you can set + /// the <b>`<onup>`</b> of a control to a group of controls to have it always + /// go back to the one you were at before. It also allows you to position + /// controls more accurately relative to each other, as any controls within + /// a group take their coordinates from the group's top left corner (or from + /// elsewhere if you use the <b>"r"</b> attribute). You can have as many + /// groups as you like within the skin, and groups within groups are handled + /// with no issues. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.group = xbmcgui.ControlGroup(100, 250, 125, 75) + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlGroup : public Control + { + public: + ControlGroup(long x, long y, long width, long height); + +#ifndef SWIG + CGUIControl* Create() override; + + inline ControlGroup() = default; +#endif + }; + /// @} + + // ControlRadioButton class + /// + /// \defgroup python_xbmcgui_control_radiobutton Subclass - ControlRadioButton + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **A radio button control (as used for on/off settings).** + /// + /// \python_class{ ControlRadioButton(x, y, width, height, label[, focusOnTexture, noFocusOnTexture, + /// focusOffTexture, noFocusOffTexture, focusTexture, noFocusTexture, + /// textOffsetX, textOffsetY, alignment, font, textColor, disabledColor]) } + /// + /// The radio button control is used for creating push button on/off + /// settings in Kodi. You can choose the position, size, and look of the + /// button, as well as the focused and unfocused radio textures. Used + /// for settings controls. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control. + /// @param y integer - y coordinate of control. + /// @param width integer - width of control. + /// @param height integer - height of control. + /// @param label string or unicode - text string. + /// @param focusOnTexture [opt] string - filename for radio ON + /// focused texture. + /// @param noFocusOnTexture [opt] string - filename for radio ON not + /// focused texture. + /// @param focusOfTexture [opt] string - filename for radio OFF + /// focused texture. + /// @param noFocusOffTexture [opt] string - filename for radio OFF + /// not focused texture. + /// @param focusTexture [opt] string - filename for focused button + /// texture. + /// @param noFocusTexture [opt] string - filename for not focused button + /// texture. + /// @param textOffsetX [opt] integer - horizontal text offset + /// @param textOffsetY [opt] integer - vertical text offset + /// @param alignment [opt] integer - alignment of label + /// - \ref kodi_gui_font_alignment "Flags for alignment" used as bits to have several together: + /// | Definition name | Bitflag | Description | + /// |-------------------|:----------:|:------------------------------------| + /// | XBFONT_LEFT | 0x00000000 | Align X left + /// | XBFONT_RIGHT | 0x00000001 | Align X right + /// | XBFONT_CENTER_X | 0x00000002 | Align X center + /// | XBFONT_CENTER_Y | 0x00000004 | Align Y center + /// | XBFONT_TRUNCATED | 0x00000008 | Truncated text + /// | XBFONT_JUSTIFIED | 0x00000010 | Justify text + /// @param font [opt] string - font used for label text. + /// (e.g. 'font13') + /// @param textColor [opt] hexstring - color of label when control + /// is enabled. + /// radiobutton's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of label when control + /// is disabled. (e.g. '0xFFFF3300') + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window with + /// addControl(). + /// + /// + ///-------------------------------------------------------------------------- + /// @python_v13 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.radiobutton = xbmcgui.ControlRadioButton(100, 250, 200, 50, 'Enable', font='font14') + /// ... + /// ~~~~~~~~~~~~~ + /// + class ControlRadioButton : public Control + { + public: + ControlRadioButton(long x, long y, long width, long height, const String& label, + const char* focusOnTexture = NULL, const char* noFocusOnTexture = NULL, + const char* focusOffTexture = NULL, const char* noFocusOffTexture = NULL, + const char* focusTexture = NULL, const char* noFocusTexture = NULL, + long textOffsetX = CONTROL_TEXT_OFFSET_X, + long textOffsetY = CONTROL_TEXT_OFFSET_Y, + long _alignment = (XBFONT_LEFT | XBFONT_CENTER_Y), + const char* font = NULL, const char* textColor = NULL, + const char* disabledColor = NULL, long angle = 0, + const char* shadowColor = NULL, const char* focusedColor = NULL, + const char* disabledOnTexture = NULL, const char* disabledOffTexture = NULL); + + // setSelected() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_radiobutton + /// @brief \python_func{ setSelected(selected) } + /// **Sets the radio buttons's selected status.** + /// + /// @param selected bool - True=selected (on) / False=not + /// selected (off) + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.radiobutton.setSelected(True) + /// ... + /// ~~~~~~~~~~~~~ + /// + setSelected(...); +#else + virtual void setSelected(bool selected); +#endif + + // isSelected() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_radiobutton + /// @brief \python_func{ isSelected() } + /// Returns the radio buttons's selected status. + /// + /// @return True if selected on + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// is = self.radiobutton.isSelected() + /// ... + /// ~~~~~~~~~~~~~ + /// + isSelected(); +#else + virtual bool isSelected(); +#endif + + // setLabel() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_radiobutton + /// @brief \python_func{ setLabel(label[, font, textColor, disabledColor, shadowColor, focusedColor]) } + /// Sets the radio buttons text attributes. + /// + /// @param label string or unicode - text string. + /// @param font [opt] string - font used for label + /// text. (e.g. 'font13') + /// @param textColor [opt] hexstring - color of enabled radio + /// button's label. (e.g. '0xFFFFFFFF') + /// @param disabledColor [opt] hexstring - color of disabled + /// radio button's label. (e.g. '0xFFFF3300') + /// @param shadowColor [opt] hexstring - color of radio + /// button's label's shadow. + /// (e.g. '0xFF000000') + /// @param focusedColor [opt] hexstring - color of focused radio + /// button's label. (e.g. '0xFFFFFF00') + /// + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setLabel(label[, font, textColor, disabledColor, shadowColor, focusedColor]) + /// self.radiobutton.setLabel('Status', 'font14', '0xFFFFFFFF', '0xFFFF3300', '0xFF000000') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel(...); +#else + virtual void setLabel(const String& label = emptyString, + const char* font = NULL, + const char* textColor = NULL, + const char* disabledColor = NULL, + const char* shadowColor = NULL, + const char* focusedColor = NULL, + const String& label2 = emptyString); +#endif + + // setRadioDimension() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_radiobutton + /// @brief \python_func{ setRadioDimension(x, y, width, height) } + /// Sets the radio buttons's radio texture's position and size. + /// + /// @param x integer - x coordinate of radio texture. + /// @param y integer - y coordinate of radio texture. + /// @param width integer - width of radio texture. + /// @param height integer - height of radio texture. + /// + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.radiobutton.setRadioDimension(x=100, y=5, width=20, height=20) + /// ... + /// ~~~~~~~~~~~~~ + /// + setRadioDimension(...); +#else + virtual void setRadioDimension(long x, long y, long width, long height); +#endif + +#ifndef SWIG + bool canAcceptMessages(int actionId) override { return true; } + + std::string strFont; + std::string strText; + std::string strTextureFocus; + std::string strTextureNoFocus; + std::string strTextureRadioOnFocus; + std::string strTextureRadioOnNoFocus; + std::string strTextureRadioOffFocus; + std::string strTextureRadioOffNoFocus; + std::string strTextureRadioOnDisabled; + std::string strTextureRadioOffDisabled; + UTILS::COLOR::Color textColor; + UTILS::COLOR::Color disabledColor; + int textOffsetX = 0; + int textOffsetY = 0; + uint32_t align; + int iAngle = 0; + UTILS::COLOR::Color shadowColor; + UTILS::COLOR::Color focusedColor; + + CGUIControl* Create() override; + + ControlRadioButton() = default; +#endif + }; + /// @} + + /// \defgroup python_xbmcgui_control_slider Subclass - ControlSlider + /// \ingroup python_xbmcgui_control + /// @{ + /// @brief **Used for a volume slider.** + /// + /// \python_class{ ControlSlider(x, y, width, height[, textureback, texture, texturefocus, orientation]) } + /// + /// The slider control is used for things where a sliding bar best represents + /// the operation at hand (such as a volume control or seek control). You can + /// choose the position, size, and look of the slider control. + /// + /// @note This class include also all calls from \ref python_xbmcgui_control "Control" + /// + /// @param x integer - x coordinate of control + /// @param y integer - y coordinate of control + /// @param width integer - width of control + /// @param height integer - height of control + /// @param textureback [opt] string - image filename + /// @param texture [opt] string - image filename + /// @param texturefocus [opt] string - image filename + /// @param orientation [opt] integer - orientation of slider (xbmcgui.HORIZONTAL / xbmcgui.VERTICAL (default)) + /// + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword.\n + /// After you create the control, you need to add it to the window + /// with addControl(). + /// + /// + ///-------------------------------------------------------------------------- + /// @python_v17 **orientation** option added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.slider = xbmcgui.ControlSlider(100, 250, 350, 40) + /// ... + /// ~~~~~~~~~~~~~ + class ControlSlider : public Control + { + public: + ControlSlider(long x, long y, long width, long height, + const char* textureback = NULL, + const char* texture = NULL, + const char* texturefocus = NULL, int orientation = 1); + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ getPercent() } + /// Returns a float of the percent of the slider. + /// + /// @return float - Percent of slider + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// print(self.slider.getPercent()) + /// ... + /// ~~~~~~~~~~~~~ + /// + getPercent(); +#else + virtual float getPercent(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ setPercent(pct) } + /// Sets the percent of the slider. + /// + /// @param pct float - Percent value of slider + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.slider.setPercent(50) + /// ... + /// ~~~~~~~~~~~~~ + /// + setPercent(...); +#else + virtual void setPercent(float pct); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ getInt() } + /// Returns the value of the slider. + /// + /// @return int - value of slider + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// print(self.slider.getInt()) + /// ... + /// ~~~~~~~~~~~~~ + /// + getInt(); +#else + virtual int getInt(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ setInt(value, min, delta, max) } + /// Sets the range, value and step size of the slider. + /// + /// @param value int - value of slider + /// @param min int - min of slider + /// @param delta int - step size of slider + /// @param max int - max of slider + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.slider.setInt(450, 200, 10, 900) + /// ... + /// ~~~~~~~~~~~~~ + /// + setInt(...); +#else + virtual void setInt(int value, int min, int delta, int max); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ getFloat() } + /// Returns the value of the slider. + /// + /// @return float - value of slider + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// print(self.slider.getFloat()) + /// ... + /// ~~~~~~~~~~~~~ + /// + getFloat(); +#else + virtual float getFloat(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_control_slider + /// @brief \python_func{ setFloat(value, min, delta, max) } + /// Sets the range, value and step size of the slider. + /// + /// @param value float - value of slider + /// @param min float - min of slider + /// @param delta float - step size of slider + /// @param max float - max of slider + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// self.slider.setFloat(15.0, 10.0, 1.0, 20.0) + /// ... + /// ~~~~~~~~~~~~~ + /// + setFloat(...); +#else + virtual void setFloat(float value, float min, float delta, float max); +#endif + +#ifndef SWIG + std::string strTextureBack; + std::string strTexture; + std::string strTextureFoc; + int iOrientation; + + CGUIControl* Create() override; + + inline ControlSlider() = default; +#endif + }; + /// @} + } +} diff --git a/xbmc/interfaces/legacy/Dialog.cpp b/xbmc/interfaces/legacy/Dialog.cpp new file mode 100644 index 0000000..6dd00f1 --- /dev/null +++ b/xbmc/interfaces/legacy/Dialog.cpp @@ -0,0 +1,622 @@ + /* + * 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 "Dialog.h" + +#include "LanguageHook.h" +#include "ListItem.h" +#include "ModuleXbmcgui.h" +#include "ServiceBroker.h" +#include "WindowException.h" +#include "dialogs/GUIDialogColorPicker.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogFileBrowser.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogNumeric.h" +#include "dialogs/GUIDialogSelect.h" +#include "dialogs/GUIDialogTextViewer.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/helpers/DialogOKHelper.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "settings/MediaSourceSettings.h" +#include "storage/MediaManager.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "video/dialogs/GUIDialogVideoInfo.h" + + using namespace KODI::MESSAGING; + +#define ACTIVE_WINDOW CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() + +namespace XBMCAddon +{ + namespace xbmcgui + { + Dialog::~Dialog() = default; + + bool Dialog::yesno(const String& heading, + const String& message, + const String& nolabel, + const String& yeslabel, + int autoclose, + int defaultbutton) + { + return yesNoCustomInternal(heading, message, nolabel, yeslabel, emptyString, autoclose, + defaultbutton) == 1; + } + + int Dialog::yesnocustom(const String& heading, + const String& message, + const String& customlabel, + const String& nolabel, + const String& yeslabel, + int autoclose, + int defaultbutton) + { + return yesNoCustomInternal(heading, message, nolabel, yeslabel, customlabel, autoclose, + defaultbutton); + } + + int Dialog::yesNoCustomInternal(const String& heading, + const String& message, + const String& nolabel, + const String& yeslabel, + const String& customlabel, + int autoclose, + int defaultbutton) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogYesNo* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogYesNo>( + WINDOW_DIALOG_YES_NO); + if (pDialog == nullptr) + throw WindowException("Error: Window is null"); + + return pDialog->ShowAndGetInput(CVariant{heading}, CVariant{message}, CVariant{nolabel}, + CVariant{yeslabel}, CVariant{customlabel}, autoclose, + defaultbutton); + } + + bool Dialog::info(const ListItem* item) + { + DelayedCallGuard dcguard(languageHook); + const AddonClass::Ref<xbmcgui::ListItem> listitem(item); + if (listitem->item->HasVideoInfoTag()) + { + CGUIDialogVideoInfo::ShowFor(*listitem->item); + return true; + } + else if (listitem->item->HasMusicInfoTag()) + { + CGUIDialogMusicInfo::ShowFor(listitem->item.get()); + return true; + } + return false; + } + + int Dialog::contextmenu(const std::vector<String>& list) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogContextMenu* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogContextMenu>(WINDOW_DIALOG_CONTEXT_MENU); + if (pDialog == NULL) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + + CContextButtons choices; + for(unsigned int i = 0; i < list.size(); i++) + { + choices.Add(i, list[i]); + } + return pDialog->Show(choices); + } + + + int Dialog::select(const String& heading, const std::vector<Alternative<String, const ListItem* > > & list, int autoclose, int preselect, bool useDetails) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogSelect* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + if (pDialog == NULL) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + + pDialog->Reset(); + if (!heading.empty()) + pDialog->SetHeading(CVariant{heading}); + for (const auto& item : list) + { + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + CFileItemPtr& fileItem = ritem->item; + pDialog->Add(*fileItem); + } + if (preselect > -1) + pDialog->SetSelected(preselect); + if (autoclose > 0) + pDialog->SetAutoClose(autoclose); + pDialog->SetUseDetails(useDetails); + pDialog->Open(); + + return pDialog->GetSelectedItem(); + } + + + std::unique_ptr<std::vector<int>> Dialog::multiselect(const String& heading, + const std::vector<Alternative<String, const ListItem* > > & options, int autoclose, const std::vector<int>& preselect, bool useDetails) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogSelect>(WINDOW_DIALOG_SELECT); + if (pDialog == nullptr) + throw WindowException("Error: Window is NULL"); + + pDialog->Reset(); + pDialog->SetMultiSelection(true); + pDialog->SetHeading(CVariant{heading}); + + for (const auto& item : options) + { + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + CFileItemPtr& fileItem = ritem->item; + pDialog->Add(*fileItem); + } + if (autoclose > 0) + pDialog->SetAutoClose(autoclose); + pDialog->SetUseDetails(useDetails); + pDialog->SetSelected(preselect); + pDialog->Open(); + + if (pDialog->IsConfirmed()) + return std::unique_ptr<std::vector<int>>(new std::vector<int>(pDialog->GetSelectedItems())); + else + return std::unique_ptr<std::vector<int>>(); + } + + bool Dialog::ok(const String& heading, const String& message) + { + DelayedCallGuard dcguard(languageHook); + return HELPERS::ShowOKDialogText(CVariant{heading}, CVariant{message}); + } + + void Dialog::textviewer(const String& heading, const String& text, bool usemono) + { + DelayedCallGuard dcguard(languageHook); + + CGUIDialogTextViewer* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogTextViewer>(WINDOW_DIALOG_TEXT_VIEWER); + if (pDialog == NULL) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + if (!heading.empty()) + pDialog->SetHeading(heading); + if (!text.empty()) + pDialog->SetText(text); + pDialog->UseMonoFont(usemono); + pDialog->Open(); + } + + + Alternative<String, std::vector<String> > Dialog::browse(int type, const String& heading, + const String& s_shares, const String& maskparam, bool useThumbs, + bool useFileDirectories, const String& defaultt, + bool enableMultiple) + { + Alternative<String, std::vector<String> > ret; + if (enableMultiple) + ret.later() = browseMultiple(type,heading,s_shares,maskparam,useThumbs,useFileDirectories,defaultt); + else + ret.former() = browseSingle(type,heading,s_shares,maskparam,useThumbs,useFileDirectories,defaultt); + return ret; + } + + String Dialog::browseSingle(int type, const String& heading, const String& s_shares, + const String& maskparam, bool useThumbs, + bool useFileDirectories, + const String& defaultt ) + { + DelayedCallGuard dcguard(languageHook); + std::string value; + std::string mask = maskparam; + VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); + + VECSOURCES localShares; + if (!shares) + { + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + if (StringUtils::CompareNoCase(s_shares, "local") != 0) + CServiceBroker::GetMediaManager().GetNetworkLocations(localShares); + } + else // always append local drives + { + localShares = *shares; + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + } + + if (useFileDirectories && !maskparam.empty()) + mask += "|.rar|.zip"; + + value = defaultt; + if (type == 1) + CGUIDialogFileBrowser::ShowAndGetFile(localShares, mask, heading, value, useThumbs, useFileDirectories); + else if (type == 2) + CGUIDialogFileBrowser::ShowAndGetImage(localShares, heading, value); + else + CGUIDialogFileBrowser::ShowAndGetDirectory(localShares, heading, value, type != 0); + return value; + } + + std::vector<String> Dialog::browseMultiple(int type, const String& heading, const String& s_shares, + const String& mask, bool useThumbs, + bool useFileDirectories, const String& defaultt ) + { + DelayedCallGuard dcguard(languageHook); + VECSOURCES *shares = CMediaSourceSettings::GetInstance().GetSources(s_shares); + std::vector<String> valuelist; + String lmask = mask; + + VECSOURCES localShares; + if (!shares) + { + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + if (StringUtils::CompareNoCase(s_shares, "local") != 0) + CServiceBroker::GetMediaManager().GetNetworkLocations(localShares); + } + else // always append local drives + { + localShares = *shares; + CServiceBroker::GetMediaManager().GetLocalDrives(localShares); + } + + if (useFileDirectories && !lmask.empty()) + lmask += "|.rar|.zip"; + + if (type == 1) + CGUIDialogFileBrowser::ShowAndGetFileList(localShares, lmask, heading, valuelist, useThumbs, useFileDirectories); + else if (type == 2) + CGUIDialogFileBrowser::ShowAndGetImageList(localShares, heading, valuelist); + else + throw WindowException("Error: Cannot retrieve multiple directories using browse %s is NULL.",s_shares.c_str()); + + return valuelist; + } + + String Dialog::numeric(int inputtype, const String& heading, const String& defaultt, bool bHiddenInput) + { + DelayedCallGuard dcguard(languageHook); + std::string value; + KODI::TIME::SystemTime timedate; + KODI::TIME::GetLocalTime(&timedate); + + if (!heading.empty()) + { + if (inputtype == 1) + { + if (!defaultt.empty() && defaultt.size() == 10) + { + const std::string& sDefault = defaultt; + timedate.day = atoi(sDefault.substr(0, 2).c_str()); + timedate.month = atoi(sDefault.substr(3, 4).c_str()); + timedate.year = atoi(sDefault.substr(sDefault.size() - 4).c_str()); + } + if (CGUIDialogNumeric::ShowAndGetDate(timedate, heading)) + value = + StringUtils::Format("{:2}/{:2}/{:4}", timedate.day, timedate.month, timedate.year); + else + return emptyString; + } + else if (inputtype == 2) + { + if (!defaultt.empty() && defaultt.size() == 5) + { + const std::string& sDefault = defaultt; + timedate.hour = atoi(sDefault.substr(0, 2).c_str()); + timedate.minute = atoi(sDefault.substr(3, 2).c_str()); + } + if (CGUIDialogNumeric::ShowAndGetTime(timedate, heading)) + value = StringUtils::Format("{:2}:{:02}", timedate.hour, timedate.minute); + else + return emptyString; + } + else if (inputtype == 3) + { + value = defaultt; + if (!CGUIDialogNumeric::ShowAndGetIPAddress(value, heading)) + return emptyString; + } + else if (inputtype == 4) + { + value = defaultt; + if (!CGUIDialogNumeric::ShowAndVerifyNewPassword(value)) + return emptyString; + } + else + { + value = defaultt; + if (!CGUIDialogNumeric::ShowAndGetNumber(value, heading, 0, bHiddenInput)) + return emptyString; + } + } + return value; + } + + void Dialog::notification(const String& heading, const String& message, const String& icon, int time, bool sound) + { + DelayedCallGuard dcguard(languageHook); + + std::string strIcon = getNOTIFICATION_INFO(); + int iTime = TOAST_DISPLAY_TIME; + + if (time > 0) + iTime = time; + if (!icon.empty()) + strIcon = icon; + + if (strIcon == getNOTIFICATION_INFO()) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, heading, message, iTime, sound); + else if (strIcon == getNOTIFICATION_WARNING()) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Warning, heading, message, iTime, sound); + else if (strIcon == getNOTIFICATION_ERROR()) + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, heading, message, iTime, sound); + else + CGUIDialogKaiToast::QueueNotification(strIcon, heading, message, iTime, sound); + } + + String Dialog::input(const String& heading, const String& defaultt, int type, int option, int autoclose) + { + DelayedCallGuard dcguard(languageHook); + std::string value(defaultt); + KODI::TIME::SystemTime timedate; + KODI::TIME::GetLocalTime(&timedate); + + switch (type) + { + case INPUT_ALPHANUM: + { + bool bHiddenInput = (option & ALPHANUM_HIDE_INPUT) == ALPHANUM_HIDE_INPUT; + if (!CGUIKeyboardFactory::ShowAndGetInput(value, CVariant{heading}, true, bHiddenInput, autoclose)) + value = emptyString; + } + break; + case INPUT_NUMERIC: + { + if (!CGUIDialogNumeric::ShowAndGetNumber(value, heading, autoclose)) + value = emptyString; + } + break; + case INPUT_DATE: + { + if (!defaultt.empty() && defaultt.size() == 10) + { + const std::string& sDefault = defaultt; + timedate.day = atoi(sDefault.substr(0, 2).c_str()); + timedate.month = atoi(sDefault.substr(3, 4).c_str()); + timedate.year = atoi(sDefault.substr(sDefault.size() - 4).c_str()); + } + if (CGUIDialogNumeric::ShowAndGetDate(timedate, heading)) + value = StringUtils::Format("{:2}/{:2}/{:4}", timedate.day, timedate.month, + timedate.year); + else + value = emptyString; + } + break; + case INPUT_TIME: + { + if (!defaultt.empty() && defaultt.size() == 5) + { + const std::string& sDefault = defaultt; + timedate.hour = atoi(sDefault.substr(0, 2).c_str()); + timedate.minute = atoi(sDefault.substr(3, 2).c_str()); + } + if (CGUIDialogNumeric::ShowAndGetTime(timedate, heading)) + value = StringUtils::Format("{:2}:{:02}", timedate.hour, timedate.minute); + else + value = emptyString; + } + break; + case INPUT_IPADDRESS: + { + if (!CGUIDialogNumeric::ShowAndGetIPAddress(value, heading)) + value = emptyString; + } + break; + case INPUT_PASSWORD: + { + bool bResult = false; + + if (option & PASSWORD_VERIFY) + bResult = CGUIKeyboardFactory::ShowAndVerifyPassword(value, heading, 0, autoclose) == 0 ? true : false; + else + bResult = CGUIKeyboardFactory::ShowAndVerifyNewPassword(value, heading, true, autoclose); + + if (!bResult) + value = emptyString; + } + break; + default: + value = emptyString; + break; + } + + return value; + } + + String Dialog::colorpicker(const String& heading, + const String& selectedcolor, + const String& colorfile, + const std::vector<const ListItem*>& colorlist) + { + DelayedCallGuard dcguard(languageHook); + std::string value = emptyString; + CGUIDialogColorPicker* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogColorPicker>( + WINDOW_DIALOG_COLOR_PICKER); + if (pDialog == nullptr) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + + pDialog->Reset(); + if (!heading.empty()) + pDialog->SetHeading(CVariant{heading}); + + if (!colorlist.empty()) + { + CFileItemList items; + for (const auto& coloritem : colorlist) + { + items.Add(coloritem->item); + } + pDialog->SetItems(items); + } + else if (!colorfile.empty()) + pDialog->LoadColors(colorfile); + else + pDialog->LoadColors(); + + if (!selectedcolor.empty()) + pDialog->SetSelectedColor(selectedcolor); + + pDialog->Open(); + + if (pDialog->IsConfirmed()) + value = pDialog->GetSelectedColor(); + return value; + } + + DialogProgress::~DialogProgress() { XBMC_TRACE; deallocating(); } + + void DialogProgress::deallocating() + { + XBMC_TRACE; + + if (dlg && open) + { + DelayedCallGuard dg; + dlg->Close(); + } + } + + void DialogProgress::create(const String& heading, const String& message) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogProgress* pDialog= CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogProgress>(WINDOW_DIALOG_PROGRESS); + + if (pDialog == NULL) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + + dlg = pDialog; + open = true; + + pDialog->SetHeading(CVariant{heading}); + + if (!message.empty()) + pDialog->SetText(CVariant{message}); + + pDialog->Open(); + } + + void DialogProgress::update(int percent, const String& message) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogProgress* pDialog = dlg; + + if (pDialog == NULL) + throw WindowException("Dialog not created."); + + if (percent >= 0 && percent <= 100) + { + pDialog->SetPercentage(percent); + pDialog->ShowProgressBar(true); + } + else + { + pDialog->ShowProgressBar(false); + } + + if (!message.empty()) + pDialog->SetText(CVariant{message}); + } + + void DialogProgress::close() + { + DelayedCallGuard dcguard(languageHook); + if (dlg == NULL) + throw WindowException("Dialog not created."); + dlg->Close(); + open = false; + } + + bool DialogProgress::iscanceled() + { + if (dlg == NULL) + throw WindowException("Dialog not created."); + return dlg->IsCanceled(); + } + + DialogProgressBG::~DialogProgressBG() { XBMC_TRACE; deallocating(); } + + void DialogProgressBG::deallocating() + { + XBMC_TRACE; + + if (dlg && open) + { + DelayedCallGuard dg; + dlg->Close(); + } + } + + void DialogProgressBG::create(const String& heading, const String& message) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogExtendedProgressBar* pDialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogExtendedProgressBar>(WINDOW_DIALOG_EXT_PROGRESS); + + if (pDialog == NULL) + throw WindowException("Error: Window is NULL, this is not possible :-)"); + + CGUIDialogProgressBarHandle* pHandle = pDialog->GetHandle(heading); + + dlg = pDialog; + handle = pHandle; + open = true; + + pHandle->SetTitle(heading); + if (!message.empty()) + pHandle->SetText(message); + } + + void DialogProgressBG::update(int percent, const String& heading, const String& message) + { + DelayedCallGuard dcguard(languageHook); + CGUIDialogProgressBarHandle* pHandle = handle; + + if (pHandle == NULL) + throw WindowException("Dialog not created."); + + if (percent >= 0 && percent <= 100) + pHandle->SetPercentage((float)percent); + if (!heading.empty()) + pHandle->SetTitle(heading); + if (!message.empty()) + pHandle->SetText(message); + } + + void DialogProgressBG::close() + { + DelayedCallGuard dcguard(languageHook); + if (handle == NULL) + throw WindowException("Dialog not created."); + handle->MarkFinished(); + open = false; + } + + bool DialogProgressBG::isFinished() + { + if (handle == NULL) + throw WindowException("Dialog not created."); + return handle->IsFinished(); + } + + } +} diff --git a/xbmc/interfaces/legacy/Dialog.h b/xbmc/interfaces/legacy/Dialog.h new file mode 100644 index 0000000..9faa733 --- /dev/null +++ b/xbmc/interfaces/legacy/Dialog.h @@ -0,0 +1,965 @@ +/* + * 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 "AddonClass.h" +#include "AddonString.h" +#include "Alternative.h" +#include "ListItem.h" +#include "dialogs/GUIDialogBoxBase.h" +#include "dialogs/GUIDialogExtendedProgressBar.h" +#include "dialogs/GUIDialogProgress.h" +#include "swighelper.h" + +#include <string> +#include <vector> + +namespace XBMCAddon +{ +namespace xbmcgui +{ +constexpr int INPUT_ALPHANUM{0}; +constexpr int INPUT_NUMERIC{1}; +constexpr int INPUT_DATE{2}; +constexpr int INPUT_TIME{3}; +constexpr int INPUT_IPADDRESS{4}; +constexpr int INPUT_PASSWORD{5}; + +constexpr int PASSWORD_VERIFY{1}; +constexpr int ALPHANUM_HIDE_INPUT{2}; + + /// + /// \defgroup python_Dialog Dialog + /// \ingroup python_xbmcgui + /// @{ + /// @brief **Kodi's dialog class** + /// + /// The graphical control element dialog box (also called dialogue box or + /// just dialog) is a small window that communicates information to the user + /// and prompts them for a response. + /// + class Dialog : public AddonClass + { + public: + + inline Dialog() = default; + ~Dialog() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().yesno(heading, message, [nolabel, yeslabel, autoclose]) } + /// **Yes / no dialog** + /// + /// The Yes / No dialog can be used to inform the user about questions and + /// get the answer. + /// + /// @param heading string or unicode - dialog heading. + /// @param message string or unicode - message text. + /// @param nolabel [opt] label to put on the no button. + /// @param yeslabel [opt] label to put on the yes button. + /// @param autoclose [opt] integer - milliseconds to autoclose dialog. (default=do not autoclose) + /// @param defaultbutton [opt] integer - specifies the default focused button. + /// <em>(default=DLG_YESNO_NO_BTN)</em> + /// | Value: | Description: | + /// |------------------------------:|---------------------------------------------------| + /// | xbmcgui.DLG_YESNO_NO_BTN | Set the "No" button as default. + /// | xbmcgui.DLG_YESNO_YES_BTN | Set the "Yes" button as default. + /// | xbmcgui.DLG_YESNO_CUSTOM_BTN | Set the "Custom" button as default. + /// @return Returns True if 'Yes' was pressed, else False. + /// + /// + /// + ///------------------------------------------------------------------------ + /// @python_v13 Added new option **autoclose**. + /// @python_v19 Renamed option **line1** to **message**. + /// @python_v19 Removed option **line2**. + /// @python_v19 Removed option **line3**. + /// @python_v20 Added new option **defaultbutton**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.yesno('Kodi', 'Do you want to exit this script?') + /// .. + /// ~~~~~~~~~~~~~ + /// + yesno(...); +#else + bool yesno(const String& heading, + const String& message, + const String& nolabel = emptyString, + const String& yeslabel = emptyString, + int autoclose = 0, + int defaultbutton = CONTROL_NO_BUTTON); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().yesnocustom(heading, message, customlabel, [nolabel, yeslabel, autoclose]) } + /// **Yes / no / custom dialog** + /// + /// The YesNoCustom dialog can be used to inform the user about questions and + /// get the answer. The dialog provides a third button appart from yes and no. + /// Button labels are fully customizable. + /// + /// @param heading string or unicode - dialog heading. + /// @param message string or unicode - message text. + /// @param customlabel string or unicode - label to put on the custom button. + /// @param nolabel [opt] label to put on the no button. + /// @param yeslabel [opt] label to put on the yes button. + /// @param autoclose [opt] integer - milliseconds to autoclose dialog. (default=do not autoclose) + /// @param defaultbutton [opt] integer - specifies the default focused button. + /// <em>(default=DLG_YESNO_NO_BTN)</em> + /// | Value: | Description: | + /// |------------------------------:|---------------------------------------------------| + /// | xbmcgui.DLG_YESNO_NO_BTN | Set the "No" button as default. + /// | xbmcgui.DLG_YESNO_YES_BTN | Set the "Yes" button as default. + /// | xbmcgui.DLG_YESNO_CUSTOM_BTN | Set the "Custom" button as default. + /// @return Returns the integer value for the selected button (-1:cancelled, 0:no, 1:yes, 2:custom) + /// + /// + /// + ///------------------------------------------------------------------------ + /// @python_v19 New function added. + /// @python_v20 Added new option **defaultbutton**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.yesnocustom('Kodi', 'Question?', 'Maybe') + /// .. + /// ~~~~~~~~~~~~~ + /// + yesnocustom(...); +#else + int yesnocustom(const String& heading, + const String& message, + const String& customlabel, + const String& nolabel = emptyString, + const String& yeslabel = emptyString, + int autoclose = 0, + int defaultbutton = CONTROL_NO_BUTTON); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().info(listitem) } + /// **Info dialog** + /// + /// Show the corresponding info dialog for a given listitem + /// + /// @param listitem xbmcgui.ListItem - ListItem to show info for. + /// @return Returns whether the dialog opened successfully. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v17 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.info(listitem) + /// .. + /// ~~~~~~~~~~~~~ + /// + info(...); +#else + bool info(const ListItem* item); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().select(heading, list[, autoclose, preselect, useDetails]) } + /// **Select dialog** + /// + /// Show of a dialog to select of an entry as a key + /// + /// @param heading string or unicode - dialog heading. + /// @param list list of strings / xbmcgui.ListItems - list of items shown in dialog. + /// @param autoclose [opt] integer - milliseconds to autoclose dialog. (default=do not autoclose) + /// @param preselect [opt] integer - index of preselected item. (default=no preselected item) + /// @param useDetails [opt] bool - use detailed list instead of a compact list. (default=false) + /// @return Returns the position of the highlighted item as an integer. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v17 **preselect** option added. + /// @python_v17 Added new option **useDetails**. + /// @python_v17 Allow listitems for parameter **list** + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.select('Choose a playlist', ['Playlist #1', 'Playlist #2, 'Playlist #3']) + /// .. + /// ~~~~~~~~~~~~~ + /// + select(...); +#else + int select(const String& heading, const std::vector<Alternative<String, const ListItem* > > & list, int autoclose=0, int preselect=-1, bool useDetails=false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().contextmenu(list) } + /// Show a context menu. + /// + /// @param list string list - list of items. + /// @return the position of the highlighted item as an integer + /// (-1 if cancelled). + /// + /// + ///-------------------------------------------------------------------------- + /// @python_v17 New function added + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.contextmenu(['Option #1', 'Option #2', 'Option #3']) + /// .. + /// ~~~~~~~~~~~~~ + /// + contextmenu(...); +#else + int contextmenu(const std::vector<String>& list); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().multiselect(heading, options[, autoclose, preselect, useDetails]) } + /// Show a multi-select dialog. + /// + /// @param heading string or unicode - dialog heading. + /// @param options list of strings / xbmcgui.ListItems - options to choose from. + /// @param autoclose [opt] integer - milliseconds to autoclose dialog. + /// (default=do not autoclose) + /// @param preselect [opt] list of int - indexes of items to preselect + /// in list (default: do not preselect any item) + /// @param useDetails [opt] bool - use detailed list instead of a compact list. (default=false) + /// @return Returns the selected items as a list of indices, + /// or None if cancelled. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v16 New function added. + /// @python_v17 Added new option **preselect**. + /// @python_v17 Added new option **useDetails**. + /// @python_v17 Allow listitems for parameter **options** + /// + /// **Example:** + /// @code{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ret = dialog.multiselect("Choose something", ["Foo", "Bar", "Baz"], preselect=[1,2]) + /// .. + /// @endcode + /// + multiselect(...); +#else + std::unique_ptr<std::vector<int> > multiselect(const String& heading, const std::vector<Alternative<String, const ListItem* > > & options, int autoclose=0, const std::vector<int>& preselect = std::vector<int>(), bool useDetails=false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().ok(heading, message) } + /// **OK dialog** + /// + /// The functions permit the call of a dialog of information, a + /// confirmation of the user by press from OK required. + /// + /// @param heading string or unicode - dialog heading. + /// @param message string or unicode - message text. + /// @return Returns True if 'Ok' was pressed, else False. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v19 Renamed option **line1** to **message**. + /// @python_v19 Removed option **line2**. + /// @python_v19 Removed option **line3**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// ok = dialog.ok('Kodi', 'There was an error.') + /// .. + /// ~~~~~~~~~~~~~ + /// + ok(...); +#else + bool ok(const String& heading, const String& message); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().textviewer(heading, text, usemono) } + /// **TextViewer dialog** + /// + /// The text viewer dialog can be used to display descriptions, help texts + /// or other larger texts. + /// + /// @param heading string or unicode - dialog heading. + /// @param text string or unicode - text. + /// @param usemono [opt] bool - use monospace font + /// + /// + ///------------------------------------------------------------------------ + /// @python_v16 New function added. + /// @python_v18 New optional param added **usemono**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// dialog.textviewer('Plot', 'Some movie plot.') + /// .. + /// ~~~~~~~~~~~~~ + /// + textviewer(...); +#else + void textviewer(const String& heading, const String& text, bool usemono = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().browse(type, heading, shares[, mask, useThumbs, treatAsFolder, defaultt, enableMultiple]) } + /// **Browser dialog** + /// + /// The function offer the possibility to select a file by the user of + /// the add-on. + /// + /// It allows all the options that are possible in Kodi itself and offers + /// all support file types. + /// + /// @param type integer - the type of browse dialog. + /// | Param | Name | + /// |:-----:|:--------------------------------| + /// | 0 | ShowAndGetDirectory | + /// | 1 | ShowAndGetFile | + /// | 2 | ShowAndGetImage | + /// | 3 | ShowAndGetWriteableDirectory | + /// @param heading string or unicode - dialog heading. + /// @param shares string or unicode - from [sources.xml](http://kodi.wiki/view/Sources.xml) + /// | Param | Name | + /// |:--------------:|:---------------------------------------------| + /// | "programs" | list program addons + /// | "video" | list video sources + /// | "music" | list music sources + /// | "pictures" | list picture sources + /// | "files" | list file sources (added through filemanager) + /// | "games" | list game sources + /// | "local" | list local drives + /// | "" | list local drives and network shares + /// @param mask [opt] string or unicode - '|' separated file mask. (i.e. '.jpg|.png') + /// @param useThumbs [opt] boolean - if True autoswitch to Thumb view if files exist. + /// @param treatAsFolder [opt] boolean - if True playlists and archives act as folders. + /// @param defaultt [opt] string - default path or file. + /// @param enableMultiple [opt] boolean - if True multiple file selection is enabled. + /// + /// @return If enableMultiple is False (default): returns filename and/or path as a string + /// to the location of the highlighted item, if user pressed 'Ok' or a masked item + /// was selected. Returns the default value if dialog was canceled. + /// If enableMultiple is True: returns tuple of marked filenames as a string + /// if user pressed 'Ok' or a masked item was selected. Returns empty tuple if dialog was canceled.\n\n + /// If type is 0 or 3 the enableMultiple parameter is ignore + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New option added to browse network and/or local drives. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// fn = dialog.browse(3, 'Kodi', 'files', '', False, False, False, 'special://masterprofile/script_data/Kodi Lyrics') + /// .. + /// ~~~~~~~~~~~~~ + /// + browse(...); +#else + Alternative<String, std::vector<String> > browse(int type, const String& heading, const String& shares, + const String& mask = emptyString, bool useThumbs = false, + bool treatAsFolder = false, const String& defaultt = emptyString, + bool enableMultiple = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().browseSingle(type, heading, shares[, mask, useThumbs, treatAsFolder, defaultt]) } + /// **Browse single dialog** + /// + /// The function offer the possibility to select a file by the user of + /// the add-on. + /// + /// It allows all the options that are possible in Kodi itself and offers + /// all support file types. + /// + /// @param type integer - the type of browse dialog. + /// | Param | Name | + /// |:-----:|:--------------------------------| + /// | 0 | ShowAndGetDirectory + /// | 1 | ShowAndGetFile + /// | 2 | ShowAndGetImage + /// | 3 | ShowAndGetWriteableDirectory + /// @param heading string or unicode - dialog heading. + /// @param shares string or unicode - from [sources.xml](http://kodi.wiki/view/Sources.xml) + /// | Param | Name | + /// |:--------------:|:---------------------------------------------| + /// | "programs" | list program addons + /// | "video" | list video sources + /// | "music" | list music sources + /// | "pictures" | list picture sources + /// | "files" | list file sources (added through filemanager) + /// | "games" | list game sources + /// | "local" | list local drives + /// | "" | list local drives and network shares + /// @param mask [opt] string or unicode - '|' separated file mask. (i.e. '.jpg|.png') + /// @param useThumbs [opt] boolean - if True autoswitch to Thumb view if files exist (default=false). + /// @param treatAsFolder [opt] boolean - if True playlists and archives act as folders (default=false). + /// @param defaultt [opt] string - default path or file. + /// + /// @return Returns filename and/or path as a string to the location of the highlighted item, + /// if user pressed 'Ok' or a masked item was selected. + /// Returns the default value if dialog was canceled. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New option added to browse network and/or local drives. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// fn = dialog.browseSingle(3, 'Kodi', 'files', '', False, False, 'special://masterprofile/script_data/Kodi Lyrics') + /// .. + /// ~~~~~~~~~~~~~ + /// + browseSingle(...); +#else + String browseSingle(int type, const String& heading, const String& shares, + const String& mask = emptyString, bool useThumbs = false, + bool treatAsFolder = false, + const String& defaultt = emptyString ); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().browseMultiple(type, heading, shares[, mask, useThumbs, treatAsFolder, defaultt]) } + /// **Browser dialog** + /// + /// The function offer the possibility to select multiple files by the + /// user of the add-on. + /// + /// It allows all the options that are possible in Kodi itself and offers + /// all support file types. + /// + /// @param type integer - the type of browse dialog. + /// | Param | Name | + /// |:-----:|:--------------------------------| + /// | 1 | ShowAndGetFile + /// | 2 | ShowAndGetImage + /// @param heading string or unicode - dialog heading. + /// @param shares string or unicode - from [sources.xml](http://kodi.wiki/view/Sources.xml) + /// | Param | Name | + /// |:--------------:|:---------------------------------------------| + /// | "programs" | list program addons + /// | "video" | list video sources + /// | "music" | list music sources + /// | "pictures" | list picture sources + /// | "files" | list file sources (added through filemanager) + /// | "games" | list game sources + /// | "local" | list local drives + /// | "" | list local drives and network shares + /// @param mask [opt] string or unicode - '|' separated file mask. (i.e. '.jpg|.png') + /// @param useThumbs [opt] boolean - if True autoswitch to Thumb view if files exist (default=false). + /// @param treatAsFolder [opt] boolean - if True playlists and archives act as folders (default=false). + /// @param defaultt [opt] string - default path or file. + /// @return Returns tuple of marked filenames as a string," + /// if user pressed 'Ok' or a masked item was selected. Returns empty tuple if dialog was canceled. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New option added to browse network and/or local drives. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// fn = dialog.browseMultiple(2, 'Kodi', 'files', '', False, False, 'special://masterprofile/script_data/Kodi Lyrics') + /// .. + /// ~~~~~~~~~~~~~ + /// + browseMultiple(...); +#else + std::vector<String> browseMultiple(int type, const String& heading, const String& shares, + const String& mask = emptyString, bool useThumbs = false, + bool treatAsFolder = false, + const String& defaultt = emptyString ); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().numeric(type, heading[, defaultt, bHiddenInput]) } + /// **Numeric dialog** + /// + /// The function have to be permitted by the user for the representation + /// of a numeric keyboard around an input. + /// + /// @param type integer - the type of numeric dialog. + /// | Param | Name | Format | + /// |:-----:|:-------------------------|:-----------------------------| + /// | 0 | ShowAndGetNumber | (default format: #) + /// | 1 | ShowAndGetDate | (default format: DD/MM/YYYY) + /// | 2 | ShowAndGetTime | (default format: HH:MM) + /// | 3 | ShowAndGetIPAddress | (default format: #.#.#.#) + /// | 4 | ShowAndVerifyNewPassword | (default format: *) + /// @param heading string or unicode - dialog heading (will be ignored for type 4). + /// @param defaultt [opt] string - default value. + /// @param bHiddenInput [opt] bool - mask input (available for type 0). + /// @return Returns the entered data as a string. + /// Returns the default value if dialog was canceled. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v19 New option added ShowAndVerifyNewPassword. + /// @python_v19 Added new option **bHiddenInput**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// d = dialog.numeric(1, 'Enter date of birth') + /// .. + /// ~~~~~~~~~~~~~ + /// + numeric(...); +#else + String numeric(int type, const String& heading, const String& defaultt = emptyString, bool bHiddenInput = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().notification(heading, message[, icon, time, sound]) } + /// Show a Notification alert. + /// + /// @param heading string - dialog heading. + /// @param message string - dialog message. + /// @param icon [opt] string - icon to use. (default xbmcgui.NOTIFICATION_INFO) + /// @param time [opt] integer - time in milliseconds (default 5000) + /// @param sound [opt] bool - play notification sound (default True) + /// + /// Builtin Icons: + /// - xbmcgui.NOTIFICATION_INFO + /// - xbmcgui.NOTIFICATION_WARNING + /// - xbmcgui.NOTIFICATION_ERROR + /// + /// + ///------------------------------------------------------------------------ + /// @python_v13 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// dialog.notification('Movie Trailers', 'Finding Nemo download finished.', xbmcgui.NOTIFICATION_INFO, 5000) + /// .. + /// ~~~~~~~~~~~~~ + /// + notification(...); +#else + void notification(const String& heading, const String& message, const String& icon = emptyString, int time = 0, bool sound = true); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().input(heading[, defaultt, type, option, autoclose]) } + /// Show an Input dialog. + /// + /// @param heading string - dialog heading. + /// @param defaultt [opt] string - default value. (default=empty string) + /// @param type [opt] integer - the type of keyboard dialog. (default=xbmcgui.INPUT_ALPHANUM) + /// | Parameter | Format | + /// |---------------------------------:|:--------------------------------| + /// | <tt>xbmcgui.INPUT_ALPHANUM</tt> | (standard keyboard) + /// | <tt>xbmcgui.INPUT_NUMERIC</tt> | (format: #) + /// | <tt>xbmcgui.INPUT_DATE</tt> | (format: DD/MM/YYYY) + /// | <tt>xbmcgui.INPUT_TIME</tt> | (format: HH:MM) + /// | <tt>xbmcgui.INPUT_IPADDRESS</tt> | (format: #.#.#.#) + /// | <tt>xbmcgui.INPUT_PASSWORD</tt> | (return md5 hash of input, input is masked) + /// @param option [opt] integer - option for the dialog. (see Options below) + /// - Password dialog: + /// - <tt>xbmcgui.PASSWORD_VERIFY</tt> (verifies an existing (default) md5 hashed password) + /// - Alphanum dialog: + /// - <tt>xbmcgui.ALPHANUM_HIDE_INPUT</tt> (masks input) + /// @param autoclose [opt] integer - milliseconds to autoclose dialog. (default=do not autoclose) + /// + /// @return Returns the entered data as a string. + /// Returns an empty string if dialog was canceled. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v13 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.Dialog() + /// d = dialog.input('Enter secret code', type=xbmcgui.INPUT_ALPHANUM, option=xbmcgui.ALPHANUM_HIDE_INPUT) + /// .. + /// ~~~~~~~~~~~~~ + /// + input(...); +#else + String input(const String& heading, + const String& defaultt = emptyString, + int type = INPUT_ALPHANUM, + int option = 0, + int autoclose = 0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Dialog + /// \python_func{ xbmcgui.Dialog().colorpicker(heading[, colorfile, colorlist, selectedcolor]) } + /// Show a color selection dialog. + /// + /// @param heading string - dialog heading. + /// @param selectedcolor [opt] string - hex value of the preselected color. + /// @param colorfile [opt] string - xml file containing color definitions.\n + /// **XML content style:** + /// ~~~~~~xml + /// <colors> + /// <color name="white">ffffffff</color> + /// <color name="grey">7fffffff</color> + /// <color name="green">ff00ff7f</color> + /// </colors> + /// ~~~~~~ + /// @param colorlist [opt] xbmcgui.ListItems - where label defines the color name and label2 is set to the hex value. + /// + /// @return Returns the hex value of the selected color as a string. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # colorfile example + /// dialog = xbmcgui.Dialog() + /// value = dialog.colorpicker('Select color', 'ff00ff00', 'os.path.join(xbmcaddon.Addon().getAddonInfo("path"), "colors.xml")') + /// .. + /// # colorlist example + /// listitems = [] + /// l1 = xbmcgui.ListItem("red", "FFFF0000") + /// l2 = xbmcgui.ListItem("green", "FF00FF00") + /// l3 = xbmcgui.ListItem("blue", "FF0000FF") + /// listitems.append(l1) + /// listitems.append(l2) + /// listitems.append(l3) + /// dialog = xbmcgui.Dialog() + /// value = dialog.colorpicker("Select color", "FF0000FF", colorlist=listitems) + /// .. + /// ~~~~~~~~~~~~~ + /// + colorpicker(...); +#else + String colorpicker( + const String& heading, + const String& selectedcolor = emptyString, + const String& colorfile = emptyString, + const std::vector<const ListItem*>& colorlist = std::vector<const ListItem*>()); +#endif + + private: +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // used by both yesno() and yesnocustom() + int yesNoCustomInternal(const String& heading, + const String& message, + const String& nolabel, + const String& yeslabel, + const String& customlabel, + int autoclose, + int defaultbutton); +#endif + }; + //@} + + /// + /// \defgroup python_DialogProgress DialogProgress + /// \ingroup python_xbmcgui + /// @{ + /// @brief <b>Kodi's progress dialog class (Duh!)</b> + /// + /// + class DialogProgress : public AddonClass + { + CGUIDialogProgress* dlg = nullptr; + bool open = false; + + protected: + void deallocating() override; + + public: + + DialogProgress() = default; + ~DialogProgress() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgress + /// \python_func{ xbmcgui.DialogProgress().create(heading[, message]) } + /// Create and show a progress dialog. + /// + /// @param heading string or unicode - dialog heading. + /// @param message [opt] string or unicode - message text. + /// + /// @note Use update() to update lines and progressbar. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v19 Renamed option **line1** to **message**. + /// @python_v19 Removed option **line2**. + /// @python_v19 Removed option **line3**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog = xbmcgui.DialogProgress() + /// pDialog.create('Kodi', 'Initializing script...') + /// .. + /// ~~~~~~~~~~~~~ + /// + create(...); +#else + void create(const String& heading, const String& message = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgress + /// \python_func{ xbmcgui.DialogProgress().update(percent[, message]) } + /// Updates the progress dialog. + /// + /// @param percent integer - percent complete. (0:100) + /// @param message [opt] string or unicode - message text. + /// + /// + /// + ///------------------------------------------------------------------------ + /// @python_v19 Renamed option **line1** to **message**. + /// @python_v19 Removed option **line2**. + /// @python_v19 Removed option **line3**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog.update(25, 'Importing modules...') + /// .. + /// ~~~~~~~~~~~~~ + /// + update(...); +#else + void update(int percent, const String& message = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgress + /// \python_func{ xbmcgui.DialogProgress().close() } + /// Close the progress dialog. + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + close(...); +#else + void close(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgress + /// \python_func{ xbmcgui.DialogProgress().iscanceled() } + /// Checks progress is canceled. + /// + /// @return True if the user pressed cancel. + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// if (pDialog.iscanceled()): return + /// .. + /// ~~~~~~~~~~~~~ + /// + iscanceled(...); +#else + bool iscanceled(); +#endif + }; + + //@} + + /// + /// \defgroup python_DialogProgressBG DialogProgressBG + /// \ingroup python_xbmcgui + /// @{ + /// @brief <b>Kodi's background progress dialog class</b> + /// + /// + class DialogProgressBG : public AddonClass + { + CGUIDialogExtendedProgressBar* dlg = nullptr; + CGUIDialogProgressBarHandle* handle = nullptr; + bool open = false; + + protected: + void deallocating() override; + + public: + + DialogProgressBG() = default; + ~DialogProgressBG() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgressBG + /// \python_func{ xbmcgui.DialogProgressBG().create(heading[, message]) } + /// Create and show a background progress dialog. + /// + /// @param heading string or unicode - dialog heading. + /// @param message [opt] string or unicode - message text. + /// + /// @note 'heading' is used for the dialog's id. Use a unique heading. + /// Use update() to update heading, message and progressbar. + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog = xbmcgui.DialogProgressBG() + /// pDialog.create('Movie Trailers', 'Downloading Monsters Inc... .') + /// .. + /// ~~~~~~~~~~~~~ + /// + create(...); +#else + void create(const String& heading, const String& message = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgressBG + /// \python_func{ xbmcgui.DialogProgressBG().update([percent, heading, message]) } + /// Updates the background progress dialog. + /// + /// @param percent [opt] integer - percent complete. (0:100) + /// @param heading [opt] string or unicode - dialog heading. + /// @param message [opt] string or unicode - message text. + /// + /// @note To clear heading or message, you must pass a blank character. + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog.update(25, message='Downloading Finding Nemo ...') + /// .. + /// ~~~~~~~~~~~~~ + /// + update(...); +#else + void update(int percent = 0, const String& heading = emptyString, const String& message = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgressBG + /// \python_func{ xbmcgui.DialogProgressBG().close() } + /// Close the background progress dialog + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pDialog.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + close(...); +#else + void close(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_DialogProgressBG + /// \python_func{ xbmcgui.DialogProgressBG().isFinished() } + /// Checks progress is finished + /// + /// @return True if the background dialog is active. + /// + /// + ///------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// if (pDialog.isFinished()): return + /// .. + /// ~~~~~~~~~~~~~ + /// + isFinished(...); +#else + bool isFinished(); +#endif + }; + //@} +#ifndef DOXYGEN_SHOULD_SKIP_THIS + SWIG_CONSTANT2(int, DLG_YESNO_NO_BTN, CONTROL_NO_BUTTON); + SWIG_CONSTANT2(int, DLG_YESNO_YES_BTN, CONTROL_YES_BUTTON); + SWIG_CONSTANT2(int, DLG_YESNO_CUSTOM_BTN, CONTROL_CUSTOM_BUTTON); +#endif +} // namespace xbmcgui +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/Dictionary.h b/xbmc/interfaces/legacy/Dictionary.h new file mode 100644 index 0000000..65a4db8 --- /dev/null +++ b/xbmc/interfaces/legacy/Dictionary.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonString.h" + +#include <map> + +namespace XBMCAddon +{ + // This is a hack in order to handle int's as strings. The correct fix for + // this is to get rid of Alternative all together and make the codegenerator + // finally handle overloading correctly. + typedef String StringOrInt; + + /** + * This is a bit of a hack for dynamically typed languages. In some + * cases python addon api calls handle dictionaries with variable + * value types. In this case we coerce all of these types into + * strings and then convert them back in the api. Yes, this is messy + * and maybe we should use the CVariant here. But for now the + * native api handles these calls by converting the string to the + * appropriate types. + */ + template<class T> class Dictionary : public std::map<String,T> {}; + + typedef Dictionary<StringOrInt> Properties; +} diff --git a/xbmc/interfaces/legacy/DrmCryptoSession.cpp b/xbmc/interfaces/legacy/DrmCryptoSession.cpp new file mode 100644 index 0000000..26f69be --- /dev/null +++ b/xbmc/interfaces/legacy/DrmCryptoSession.cpp @@ -0,0 +1,108 @@ +/* + * 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 "DrmCryptoSession.h" + +#include "media/drm/CryptoSession.h" + +using namespace XbmcCommons; + +namespace XBMCAddon +{ + namespace xbmcdrm + { + CryptoSession::CryptoSession(const String& UUID, + const String& cipherAlgorithm, + const String& macAlgorithm) + : m_cryptoSession(DRM::CCryptoSession::GetCryptoSession(UUID, cipherAlgorithm, macAlgorithm)) + { + } + + CryptoSession::~CryptoSession() + { + delete m_cryptoSession; + } + + Buffer CryptoSession::GetKeyRequest(const Buffer &init, const String &mimeType, bool offlineKey, const std::map<String, String> ¶meters) + { + if (m_cryptoSession) + return m_cryptoSession->GetKeyRequest(init, mimeType, offlineKey, parameters); + + return Buffer(); + } + + String CryptoSession::GetPropertyString(const String &name) + { + if (m_cryptoSession) + return m_cryptoSession->GetPropertyString(name); + + return ""; + } + + String CryptoSession::ProvideKeyResponse(const Buffer &response) + { + if (m_cryptoSession) + return m_cryptoSession->ProvideKeyResponse(response); + + return ""; + } + + void CryptoSession::RemoveKeys() + { + if (m_cryptoSession) + m_cryptoSession->RemoveKeys(); + } + + void CryptoSession::RestoreKeys(const String& keySetId) + { + if (m_cryptoSession) + m_cryptoSession->RestoreKeys(keySetId); + } + + void CryptoSession::SetPropertyString(const String &name, const String &value) + { + if (m_cryptoSession) + return m_cryptoSession->SetPropertyString(name, value); + } + + /*******************Crypto section *****************/ + + Buffer CryptoSession::Decrypt(const Buffer &cipherKeyId, const Buffer &input, const Buffer &iv) + { + if (m_cryptoSession) + return m_cryptoSession->Decrypt(cipherKeyId, input, iv); + + return Buffer(); + } + + Buffer CryptoSession::Encrypt(const Buffer &cipherKeyId, const Buffer &input, const Buffer &iv) + { + if (m_cryptoSession) + return m_cryptoSession->Encrypt(cipherKeyId, input, iv); + + return Buffer(); + } + + Buffer CryptoSession::Sign(const Buffer &macKeyId, const Buffer &message) + { + if (m_cryptoSession) + return m_cryptoSession->Sign(macKeyId, message); + + return Buffer(); + } + + bool CryptoSession::Verify(const Buffer &macKeyId, const Buffer &message, const Buffer &signature) + { + if (m_cryptoSession) + return m_cryptoSession->Verify(macKeyId, message, signature); + + return false; + } + + } //namespace xbmcdrm +} //namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/DrmCryptoSession.h b/xbmc/interfaces/legacy/DrmCryptoSession.h new file mode 100644 index 0000000..816bec8 --- /dev/null +++ b/xbmc/interfaces/legacy/DrmCryptoSession.h @@ -0,0 +1,342 @@ +/* + * 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 "AddonClass.h" +#include "Exception.h" +#include "commons/Buffer.h" + +#include <map> +#include <vector> + +namespace DRM +{ + class CCryptoSession; +} + +namespace XBMCAddon +{ + + typedef std::vector<char> charVec; + + namespace xbmcdrm + { + + XBMCCOMMONS_STANDARD_EXCEPTION(DRMException); + + // + /// \defgroup python_xbmcdrm Library - xbmcdrm + ///@{ + /// @brief **Kodi's %DRM class.** + /// + /// Offers classes and functions that allow a developer to work with + /// DRM-protected contents like Widevine. + /// + /// This type of functionality is closely related to the type of %DRM + /// used and the service to be implemented. + /// + /// Using the \ref xbmcdrm_CryptoSession "CryptoSession" constructor allow you + /// to have access to a %DRM session. + /// With a %DRM session you can read and write the %DRM properties + /// \ref xbmcdrm_GetPropertyString "GetPropertyString", + /// \ref xbmcdrm_SetPropertyString "SetPropertyString" + /// and establish session keys with + /// \ref xbmcdrm_GetKeyRequest "GetKeyRequest" and + /// \ref xbmcdrm_ProvideKeyResponse "ProvideKeyResponse", + /// or resume previous session keys with + /// \ref xbmcdrm_RestoreKeys "RestoreKeys". + /// + /// When the session keys are established you can use these methods + /// to perform various operations: + /// \ref xbmcdrm_Encrypt "Encrypt" / + /// \ref xbmcdrm_Decrypt "Decrypt" for data encryption / decryption, + /// \ref xbmcdrm_Sign "Sign" / + /// \ref xbmcdrm_Verify "Verify" for make or verify data-signature. + /// Useful for example to implement encrypted communication between + /// a client and the server. + /// + /// An example where such functionality is useful is the Message + /// Security Layer (MSL) transmission protocol used in some VOD applications. + /// This protocol (or rather framework) is used to increase the level of security + /// in the exchange of messages (such as licences, manifests or other data), + /// which defines a security extension / layer on top of the HTTP protocol. + /// + ///-------------------------------------------------------------------------- + /// Constructor for %DRM crypto session + /// + /// \anchor xbmcdrm_CryptoSession + /// \python_class{ xbmcdrm.CryptoSession(UUID, cipherAlgorithm, macAlgorithm) } + /// + /// @param UUID string - 16 byte UUID of the %DRM system to use + /// @param cipherAlgorithm string - Algorithm used for encryption / decryption ciphers + /// @param macAlgorithm string - Algorithm used for sign / verify + /// + /// @throws RuntimeException If the session can not be established + /// + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New class added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// uuid_widevine = 'edef8ba9-79d6-4ace-a3c8-27dcd51d21ed' + /// crypto_session = xbmcdrm.CryptoSession(uuid_widevine, 'AES/CBC/NoPadding', 'HmacSHA256') + /// .. + /// ~~~~~~~~~~~~~ + /// + class CryptoSession : public AddonClass + { + DRM::CCryptoSession* m_cryptoSession; + public: + CryptoSession(const String& UUID, const String& cipherAlgorithm, const String& macAlgorithm); + ~CryptoSession() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ GetKeyRequest(init, mimeType, offlineKey, optionalParameters) } + /// Generate a key request + /// + /// Generate a key request, used for request/response exchange between the app + /// and a license server to obtain or release keys used to decrypt encrypted content. + /// After the app has received the key request response from the license server, + /// it should deliver to the response to the %DRM instance using + /// the method \ref xbmcdrm_ProvideKeyResponse "ProvideKeyResponse", to activate the keys. + // \anchor xbmcdrm_GetKeyRequest + /// + /// @param init byte - Initialization bytes container-specific data, + /// its meaning is interpreted based on the mime type provided + /// in the mimeType parameter. It could contain, for example, + /// the content ID, key ID or other data required in generating + /// the key request. + /// @param mimeType string - Type of media which is exchanged + /// (e.g. "application/xml", "video/mp4") + /// @param offlineKey bool - Specifies the type of the request. + /// The request may be to acquire keys for Streaming or Offline content + /// @param optionalParameters [opt] map - Will be included in the key request message + /// to allow a client application to provide additional + /// message parameters to the server + /// + /// @return byte - The opaque key request data (challenge) which is send to key server + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 the init param must be a bytearray instead of byte. + /// + GetKeyRequest(...); +#else + XbmcCommons::Buffer GetKeyRequest(const XbmcCommons::Buffer &init, const String &mimeType, bool offlineKey, const std::map<String, String> &optionalParameters); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ GetPropertyString(name) } + /// Request a system specific property value of the %DRM system. + /// + ///\anchor xbmcdrm_GetPropertyString + /// @param Name string - Name of the property to query + /// + /// @return Value of the requested property + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + GetPropertyString(...); +#else + String GetPropertyString(const String &name); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ ProvideKeyResponse(response) } + /// Provide a key response + /// + /// \anchor xbmcdrm_ProvideKeyResponse + /// When a key response is received from the license server, + /// must be sent to the %DRM instance by using provideKeyResponse. + /// See also \ref xbmcdrm_GetKeyRequest "GetKeyRequest". + /// + /// @param response byte - Key data returned from the license server + /// + /// @return A keySetId if the response is for an offline key requests which + /// can be used later with restoreKeys, + /// else return empty for streaming key requests. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 the response argument must be a bytearray instead of byte. + /// + ProvideKeyResponse(...); +#else + String ProvideKeyResponse(const XbmcCommons::Buffer &response); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ RemoveKeys() } + /// Removes all keys currently loaded in a session. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + RemoveKeys(...); +#else + void RemoveKeys(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ RestoreKeys(keySetId) } + /// Restores session keys stored during previous + /// \ref xbmcdrm_ProvideKeyResponse "ProvideKeyResponse" call. + /// \anchor xbmcdrm_RestoreKeys + /// + /// @param keySetId string - Identifies the saved key set to restore. + /// This value must never be null. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + RestoreKeys(...); +#else + void RestoreKeys(const String& keySetId); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ SetPropertyString(name, value) } + /// Set a system specific property value in the %DRM system. + /// + /// \anchor xbmcdrm_SetPropertyString + /// + /// @param name string - Name of the property. This value must never be null. + /// @param value string - Value of the property to set. This value must never be null. + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + SetPropertyString(...); +#else + void SetPropertyString(const String &name, const String &value); +#endif + +/*******************Crypto section *****************/ + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ Decrypt(cipherKeyId, input, iv) } + /// Decrypt an encrypted data by using session keys. + /// + /// \anchor xbmcdrm_Decrypt + /// + /// @param cipherKeyId byte - Encryption key id (provided from a service handshake) + /// @param input byte - Cipher text to decrypt + /// @param iv byte - Initialization vector of cipher text + /// + /// @return Decrypted input data + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 all arguments need to be of type bytearray instead of byte. + /// + Decrypt(...); +#else + XbmcCommons::Buffer Decrypt(const XbmcCommons::Buffer &cipherKeyId, const XbmcCommons::Buffer &input, const XbmcCommons::Buffer &iv); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ Encrypt(cipherKeyId, input, iv) } + /// Encrypt data by using session keys. + /// + /// \anchor xbmcdrm_Encrypt + /// + /// @param cipherKeyId byte - Encryption key id (provided from a service handshake) + /// @param input byte - Encrypted text + /// @param iv byte - Initialization vector of encrypted text + /// + /// @return byte - Encrypted input data + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 all arguments need to be of type bytearray instead of byte. + /// + Encrypt(...); +#else + XbmcCommons::Buffer Encrypt(const XbmcCommons::Buffer &cipherKeyId, const XbmcCommons::Buffer &input, const XbmcCommons::Buffer &iv); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ Sign(macKeyId, message) } + /// Generate a %DRM encrypted signature for a text message. + /// + /// \anchor xbmcdrm_Sign + /// + /// @param macKeyId byte - HMAC key id (provided from a service handshake) + /// @param message byte - Message text on which to base the signature + /// + /// @return byte - Signature + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 all arguments need to be of type bytearray instead of byte. + /// + Sign(...); +#else + XbmcCommons::Buffer Sign(const XbmcCommons::Buffer &macKeyId, const XbmcCommons::Buffer &message); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcdrm + /// @brief \python_func{ Verify(macKeyId, message, signature) } + /// Verify the validity of a %DRM signature of a text message. + /// + /// \anchor xbmcdrm_Verify + /// + /// @param macKeyId byte - HMAC key id (provided from a service handshake) + /// @param message byte - Message text on which the signature is based + /// @param signature byte - The signature to verify + /// + /// @return true when the signature is valid + /// + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// @python_v19 With python 3 for all arguments is needed to pass bytearray instead of byte. + /// + Verify(...); +#else + bool Verify(const XbmcCommons::Buffer &macKeyId, const XbmcCommons::Buffer &message, const XbmcCommons::Buffer &signature); +#endif + + }; + ///@} + } +} diff --git a/xbmc/interfaces/legacy/Exception.h b/xbmc/interfaces/legacy/Exception.h new file mode 100644 index 0000000..61a32be --- /dev/null +++ b/xbmc/interfaces/legacy/Exception.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 "commons/Exception.h" +#include "utils/log.h" + +#ifndef SWIG +namespace XBMCAddon +{ + XBMCCOMMONS_STANDARD_EXCEPTION(WrongTypeException); + + /** + * UnimplementedException Can be used in places like the + * Control hierarchy where the + * requirements of dynamic language usage force us to add + * unimplemented methods to a class hierarchy. See the + * detailed explanation on the class Control for more. + */ + class UnimplementedException : public XbmcCommons::Exception + { + public: + inline UnimplementedException(const UnimplementedException& other) = default; + inline UnimplementedException(const char* classname, const char* methodname) : + Exception("UnimplementedException") + { SetMessage("Unimplemented method: %s::%s(...)", classname, methodname); } + }; + + /** + * This is what callback exceptions from the scripting language + * are translated to. + */ + class UnhandledException : public XbmcCommons::Exception + { + public: + inline UnhandledException(const UnhandledException& other) = default; + inline UnhandledException(const char* _message,...) : Exception("UnhandledException") { XBMCCOMMONS_COPYVARARGS(_message); } + }; +} +#endif + +/** + * These macros allow the easy declaration (and definition) of parent + * class virtual methods that are not implemented until the child class. + * This is to support the idosyncracies of dynamically typed scripting + * languages. See the comment in AddonControl.h for more details. + */ +#define THROW_UNIMP(classname) throw UnimplementedException(classname, __FUNCTION__) + diff --git a/xbmc/interfaces/legacy/File.cpp b/xbmc/interfaces/legacy/File.cpp new file mode 100644 index 0000000..4c0ca68 --- /dev/null +++ b/xbmc/interfaces/legacy/File.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "File.h" + +namespace XBMCAddon +{ + namespace xbmcvfs + { + XbmcCommons::Buffer File::readBytes(unsigned long numBytes) + { + DelayedCallGuard dg(languageHook); + int64_t size = file->GetLength(); + if ((!numBytes || (((int64_t)numBytes) > size)) && (size >= 0)) + numBytes = (unsigned long) size; + + + XbmcCommons::Buffer ret(numBytes); + + if (numBytes == 0) + return ret; + + while(ret.remaining() > 0) + { + ssize_t bytesRead = file->Read(ret.curPosition(), ret.remaining()); + if (bytesRead <= 0) // we consider this a failure or a EOF, can't tell which, + { // return whatever we have already. + ret.flip(); + return ret; + } + ret.forward(bytesRead); + } + ret.flip(); + return ret; + } + + bool File::write(XbmcCommons::Buffer& buffer) + { + DelayedCallGuard dg(languageHook); + while (buffer.remaining() > 0) + { + ssize_t bytesWritten = file->Write( buffer.curPosition(), buffer.remaining()); + if (bytesWritten == 0) // this could be a failure (see HDFile, and XFileUtils) or + // it could mean something else when a negative number means an error + // (see CCurlFile). There is no consistency so we can only assume we're + // done when we get a 0. + return false; + else if (bytesWritten < 0) // But, if we get something less than zero, we KNOW it's an error. + return false; + buffer.forward(bytesWritten);// Otherwise, we advance the buffer by the amount written. + } + return true; + } + + } +} diff --git a/xbmc/interfaces/legacy/File.h b/xbmc/interfaces/legacy/File.h new file mode 100644 index 0000000..01a757a --- /dev/null +++ b/xbmc/interfaces/legacy/File.h @@ -0,0 +1,326 @@ +/* + * 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 "AddonClass.h" +#include "AddonString.h" +#include "LanguageHook.h" +#include "commons/Buffer.h" +#include "filesystem/File.h" + +#include <algorithm> + +namespace XBMCAddon +{ + + namespace xbmcvfs + { + + // + /// \defgroup python_file File + /// \ingroup python_xbmcvfs + /// @{ + /// @brief <b>Kodi's file class.</b> + /// + /// \python_class{ xbmcvfs.File(filepath, [mode]) } + /// + /// @param filepath string Selected file path + /// @param mode [opt] string Additional mode options (if no mode is supplied, the default is Open for Read). + /// | Mode | Description | + /// |:------:|:--------------------------------| + /// | w | Open for write | + /// + /// + ///-------------------------------------------------------------------------- + /// @python_v19 Added context manager support + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file, 'w') + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file, 'w') as f: + /// .. + /// .. + /// ~~~~~~~~~~~~~ + // + class File : public AddonClass + { + XFILE::CFile* file; + public: + inline File(const String& filepath, const char* mode = NULL) : file(new XFILE::CFile()) + { + DelayedCallGuard dg(languageHook); + if (mode && strncmp(mode, "w", 1) == 0) + file->OpenForWrite(filepath,true); + else + file->Open(filepath, XFILE::READ_NO_CACHE); + } + + inline ~File() override { delete file; } + +#if !defined(DOXYGEN_SHOULD_USE_THIS) + inline File* __enter__() { return this; } + inline void __exit__() { close(); } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ read([bytes]) } + /// Read file parts as string. + /// + /// @param bytes [opt] How many bytes to read - if not + /// set it will read the whole file + /// @return string + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// b = f.read() + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as file: + /// b = f.read() + /// .. + /// ~~~~~~~~~~~~~ + /// + read(...); +#else + inline String read(unsigned long numBytes = 0) + { + XbmcCommons::Buffer b = readBytes(numBytes); + return b.getString(numBytes == 0 ? b.remaining() : std::min((unsigned long)b.remaining(),numBytes)); + } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ readBytes(numbytes) } + /// Read bytes from file. + /// + /// @param numbytes How many bytes to read [opt]- if not set + /// it will read the whole file + /// @return bytearray + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// b = f.readBytes() + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as f: + /// b = f.readBytes() + /// .. + /// ~~~~~~~~~~~~~ + /// + readBytes(...); +#else + XbmcCommons::Buffer readBytes(unsigned long numBytes = 0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ write(buffer) } + /// To write given data in file. + /// + /// @param buffer Buffer to write to file + /// @return True on success. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file, 'w') + /// result = f.write(buffer) + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file, 'w') as f: + /// result = f.write(buffer) + /// .. + /// ~~~~~~~~~~~~~ + /// + write(...); +#else + bool write(XbmcCommons::Buffer& buffer); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ size() } + /// Get the file size. + /// + /// @return The file size + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// s = f.size() + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as f: + /// s = f.size() + /// .. + /// ~~~~~~~~~~~~~ + /// + size(); +#else + inline long long size() { DelayedCallGuard dg(languageHook); return file->GetLength(); } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ seek(seekBytes, iWhence) } + /// Seek to position in file. + /// + /// @param seekBytes position in the file + /// @param iWhence [opt] where in a file to seek from[0 beginning, + /// 1 current , 2 end position] + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 Function changed. param **iWhence** is now optional. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// result = f.seek(8129, 0) + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as f: + /// result = f.seek(8129, 0) + /// .. + /// ~~~~~~~~~~~~~ + /// + seek(...); +#else + inline long long seek(long long seekBytes, int iWhence = SEEK_SET) { DelayedCallGuard dg(languageHook); return file->Seek(seekBytes,iWhence); } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ tell() } + /// Get the current position in the file. + /// + /// @return The file position + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// s = f.tell() + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as f: + /// s = f.tell() + /// .. + /// ~~~~~~~~~~~~~ + /// + tell(); +#else + inline long long tell() { DelayedCallGuard dg(languageHook); return file->GetPosition(); } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_file + /// @brief \python_func{ close() } + /// Close opened file. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// f = xbmcvfs.File(file) + /// f.close() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// **Example (v19 and up):** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// with xbmcvfs.File(file) as f: + /// .. + /// .. + /// ~~~~~~~~~~~~~ + /// + close(); +#else + inline void close() { DelayedCallGuard dg(languageHook); file->Close(); } +#endif + +#ifndef SWIG + inline const XFILE::CFile* getFile() const { return file; } +#endif + + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/InfoTagGame.cpp b/xbmc/interfaces/legacy/InfoTagGame.cpp new file mode 100644 index 0000000..697247b --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagGame.cpp @@ -0,0 +1,172 @@ +/* + * Copyright (C) 2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "InfoTagGame.h" + +#include "AddonUtils.h" +#include "games/tags/GameInfoTag.h" + +using namespace KODI::GAME; +using namespace XBMCAddonUtils; + +namespace XBMCAddon +{ +namespace xbmc +{ + +InfoTagGame::InfoTagGame(bool offscreen /* = false */) + : infoTag(new CGameInfoTag), offscreen(offscreen), owned(true) +{ +} + +InfoTagGame::InfoTagGame(const CGameInfoTag* tag) + : infoTag(new CGameInfoTag(*tag)), offscreen(true), owned(true) +{ +} + +InfoTagGame::InfoTagGame(CGameInfoTag* tag, bool offscreen /* = false */) + : infoTag(tag), offscreen(offscreen), owned(false) +{ +} + +InfoTagGame::~InfoTagGame() +{ + if (owned) + delete infoTag; +} + +String InfoTagGame::getTitle() +{ + return infoTag->GetTitle(); +} + +String InfoTagGame::getPlatform() +{ + return infoTag->GetPlatform(); +} + +std::vector<String> InfoTagGame::getGenres() +{ + return infoTag->GetGenres(); +} + +String InfoTagGame::getPublisher() +{ + return infoTag->GetPublisher(); +} + +String InfoTagGame::getDeveloper() +{ + return infoTag->GetDeveloper(); +} + +String InfoTagGame::getOverview() +{ + return infoTag->GetOverview(); +} + +unsigned int InfoTagGame::getYear() +{ + return infoTag->GetYear(); +} + +String InfoTagGame::getGameClient() +{ + return infoTag->GetGameClient(); +} + +void InfoTagGame::setTitle(const String& title) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTitleRaw(infoTag, title); +} + +void InfoTagGame::setPlatform(const String& platform) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPlatformRaw(infoTag, platform); +} + +void InfoTagGame::setGenres(const std::vector<String>& genres) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setGenresRaw(infoTag, genres); +} + +void InfoTagGame::setPublisher(const String& publisher) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPublisherRaw(infoTag, publisher); +} + +void InfoTagGame::setDeveloper(const String& developer) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDeveloperRaw(infoTag, developer); +} + +void InfoTagGame::setOverview(const String& overview) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setOverviewRaw(infoTag, overview); +} + +void InfoTagGame::setYear(unsigned int year) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setYearRaw(infoTag, year); +} + +void InfoTagGame::setGameClient(const String& gameClient) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setGameClientRaw(infoTag, gameClient); +} + +void InfoTagGame::setTitleRaw(KODI::GAME::CGameInfoTag* infoTag, const String& title) +{ + infoTag->SetTitle(title); +} + +void InfoTagGame::setPlatformRaw(KODI::GAME::CGameInfoTag* infoTag, const String& platform) +{ + infoTag->SetPlatform(platform); +} + +void InfoTagGame::setGenresRaw(KODI::GAME::CGameInfoTag* infoTag, const std::vector<String>& genres) +{ + infoTag->SetGenres(genres); +} + +void InfoTagGame::setPublisherRaw(KODI::GAME::CGameInfoTag* infoTag, const String& publisher) +{ + infoTag->SetPublisher(publisher); +} + +void InfoTagGame::setDeveloperRaw(KODI::GAME::CGameInfoTag* infoTag, const String& developer) +{ + infoTag->SetDeveloper(developer); +} + +void InfoTagGame::setOverviewRaw(KODI::GAME::CGameInfoTag* infoTag, const String& overview) +{ + infoTag->SetOverview(overview); +} + +void InfoTagGame::setYearRaw(KODI::GAME::CGameInfoTag* infoTag, unsigned int year) +{ + infoTag->SetYear(year); +} + +void InfoTagGame::setGameClientRaw(KODI::GAME::CGameInfoTag* infoTag, const String& gameClient) +{ + infoTag->SetGameClient(gameClient); +} + +} // namespace xbmc +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/InfoTagGame.h b/xbmc/interfaces/legacy/InfoTagGame.h new file mode 100644 index 0000000..a6161b4 --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagGame.h @@ -0,0 +1,383 @@ +/* + * Copyright (C) 2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonClass.h" + +namespace KODI +{ +namespace GAME +{ +class CGameInfoTag; +} +} // namespace KODI + +namespace XBMCAddon +{ +namespace xbmc +{ +/// +/// \defgroup python_InfoTagGame InfoTagGame +/// \ingroup python_xbmc +/// @{ +/// @brief **Kodi's game info tag class.** +/// +/// \python_class{ InfoTagGame() } +/// +/// Access and / or modify the game metadata of a ListItem. +/// +///------------------------------------------------------------------------- +/// @python_v20 New class added. +/// +/// **Example:** +/// ~~~~~~~~~~~~~{.py} +/// ... +/// tag = item.getGameInfoTag() +/// +/// title = tag.getTitle() +/// tag.setDeveloper('John Doe') +/// ... +/// ~~~~~~~~~~~~~ +/// +class InfoTagGame : public AddonClass +{ +private: + KODI::GAME::CGameInfoTag* infoTag; + bool offscreen; + bool owned; + +public: +#ifndef SWIG + explicit InfoTagGame(const KODI::GAME::CGameInfoTag* tag); + explicit InfoTagGame(KODI::GAME::CGameInfoTag* tag, bool offscreen = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ xbmc.InfoTagGame([offscreen]) } + /// Create a game info tag. + /// + /// @param offscreen [opt] bool (default `False`) - if GUI based locks should be + /// avoided. Most of the times listitems are created + /// offscreen and added later to a container + /// for display (e.g. plugins) or they are not + /// even displayed (e.g. python scrapers). + /// In such cases, there is no need to lock the + /// GUI when creating the items (increasing your addon + /// performance). + /// Note however, that if you are creating listitems + /// and managing the container itself (e.g using + /// WindowXML or WindowXMLDialog classes) subsquent + /// modifications to the item will require locking. + /// Thus, in such cases, use the default value (`False`). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// gameinfo = xbmc.InfoTagGame(offscreen=False) + /// ... + /// ~~~~~~~~~~~~~ + /// + InfoTagGame(...); +#else + explicit InfoTagGame(bool offscreen = false); +#endif + ~InfoTagGame() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getTitle() } + /// Gets the title of the game. + /// + /// @return [string] title + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getTitle(); +#else + String getTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getPlatform() } + /// Gets the platform on which the game is run. + /// + /// @return [string] platform + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getPlatform(); +#else + String getPlatform(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getGenres() } + /// Gets the genres of the game. + /// + /// @return [list] genres + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getGenres(); +#else + std::vector<String> getGenres(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getPublisher() } + /// Gets the publisher of the game. + /// + /// @return [string] publisher + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getPublisher(); +#else + String getPublisher(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getDeveloper() } + /// Gets the developer of the game. + /// + /// @return [string] developer + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getDeveloper(); +#else + String getDeveloper(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getOverview() } + /// Gets the overview of the game. + /// + /// @return [string] overview + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getOverview(); +#else + String getOverview(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getYear() } + /// Gets the year in which the game was published. + /// + /// @return [integer] year + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getYear(); +#else + unsigned int getYear(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ getGameClient() } + /// Gets the add-on ID of the game client executing the game. + /// + /// @return [string] game client + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getGameClient(); +#else + String getGameClient(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setTitle(title) } + /// Sets the title of the game. + /// + /// @param title string - title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTitle(...); +#else + void setTitle(const String& title); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setPlatform(platform) } + /// Sets the platform on which the game is run. + /// + /// @param platform string - platform. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPlatform(...); +#else + void setPlatform(const String& platform); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setGenres(genres) } + /// Sets the genres of the game. + /// + /// @param genres list - genres. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setGenres(...); +#else + void setGenres(const std::vector<String>& genres); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setPublisher(publisher) } + /// Sets the publisher of the game. + /// + /// @param publisher string - publisher. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPublisher(...); +#else + void setPublisher(const String& publisher); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setDeveloper(developer) } + /// Sets the developer of the game. + /// + /// @param developer string - title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDeveloper(...); +#else + void setDeveloper(const String& developer); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setOverview(overview) } + /// Sets the overview of the game. + /// + /// @param overview string - overview. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setOverview(...); +#else + void setOverview(const String& overview); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setYear(year) } + /// Sets the year in which the game was published. + /// + /// @param year integer - year. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setYear(...); +#else + void setYear(unsigned int year); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagGame + /// @brief \python_func{ setGameClient(gameClient) } + /// Sets the add-on ID of the game client executing the game. + /// + /// @param gameClient string - game client. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setGameClient(...); +#else + void setGameClient(const String& gameClient); +#endif + +#ifndef SWIG + static void setTitleRaw(KODI::GAME::CGameInfoTag* infoTag, const String& title); + static void setPlatformRaw(KODI::GAME::CGameInfoTag* infoTag, const String& platform); + static void setGenresRaw(KODI::GAME::CGameInfoTag* infoTag, const std::vector<String>& genres); + static void setPublisherRaw(KODI::GAME::CGameInfoTag* infoTag, const String& publisher); + static void setDeveloperRaw(KODI::GAME::CGameInfoTag* infoTag, const String& developer); + static void setOverviewRaw(KODI::GAME::CGameInfoTag* infoTag, const String& overview); + static void setYearRaw(KODI::GAME::CGameInfoTag* infoTag, unsigned int year); + static void setGameClientRaw(KODI::GAME::CGameInfoTag* infoTag, const String& gameClient); +#endif +}; + +} // namespace xbmc +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/InfoTagMusic.cpp b/xbmc/interfaces/legacy/InfoTagMusic.cpp new file mode 100644 index 0000000..3baf7d5 --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagMusic.cpp @@ -0,0 +1,468 @@ +/* + * 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 "InfoTagMusic.h" + +#include "AddonUtils.h" +#include "ServiceBroker.h" +#include "music/tags/MusicInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +namespace XBMCAddon +{ + namespace xbmc + { + InfoTagMusic::InfoTagMusic(bool offscreen /* = false */) + : infoTag(new MUSIC_INFO::CMusicInfoTag()), offscreen(offscreen), owned(true) + { + } + + InfoTagMusic::InfoTagMusic(const MUSIC_INFO::CMusicInfoTag* tag) : InfoTagMusic(true) + { + *infoTag = *tag; + } + + InfoTagMusic::InfoTagMusic(MUSIC_INFO::CMusicInfoTag* tag, bool offscreen /* = false */) + : infoTag(tag), offscreen(offscreen), owned(false) + { + } + + InfoTagMusic::~InfoTagMusic() + { + if (owned) + delete infoTag; + } + + int InfoTagMusic::getDbId() + { + return infoTag->GetDatabaseId(); + } + + String InfoTagMusic::getURL() + { + return infoTag->GetURL(); + } + + String InfoTagMusic::getTitle() + { + return infoTag->GetTitle(); + } + + String InfoTagMusic::getMediaType() + { + return infoTag->GetType(); + } + + String InfoTagMusic::getArtist() + { + return infoTag->GetArtistString(); + } + + String InfoTagMusic::getAlbumArtist() + { + return infoTag->GetAlbumArtistString(); + } + + String InfoTagMusic::getAlbum() + { + return infoTag->GetAlbum(); + } + + String InfoTagMusic::getGenre() + { + return StringUtils::Join(infoTag->GetGenre(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + } + + std::vector<String> InfoTagMusic::getGenres() + { + return infoTag->GetGenre(); + } + + int InfoTagMusic::getDuration() + { + return infoTag->GetDuration(); + } + + int InfoTagMusic::getYear() + { + return infoTag->GetYear(); + } + + int InfoTagMusic::getRating() + { + return infoTag->GetRating(); + } + + int InfoTagMusic::getUserRating() + { + return infoTag->GetUserrating(); + } + + int InfoTagMusic::getTrack() + { + return infoTag->GetTrackNumber(); + } + + int InfoTagMusic::getDisc() + { + return infoTag->GetDiscNumber(); + } + + String InfoTagMusic::getReleaseDate() + { + return infoTag->GetReleaseDate(); + } + + int InfoTagMusic::getListeners() + { + return infoTag->GetListeners(); + } + + int InfoTagMusic::getPlayCount() + { + return infoTag->GetPlayCount(); + } + + String InfoTagMusic::getLastPlayed() + { + CLog::Log(LOGWARNING, "InfoTagMusic.getLastPlayed() is deprecated and might be removed in " + "future Kodi versions. Please use InfoTagMusic.getLastPlayedAsW3C()."); + + return infoTag->GetLastPlayed().GetAsLocalizedDate(); + } + + String InfoTagMusic::getLastPlayedAsW3C() + { + return infoTag->GetLastPlayed().GetAsW3CDateTime(); + } + + String InfoTagMusic::getComment() + { + return infoTag->GetComment(); + } + + String InfoTagMusic::getLyrics() + { + return infoTag->GetLyrics(); + } + + String InfoTagMusic::getMusicBrainzTrackID() + { + return infoTag->GetMusicBrainzTrackID(); + } + + std::vector<String> InfoTagMusic::getMusicBrainzArtistID() + { + return infoTag->GetMusicBrainzArtistID(); + } + + String InfoTagMusic::getMusicBrainzAlbumID() + { + return infoTag->GetMusicBrainzAlbumID(); + } + + String InfoTagMusic::getMusicBrainzReleaseGroupID() + { + return infoTag->GetMusicBrainzReleaseGroupID(); + } + + std::vector<String> InfoTagMusic::getMusicBrainzAlbumArtistID() + { + return infoTag->GetMusicBrainzAlbumArtistID(); + } + + void InfoTagMusic::setDbId(int dbId, const String& type) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDbIdRaw(infoTag, dbId, type); + } + + void InfoTagMusic::setURL(const String& url) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setURLRaw(infoTag, url); + } + + void InfoTagMusic::setMediaType(const String& mediaType) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMediaTypeRaw(infoTag, mediaType); + } + + void InfoTagMusic::setTrack(int track) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTrackRaw(infoTag, track); + } + + void InfoTagMusic::setDisc(int disc) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDiscRaw(infoTag, disc); + } + + void InfoTagMusic::setDuration(int duration) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDurationRaw(infoTag, duration); + } + + void InfoTagMusic::setYear(int year) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setYearRaw(infoTag, year); + } + + void InfoTagMusic::setReleaseDate(const String& releaseDate) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setReleaseDateRaw(infoTag, releaseDate); + } + + void InfoTagMusic::setListeners(int listeners) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setListenersRaw(infoTag, listeners); + } + + void InfoTagMusic::setPlayCount(int playcount) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPlayCountRaw(infoTag, playcount); + } + + void InfoTagMusic::setGenres(const std::vector<String>& genres) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setGenresRaw(infoTag, genres); + } + + void InfoTagMusic::setAlbum(const String& album) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setAlbumRaw(infoTag, album); + } + + void InfoTagMusic::setArtist(const String& artist) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setArtistRaw(infoTag, artist); + } + + void InfoTagMusic::setAlbumArtist(const String& albumArtist) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setAlbumArtistRaw(infoTag, albumArtist); + } + + void InfoTagMusic::setTitle(const String& title) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTitleRaw(infoTag, title); + } + + void InfoTagMusic::setRating(float rating) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setRatingRaw(infoTag, rating); + } + + void InfoTagMusic::setUserRating(int userrating) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setUserRatingRaw(infoTag, userrating); + } + + void InfoTagMusic::setLyrics(const String& lyrics) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setLyricsRaw(infoTag, lyrics); + } + + void InfoTagMusic::setLastPlayed(const String& lastPlayed) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setLastPlayedRaw(infoTag, lastPlayed); + } + + void InfoTagMusic::setMusicBrainzTrackID(const String& musicBrainzTrackID) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMusicBrainzTrackIDRaw(infoTag, musicBrainzTrackID); + } + + void InfoTagMusic::setMusicBrainzArtistID(const std::vector<String>& musicBrainzArtistID) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMusicBrainzArtistIDRaw(infoTag, musicBrainzArtistID); + } + + void InfoTagMusic::setMusicBrainzAlbumID(const String& musicBrainzAlbumID) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMusicBrainzAlbumIDRaw(infoTag, musicBrainzAlbumID); + } + + void InfoTagMusic::setMusicBrainzReleaseGroupID(const String& musicBrainzReleaseGroupID) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMusicBrainzReleaseGroupIDRaw(infoTag, musicBrainzReleaseGroupID); + } + + void InfoTagMusic::setMusicBrainzAlbumArtistID( + const std::vector<String>& musicBrainzAlbumArtistID) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMusicBrainzAlbumArtistIDRaw(infoTag, musicBrainzAlbumArtistID); + } + + void InfoTagMusic::setComment(const String& comment) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setCommentRaw(infoTag, comment); + } + + void InfoTagMusic::setDbIdRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int dbId, const String& type) + { + infoTag->SetDatabaseId(dbId, type); + } + + void InfoTagMusic::setURLRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& url) + { + infoTag->SetURL(url); + } + + void InfoTagMusic::setMediaTypeRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& mediaType) + { + infoTag->SetType(mediaType); + } + + void InfoTagMusic::setTrackRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int track) + { + infoTag->SetTrackNumber(track); + } + + void InfoTagMusic::setDiscRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int disc) + { + infoTag->SetDiscNumber(disc); + } + + void InfoTagMusic::setDurationRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int duration) + { + infoTag->SetDuration(duration); + } + + void InfoTagMusic::setYearRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int year) + { + infoTag->SetYear(year); + } + + void InfoTagMusic::setReleaseDateRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& releaseDate) + { + infoTag->SetReleaseDate(releaseDate); + } + + void InfoTagMusic::setListenersRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int listeners) + { + infoTag->SetListeners(listeners); + } + + void InfoTagMusic::setPlayCountRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int playcount) + { + infoTag->SetPlayCount(playcount); + } + + void InfoTagMusic::setGenresRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const std::vector<String>& genres) + { + infoTag->SetGenre(genres); + } + + void InfoTagMusic::setAlbumRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& album) + { + infoTag->SetAlbum(album); + } + + void InfoTagMusic::setArtistRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& artist) + { + infoTag->SetArtist(artist); + } + + void InfoTagMusic::setAlbumArtistRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& albumArtist) + { + infoTag->SetAlbumArtist(albumArtist); + } + + void InfoTagMusic::setTitleRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& title) + { + infoTag->SetTitle(title); + } + + void InfoTagMusic::setRatingRaw(MUSIC_INFO::CMusicInfoTag* infoTag, float rating) + { + infoTag->SetRating(rating); + } + + void InfoTagMusic::setUserRatingRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int userrating) + { + infoTag->SetUserrating(userrating); + } + + void InfoTagMusic::setLyricsRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& lyrics) + { + infoTag->SetLyrics(lyrics); + } + + void InfoTagMusic::setLastPlayedRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& lastPlayed) + { + infoTag->SetLastPlayed(lastPlayed); + } + + void InfoTagMusic::setMusicBrainzTrackIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzTrackID) + { + infoTag->SetMusicBrainzTrackID(musicBrainzTrackID); + } + + void InfoTagMusic::setMusicBrainzArtistIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const std::vector<String>& musicBrainzArtistID) + { + infoTag->SetMusicBrainzArtistID(musicBrainzArtistID); + } + + void InfoTagMusic::setMusicBrainzAlbumIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzAlbumID) + { + infoTag->SetMusicBrainzAlbumID(musicBrainzAlbumID); + } + + void InfoTagMusic::setMusicBrainzReleaseGroupIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzReleaseGroupID) + { + infoTag->SetMusicBrainzReleaseGroupID(musicBrainzReleaseGroupID); + } + + void InfoTagMusic::setMusicBrainzAlbumArtistIDRaw( + MUSIC_INFO::CMusicInfoTag* infoTag, const std::vector<String>& musicBrainzAlbumArtistID) + { + infoTag->SetMusicBrainzAlbumArtistID(musicBrainzAlbumArtistID); + } + + void InfoTagMusic::setCommentRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& comment) + { + infoTag->SetComment(comment); + } + } +} + diff --git a/xbmc/interfaces/legacy/InfoTagMusic.h b/xbmc/interfaces/legacy/InfoTagMusic.h new file mode 100644 index 0000000..835a705 --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagMusic.h @@ -0,0 +1,1034 @@ +/* + * 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 "AddonClass.h" + +#include <vector> + +namespace MUSIC_INFO +{ +class CMusicInfoTag; +} + +namespace XBMCAddon +{ + namespace xbmc + { + // + /// \defgroup python_InfoTagMusic InfoTagMusic + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's music info tag class.** + /// + /// \python_class{ xbmc.InfoTagMusic([offscreen]) } + /// + /// Access and / or modify the music metadata of a ListItem. + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// tag = xbmc.Player().getMusicInfoTag() + /// + /// title = tag.getTitle() + /// url = tag.getURL() + /// ... + /// ~~~~~~~~~~~~~ + // + class InfoTagMusic : public AddonClass + { + private: + MUSIC_INFO::CMusicInfoTag* infoTag; + bool offscreen; + bool owned; + + public: +#ifndef SWIG + explicit InfoTagMusic(const MUSIC_INFO::CMusicInfoTag* tag); + explicit InfoTagMusic(MUSIC_INFO::CMusicInfoTag* tag, bool offscreen = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ xbmc.InfoTagMusic([offscreen]) } + /// Create a music info tag. + /// + /// @param offscreen [opt] bool (default `False`) - if GUI based locks should be + /// avoided. Most of the times listitems are created + /// offscreen and added later to a container + /// for display (e.g. plugins) or they are not + /// even displayed (e.g. python scrapers). + /// In such cases, there is no need to lock the + /// GUI when creating the items (increasing your addon + /// performance). + /// Note however, that if you are creating listitems + /// and managing the container itself (e.g using + /// WindowXML or WindowXMLDialog classes) subsquent + /// modifications to the item will require locking. + /// Thus, in such cases, use the default value (`False`). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Added **offscreen** argument. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// musicinfo = xbmc.InfoTagMusic(offscreen=False) + /// ... + /// ~~~~~~~~~~~~~ + /// + InfoTagMusic(...); +#else + explicit InfoTagMusic(bool offscreen = false); +#endif + ~InfoTagMusic() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getDbId() } + /// Get identification number of tag in database. + /// + /// @return [integer] database id. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getDbId(); +#else + int getDbId(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getURL() } + /// Returns url of source as string from music info tag. + /// + /// @return [string] Url of source + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getURL(); +#else + String getURL(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getTitle() } + /// Returns the title from music as string on info tag. + /// + /// @return [string] Music title + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getTitle(); +#else + String getTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMediaType() } + /// Get the media type of the music item. + /// + /// @return [string] media type + /// + /// Available strings about media type for music: + /// | String | Description | + /// |---------------:|:--------------------------------------------------| + /// | artist | If it is defined as an artist + /// | album | If it is defined as an album + /// | song | If it is defined as a song + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getMediaType(); +#else + String getMediaType(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getArtist() } + /// Returns the artist from music as string if present. + /// + /// @return [string] Music artist + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getArtist(); +#else + String getArtist(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getAlbum() } + /// Returns the album from music tag as string if present. + /// + /// @return [string] Music album name + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getAlbum(); +#else + String getAlbum(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getAlbumArtist() } + /// Returns the album artist from music tag as string if present. + /// + /// @return [string] Music album artist name + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getAlbumArtist(); +#else + String getAlbumArtist(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getGenre() } + /// Returns the genre name from music tag as string if present. + /// + /// @return [string] Genre name + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getGenres()** instead. + /// + getGenre(); +#else + String getGenre(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getGenres() } + /// Returns the list of genres from music tag if present. + /// + /// @return [list] List of genres + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getGenres(); +#else + std::vector<String> getGenres(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getDuration() } + /// Returns the duration of music as integer from info tag. + /// + /// @return [integer] Duration + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getDuration(); +#else + int getDuration(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getYear() } + /// Returns the year of music as integer from info tag. + /// + /// @return [integer] Year + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// + getYear(); +#else + int getYear(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getRating() } + /// Returns the scraped rating as integer. + /// + /// @return [integer] Rating + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getRating(); +#else + int getRating(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getUserRating() } + /// Returns the user rating as integer (-1 if not existing) + /// + /// @return [integer] User rating + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getUserRating(); +#else + int getUserRating(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getTrack() } + /// Returns the track number (if present) from music info tag as integer. + /// + /// @return [integer] Track number + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getTrack(); +#else + int getTrack(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getDisc() } + /// Returns the disk number (if present) from music info tag as integer. + /// + /// @return [integer] Disc number + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getDisc(); +#else + /** + * getDisc() -- returns an integer.\n + */ + int getDisc(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getReleaseDate() } + /// Returns the release date as string from music info tag (if present). + /// + /// @return [string] Release date + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getReleaseDate(); +#else + String getReleaseDate(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getListeners() } + /// Returns the listeners as integer from music info tag. + /// + /// @return [integer] Listeners + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getListeners(); +#else + int getListeners(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getPlayCount() } + /// Returns the number of carried out playbacks. + /// + /// @return [integer] Playback count + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPlayCount(); +#else + int getPlayCount(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getLastPlayed() } + /// Returns last played time as string from music info tag. + /// + /// @return [string] Last played date / time on tag + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getLastPlayedAsW3C()** instead. + /// + getLastPlayed(); +#else + String getLastPlayed(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getLastPlayedAsW3C() } + /// Returns last played time as string in W3C format (YYYY-MM-DDThh:mm:ssTZD). + /// + /// @return [string] Last played datetime (W3C) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getLastPlayedAsW3C(); +#else + String getLastPlayedAsW3C(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getComment() } + /// Returns comment as string from music info tag. + /// + /// @return [string] Comment on tag + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getComment(); +#else + String getComment(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getLyrics() } + /// Returns a string from lyrics. + /// + /// @return [string] Lyrics on tag + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getLyrics(); +#else + String getLyrics(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMusicBrainzTrackID() } + /// Returns the MusicBrainz Recording ID from music info tag (if present). + /// + /// @return [string] MusicBrainz Recording ID + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getMusicBrainzTrackID(); +#else + String getMusicBrainzTrackID(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMusicBrainzArtistID() } + /// Returns the MusicBrainz Artist IDs from music info tag (if present). + /// + /// @return [list] MusicBrainz Artist IDs + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getMusicBrainzArtistID(); +#else + std::vector<String> getMusicBrainzArtistID(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMusicBrainzAlbumID() } + /// Returns the MusicBrainz Release ID from music info tag (if present). + /// + /// @return [string] MusicBrainz Release ID + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getMusicBrainzAlbumID(); +#else + String getMusicBrainzAlbumID(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMusicBrainzReleaseGroupID() } + /// Returns the MusicBrainz Release Group ID from music info tag (if present). + /// + /// @return [string] MusicBrainz Release Group ID + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getMusicBrainzReleaseGroupID(); +#else + String getMusicBrainzReleaseGroupID(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ getMusicBrainzAlbumArtistID() } + /// Returns the MusicBrainz Release Artist IDs from music info tag (if present). + /// + /// @return [list] MusicBrainz Release Artist IDs + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getMusicBrainzAlbumArtistID(); +#else + std::vector<String> getMusicBrainzAlbumArtistID(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setDbId(dbId, type) } + /// Set the database identifier of the music item. + /// + /// @param dbId integer - Database identifier. + /// @param type string - Media type of the item. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDbId(...); +#else + void setDbId(int dbId, const String& type); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setURL(url) } + /// Set the URL of the music item. + /// + /// @param url string - URL. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setURL(...); +#else + void setURL(const String& url); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMediaType(mediaType) } + /// Set the media type of the music item. + /// + /// @param mediaType string - Media type. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMediaType(...); +#else + void setMediaType(const String& mediaType); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setTrack(track) } + /// Set the track number of the song. + /// + /// @param track integer - Track number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTrack(...); +#else + void setTrack(int track); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setDisc(disc) } + /// Set the disc number of the song. + /// + /// @param disc integer - Disc number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDisc(...); +#else + void setDisc(int disc); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setDuration(duration) } + /// Set the duration of the song. + /// + /// @param duration integer - Duration in seconds. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDuration(...); +#else + void setDuration(int duration); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setYear(year) } + /// Set the year of the music item. + /// + /// @param year integer - Year. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setYear(...); +#else + void setYear(int year); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setReleaseDate(releaseDate) } + /// Set the release date of the music item. + /// + /// @param releaseDate string - Release date in ISO8601 format (YYYY, YYYY-MM or YYYY-MM-DD). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setReleaseDate(...); +#else + void setReleaseDate(const String& releaseDate); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setListeners(listeners) } + /// Set the number of listeners of the music item. + /// + /// @param listeners integer - Number of listeners. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setListeners(...); +#else + void setListeners(int listeners); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setPlayCount(playcount) } + /// Set the playcount of the music item. + /// + /// @param playcount integer - Playcount. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPlayCount(...); +#else + void setPlayCount(int playcount); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setGenres(genres) } + /// Set the genres of the music item. + /// + /// @param genres list - Genres. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setGenres(...); +#else + void setGenres(const std::vector<String>& genres); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setAlbum(album) } + /// Set the album of the music item. + /// + /// @param album string - Album. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setAlbum(...); +#else + void setAlbum(const String& album); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setArtist(artist) } + /// Set the artist(s) of the music item. + /// + /// @param artist string - Artist(s). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setArtist(...); +#else + void setArtist(const String& artist); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setAlbumArtist(albumArtist) } + /// Set the album artist(s) of the music item. + /// + /// @param albumArtist string - Album artist(s). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setAlbumArtist(...); +#else + void setAlbumArtist(const String& albumArtist); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setTitle(title) } + /// Set the title of the music item. + /// + /// @param title string - Title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTitle(...); +#else + void setTitle(const String& title); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setRating(rating) } + /// Set the rating of the music item. + /// + /// @param rating float - Rating. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setRating(...); +#else + void setRating(float rating); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setUserRating(userrating) } + /// Set the user rating of the music item. + /// + /// @param userrating integer - User rating. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setUserRating(...); +#else + void setUserRating(int userrating); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setLyrics(lyrics) } + /// Set the lyrics of the song. + /// + /// @param lyrics string - Lyrics. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLyrics(...); +#else + void setLyrics(const String& lyrics); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setLastPlayed(lastPlayed) } + /// Set the last played date of the music item. + /// + /// @param lastPlayed string - Last played date (YYYY-MM-DD HH:MM:SS). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLastPlayed(...); +#else + void setLastPlayed(const String& lastPlayed); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMusicBrainzTrackID(musicBrainzTrackID) } + /// Set the MusicBrainz track ID of the song. + /// + /// @param musicBrainzTrackID string - MusicBrainz track ID. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMusicBrainzTrackID(...); +#else + void setMusicBrainzTrackID(const String& musicBrainzTrackID); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMusicBrainzArtistID(musicBrainzArtistID) } + /// Set the MusicBrainz artist IDs of the music item. + /// + /// @param musicBrainzArtistID list - MusicBrainz artist IDs. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMusicBrainzArtistID(...); +#else + void setMusicBrainzArtistID(const std::vector<String>& musicBrainzArtistID); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMusicBrainzAlbumID(musicBrainzAlbumID) } + /// Set the MusicBrainz album ID of the music item. + /// + /// @param musicBrainzAlbumID string - MusicBrainz album ID. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMusicBrainzAlbumID(...); +#else + void setMusicBrainzAlbumID(const String& musicBrainzAlbumID); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMusicBrainzReleaseGroupID(musicBrainzReleaseGroupID) } + /// Set the MusicBrainz release group ID of the music item. + /// + /// @param musicBrainzReleaseGroupID string - MusicBrainz release group ID. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMusicBrainzReleaseGroupID(...); +#else + void setMusicBrainzReleaseGroupID(const String& musicBrainzReleaseGroupID); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setMusicBrainzAlbumArtistID(musicBrainzAlbumArtistID) } + /// Set the MusicBrainz album artist IDs of the music item. + /// + /// @param musicBrainzAlbumArtistID list - MusicBrainz album artist IDs. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMusicBrainzAlbumArtistID(...); +#else + void setMusicBrainzAlbumArtistID(const std::vector<String>& musicBrainzAlbumArtistID); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagMusic + /// @brief \python_func{ setComment(comment) } + /// Set the comment of the music item. + /// + /// @param comment string - Comment. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setComment(...); +#else + void setComment(const String& comment); +#endif + +#ifndef SWIG + static void setDbIdRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int dbId, const String& type); + static void setURLRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& url); + static void setMediaTypeRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& mediaType); + static void setTrackRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int track); + static void setDiscRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int disc); + static void setDurationRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int duration); + static void setYearRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int year); + static void setReleaseDateRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& releaseDate); + static void setListenersRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int listeners); + static void setPlayCountRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int playcount); + static void setGenresRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const std::vector<String>& genres); + static void setAlbumRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& album); + static void setArtistRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& artist); + static void setAlbumArtistRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& albumArtist); + static void setTitleRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& title); + static void setRatingRaw(MUSIC_INFO::CMusicInfoTag* infoTag, float rating); + static void setUserRatingRaw(MUSIC_INFO::CMusicInfoTag* infoTag, int userrating); + static void setLyricsRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& lyrics); + static void setLastPlayedRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& lastPlayed); + static void setMusicBrainzTrackIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzTrackID); + static void setMusicBrainzArtistIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const std::vector<String>& musicBrainzArtistID); + static void setMusicBrainzAlbumIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzAlbumID); + static void setMusicBrainzReleaseGroupIDRaw(MUSIC_INFO::CMusicInfoTag* infoTag, + const String& musicBrainzReleaseGroupID); + static void setMusicBrainzAlbumArtistIDRaw( + MUSIC_INFO::CMusicInfoTag* infoTag, const std::vector<String>& musicBrainzAlbumArtistID); + static void setCommentRaw(MUSIC_INFO::CMusicInfoTag* infoTag, const String& comment); +#endif + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/InfoTagPicture.cpp b/xbmc/interfaces/legacy/InfoTagPicture.cpp new file mode 100644 index 0000000..892e2be --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagPicture.cpp @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "InfoTagPicture.h" + +#include "AddonUtils.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "interfaces/legacy/Exception.h" +#include "pictures/PictureInfoTag.h" +#include "utils/StringUtils.h" + +using namespace XBMCAddonUtils; + +namespace XBMCAddon +{ +namespace xbmc +{ + +InfoTagPicture::InfoTagPicture(bool offscreen /* = false */) + : infoTag(new CPictureInfoTag), offscreen(offscreen), owned(true) +{ +} + +InfoTagPicture::InfoTagPicture(const CPictureInfoTag* tag) + : infoTag(new CPictureInfoTag(*tag)), offscreen(true), owned(true) +{ +} + +InfoTagPicture::InfoTagPicture(CPictureInfoTag* tag, bool offscreen /* = false */) + : infoTag(tag), offscreen(offscreen), owned(false) +{ +} + +InfoTagPicture::~InfoTagPicture() +{ + if (owned) + delete infoTag; +} + +String InfoTagPicture::getResolution() +{ + return infoTag->GetInfo(SLIDESHOW_RESOLUTION); +} + +String InfoTagPicture::getDateTimeTaken() +{ + return infoTag->GetDateTimeTaken().GetAsW3CDateTime(); +} + +void InfoTagPicture::setResolution(int width, int height) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setResolutionRaw(infoTag, width, height); +} + +void InfoTagPicture::setDateTimeTaken(const String& datetimetaken) +{ + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDateTimeTakenRaw(infoTag, datetimetaken); +} + +void InfoTagPicture::setResolutionRaw(CPictureInfoTag* infoTag, const String& resolution) +{ + infoTag->SetInfo("resolution", resolution); +} + +void InfoTagPicture::setResolutionRaw(CPictureInfoTag* infoTag, int width, int height) +{ + if (width <= 0) + throw WrongTypeException("InfoTagPicture.setResolution: width must be greater than zero (0)"); + if (height <= 0) + throw WrongTypeException("InfoTagPicture.setResolution: height must be greater than zero (0)"); + + setResolutionRaw(infoTag, StringUtils::Format("{:d},{:d}", width, height)); +} + +void InfoTagPicture::setDateTimeTakenRaw(CPictureInfoTag* infoTag, String datetimetaken) +{ + // try to parse the datetimetaken as from W3C format and adjust it to the EXIF datetime format YYYY:MM:DD HH:MM:SS + CDateTime w3cDateTimeTaken; + if (w3cDateTimeTaken.SetFromW3CDateTime(datetimetaken)) + { + datetimetaken = StringUtils::Format("{:4d}:{:2d}:{:2d} {:2d}:{:2d}:{:2d}", + w3cDateTimeTaken.GetYear(), w3cDateTimeTaken.GetMonth(), + w3cDateTimeTaken.GetDay(), w3cDateTimeTaken.GetHour(), + w3cDateTimeTaken.GetMinute(), w3cDateTimeTaken.GetSecond()); + } + + infoTag->SetInfo("exiftime", datetimetaken); +} + +} // namespace xbmc +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/InfoTagPicture.h b/xbmc/interfaces/legacy/InfoTagPicture.h new file mode 100644 index 0000000..a03bf0f --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagPicture.h @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "AddonClass.h" + +class CPictureInfoTag; + +namespace XBMCAddon +{ +namespace xbmc +{ + +/// +/// \defgroup python_InfoTagPicture InfoTagPicture +/// \ingroup python_xbmc +/// @{ +/// @brief **Kodi's picture info tag class.** +/// +/// \python_class{ InfoTagPicture() } +/// +/// Access and / or modify the picture metadata of a ListItem. +/// +///------------------------------------------------------------------------- +/// @python_v20 New class added. +/// +/// **Example:** +/// ~~~~~~~~~~~~~{.py} +/// ... +/// tag = item.getPictureInfoTag() +/// +/// datetime_taken = tag.getDateTimeTaken() +/// tag.setResolution(1920, 1080) +/// ... +/// ~~~~~~~~~~~~~ +/// +class InfoTagPicture : public AddonClass +{ +private: + CPictureInfoTag* infoTag; + bool offscreen; + bool owned; + +public: +#ifndef SWIG + explicit InfoTagPicture(const CPictureInfoTag* tag); + explicit InfoTagPicture(CPictureInfoTag* tag, bool offscreen = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagPicture + /// @brief \python_func{ xbmc.InfoTagPicture([offscreen]) } + /// Create a picture info tag. + /// + /// @param offscreen [opt] bool (default `False`) - if GUI based locks should be + /// avoided. Most of the times listitems are created + /// offscreen and added later to a container + /// for display (e.g. plugins) or they are not + /// even displayed (e.g. python scrapers). + /// In such cases, there is no need to lock the + /// GUI when creating the items (increasing your addon + /// performance). + /// Note however, that if you are creating listitems + /// and managing the container itself (e.g using + /// WindowXML or WindowXMLDialog classes) subsquent + /// modifications to the item will require locking. + /// Thus, in such cases, use the default value (`False`). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// pictureinfo = xbmc.InfoTagPicture(offscreen=False) + /// ... + /// ~~~~~~~~~~~~~ + /// + InfoTagPicture(...); +#else + explicit InfoTagPicture(bool offscreen = false); +#endif + ~InfoTagPicture() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagPicture + /// @brief \python_func{ getResolution() } + /// Get the resolution of the picture in the format "w x h". + /// + /// @return [string] Resolution of the picture in the format "w x h". + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getResolution(); +#else + String getResolution(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagPicture + /// @brief \python_func{ getDateTimeTaken() } + /// Get the date and time at which the picture was taken in W3C format. + /// + /// @return [string] Date and time at which the picture was taken in W3C format. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getDirector(); +#else + String getDateTimeTaken(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagPicture + /// @brief \python_func{ setResolution(width, height) } + /// Sets the resolution of the picture. + /// + /// @param width int - Width of the picture in pixels. + /// @param height int - Height of the picture in pixels. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setResolution(...); +#else + void setResolution(int width, int height); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagPicture + /// @brief \python_func{ setDateTimeTaken(datetimetaken) } + /// Sets the date and time at which the picture was taken in W3C format. + /// The following formats are supported: + /// - YYYY + /// - YYYY-MM-DD + /// - YYYY-MM-DDThh:mm[TZD] + /// - YYYY-MM-DDThh:mm:ss[TZD] + /// where the timezone (TZD) is always optional and can be in one of the + /// following formats: + /// - Z (for UTC) + /// - +hh:mm + /// - -hh:mm + /// + /// @param datetimetaken string - Date and time at which the picture was taken in W3C format. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDateTimeTaken(...); +#else + void setDateTimeTaken(const String& datetimetaken); +#endif + +#ifndef SWIG + static void setResolutionRaw(CPictureInfoTag* infoTag, const String& resolution); + static void setResolutionRaw(CPictureInfoTag* infoTag, int width, int height); + static void setDateTimeTakenRaw(CPictureInfoTag* infoTag, String datetimetaken); +#endif +}; + +} // namespace xbmc +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/InfoTagRadioRDS.cpp b/xbmc/interfaces/legacy/InfoTagRadioRDS.cpp new file mode 100644 index 0000000..b60ea4c --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagRadioRDS.cpp @@ -0,0 +1,234 @@ +/* + * 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 "InfoTagRadioRDS.h" + +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRRadioRDSInfoTag.h" +#include "utils/StringUtils.h" + +namespace XBMCAddon +{ + namespace xbmc + { + InfoTagRadioRDS::InfoTagRadioRDS() = default; + + InfoTagRadioRDS::InfoTagRadioRDS(const std::shared_ptr<PVR::CPVRChannel>& channel) + { + if (channel) + infoTag = channel->GetRadioRDSInfoTag(); + } + + InfoTagRadioRDS::~InfoTagRadioRDS() = default; + + String InfoTagRadioRDS::getTitle() + { + if (infoTag) + return infoTag->GetTitle(); + return ""; + } + + String InfoTagRadioRDS::getBand() + { + if (infoTag) + return infoTag->GetBand(); + return ""; + } + + String InfoTagRadioRDS::getArtist() + { + if (infoTag) + return infoTag->GetArtist(); + return ""; + } + + String InfoTagRadioRDS::getComposer() + { + if (infoTag) + return infoTag->GetComposer(); + return ""; + } + + String InfoTagRadioRDS::getConductor() + { + if (infoTag) + return infoTag->GetConductor(); + return ""; + } + + String InfoTagRadioRDS::getAlbum() + { + if (infoTag) + return infoTag->GetAlbum(); + return ""; + } + + String InfoTagRadioRDS::getComment() + { + if (infoTag) + return infoTag->GetComment(); + return ""; + } + + int InfoTagRadioRDS::getAlbumTrackNumber() + { + if (infoTag) + return infoTag->GetAlbumTrackNumber(); + return 0; + } + + String InfoTagRadioRDS::getInfoNews() + { + if (infoTag) + return infoTag->GetInfoNews(); + return ""; + } + + String InfoTagRadioRDS::getInfoNewsLocal() + { + if (infoTag) + return infoTag->GetInfoNewsLocal(); + return ""; + } + + String InfoTagRadioRDS::getInfoSport() + { + if (infoTag) + return infoTag->GetInfoSport(); + return ""; + } + + String InfoTagRadioRDS::getInfoStock() + { + if (infoTag) + return infoTag->GetInfoStock(); + return ""; + } + + String InfoTagRadioRDS::getInfoWeather() + { + if (infoTag) + return infoTag->GetInfoWeather(); + return ""; + } + + String InfoTagRadioRDS::getInfoHoroscope() + { + if (infoTag) + return infoTag->GetInfoHoroscope(); + return ""; + } + + String InfoTagRadioRDS::getInfoCinema() + { + if (infoTag) + return infoTag->GetInfoCinema(); + return ""; + } + + String InfoTagRadioRDS::getInfoLottery() + { + if (infoTag) + return infoTag->GetInfoLottery(); + return ""; + } + + String InfoTagRadioRDS::getInfoOther() + { + if (infoTag) + return infoTag->GetInfoOther(); + return ""; + } + + String InfoTagRadioRDS::getEditorialStaff() + { + if (infoTag) + return infoTag->GetEditorialStaff(); + return ""; + } + + String InfoTagRadioRDS::getProgStation() + { + if (infoTag) + return infoTag->GetProgStation(); + return ""; + } + + String InfoTagRadioRDS::getProgStyle() + { + if (infoTag) + return infoTag->GetProgStyle(); + return ""; + } + + String InfoTagRadioRDS::getProgHost() + { + if (infoTag) + return infoTag->GetProgHost(); + return ""; + } + + String InfoTagRadioRDS::getProgWebsite() + { + if (infoTag) + return infoTag->GetProgWebsite(); + return ""; + } + + String InfoTagRadioRDS::getProgNow() + { + if (infoTag) + return infoTag->GetProgNow(); + return ""; + } + + String InfoTagRadioRDS::getProgNext() + { + if (infoTag) + return infoTag->GetProgNext(); + return ""; + } + + String InfoTagRadioRDS::getPhoneHotline() + { + if (infoTag) + return infoTag->GetPhoneHotline(); + return ""; + } + + String InfoTagRadioRDS::getEMailHotline() + { + if (infoTag) + return infoTag->GetEMailHotline(); + return ""; + } + + String InfoTagRadioRDS::getPhoneStudio() + { + if (infoTag) + return infoTag->GetPhoneStudio(); + return ""; + } + + String InfoTagRadioRDS::getEMailStudio() + { + if (infoTag) + return infoTag->GetEMailStudio(); + return ""; + } + + String InfoTagRadioRDS::getSMSStudio() + { + if (infoTag) + return infoTag->GetSMSStudio(); + return ""; + } + + } +} + diff --git a/xbmc/interfaces/legacy/InfoTagRadioRDS.h b/xbmc/interfaces/legacy/InfoTagRadioRDS.h new file mode 100644 index 0000000..6a68221 --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagRadioRDS.h @@ -0,0 +1,443 @@ +/* + * 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 "AddonClass.h" + +#include <memory> + +namespace PVR +{ +class CPVRChannel; +class CPVRRadioRDSInfoTag; +} + +namespace XBMCAddon +{ + namespace xbmc + { + // + /// \defgroup python_InfoTagRadioRDS InfoTagRadioRDS + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's radio RDS info tag class.** + /// + /// \python_class{ InfoTagRadioRDS() } + /// + /// To get radio RDS info tag data of currently played PVR radio channel source. + /// + /// @note Info tag load is only be possible from present player class.\n + /// Also is all the data variable from radio channels and not known on beginning + /// of radio receiving. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// tag = xbmc.Player().getRadioRDSInfoTag() + /// + /// title = tag.getTitle() + /// artist = tag.getArtist() + /// ... + /// ~~~~~~~~~~~~~ + // + class InfoTagRadioRDS : public AddonClass + { + private: + std::shared_ptr<PVR::CPVRRadioRDSInfoTag> infoTag; + + public: +#ifndef SWIG + explicit InfoTagRadioRDS(const std::shared_ptr<PVR::CPVRChannel>& channel); +#endif + InfoTagRadioRDS(); + ~InfoTagRadioRDS() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getTitle() } + /// Title of the item on the air; i.e. song title. + /// + /// @return Title + /// + getTitle(); +#else + String getTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getBand() } + /// Band of the item on air. + /// + /// @return Band + /// + getBand(); +#else + String getBand(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getArtist() } + /// Artist of the item on air. + /// + /// @return Artist + /// + getArtist(); +#else + String getArtist(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getComposer() } + /// Get the Composer of the music. + /// + /// @return Composer + /// + getComposer(); +#else + String getComposer(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getConductor() } + /// Get the Conductor of the Band. + /// + /// @return Conductor + /// + getConductor(); +#else + String getConductor(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getAlbum() } + /// Album of item on air. + /// + /// @return Album name + /// + getAlbum(); +#else + String getAlbum(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getComment() } + /// Get Comment text from channel. + /// + /// @return Comment + /// + getComment(); +#else + String getComment(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getAlbumTrackNumber() } + /// Get the album track number of currently sended music. + /// + /// @return Track Number + /// + getAlbumTrackNumber(); +#else + int getAlbumTrackNumber(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoNews() } + /// Get News informations. + /// + /// @return News Information + /// + getInfoNews(); +#else + String getInfoNews(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoNewsLocal() } + /// Get Local news informations. + /// + /// @return Local News Information + /// + getInfoNewsLocal(); +#else + String getInfoNewsLocal(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoSport() } + /// Get Sport informations. + /// + /// @return Sport Information + /// + getInfoSport(); +#else + String getInfoSport(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoStock() } + /// Get Stock informations. + /// + /// @return Stock Information + /// + getInfoStock(); +#else + String getInfoStock(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoWeather() } + /// Get Weather informations. + /// + /// @return Weather Information + /// + getInfoWeather(); +#else + String getInfoWeather(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoHoroscope() } + /// Get Horoscope informations. + /// + /// @return Horoscope Information + /// + getInfoHoroscope(); +#else + String getInfoHoroscope(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoCinema() } + /// Get Cinema informations. + /// + /// @return Cinema Information + /// + getInfoCinema(); +#else + String getInfoCinema(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoLottery() } + /// Get Lottery informations. + /// + /// @return Lottery Information + /// + getInfoLottery(); +#else + String getInfoLottery(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getInfoOther() } + /// Get other informations. + /// + /// @return Other Information + /// + getInfoOther(); +#else + String getInfoOther(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getEditorialStaff() } + /// Get Editorial Staff names. + /// + /// @return Editorial Staff + /// + getEditorialStaff(); +#else + String getEditorialStaff(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgStation() } + /// Name describing station. + /// + /// @return Program Station + /// + getProgStation(); +#else + String getProgStation(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgStyle() } + /// The the radio channel style currently used. + /// + /// @return Program Style + /// + getProgStyle(); +#else + String getProgStyle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgHost() } + /// Host of current radio show. + /// + /// @return Program Host + /// + getProgHost(); +#else + String getProgHost(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgWebsite() } + /// Link to URL (web page) for radio station homepage. + /// + /// @return Program Website + /// + getProgWebsite(); +#else + String getProgWebsite(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgNow() } + /// Current radio program show. + /// + /// @return Program Now + /// + getProgNow(); +#else + String getProgNow(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getProgNext() } + /// Next program show. + /// + /// @return Program Next + /// + getProgNext(); +#else + String getProgNext(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getPhoneHotline() } + /// Telephone number of the radio station's hotline. + /// + /// @return Phone Hotline + /// + getPhoneHotline(); +#else + String getPhoneHotline(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getEMailHotline() } + /// Email address of the radio station's studio. + /// + /// @return EMail Hotline + /// + getEMailHotline(); +#else + String getEMailHotline(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getPhoneStudio() } + /// Telephone number of the radio station's studio. + /// + /// @return Phone Studio + /// + getPhoneStudio(); +#else + String getPhoneStudio(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getEMailStudio() } + /// Email address of radio station studio. + /// + /// @return EMail Studio + /// + getEMailStudio(); +#else + String getEMailStudio(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// @ingroup python_InfoTagRadioRDS + /// @brief \python_func{ getSMSStudio() } + /// SMS (Text Messaging) number for studio. + /// + /// @return SMS Studio + /// + getSMSStudio(); +#else + String getSMSStudio(); +#endif + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/InfoTagVideo.cpp b/xbmc/interfaces/legacy/InfoTagVideo.cpp new file mode 100644 index 0000000..8bc5fd1 --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagVideo.cpp @@ -0,0 +1,1063 @@ +/* + * 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 "InfoTagVideo.h" + +#include "AddonUtils.h" +#include "ServiceBroker.h" +#include "interfaces/legacy/Exception.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <utility> + +namespace XBMCAddon +{ + namespace xbmc + { + Actor::Actor(const String& name /* = emptyString */, + const String& role /* = emptyString */, + int order /* = -1 */, + const String& thumbnail /* = emptyString */) + : m_name(name), m_role(role), m_order(order), m_thumbnail(thumbnail) + { + if (m_name.empty()) + throw WrongTypeException("Actor: name property must not be empty"); + } + + SActorInfo Actor::ToActorInfo() const + { + SActorInfo actorInfo; + actorInfo.strName = m_name; + actorInfo.strRole = m_role; + actorInfo.order = m_order; + actorInfo.thumbUrl = CScraperUrl(m_thumbnail); + if (!actorInfo.thumbUrl.GetFirstThumbUrl().empty()) + actorInfo.thumb = CScraperUrl::GetThumbUrl(actorInfo.thumbUrl.GetFirstUrlByType()); + + return actorInfo; + } + + VideoStreamDetail::VideoStreamDetail(int width /* = 0 */, + int height /* = 0 */, + float aspect /* = 0.0f */, + int duration /* = 0 */, + const String& codec /* = emptyString */, + const String& stereoMode /* = emptyString */, + const String& language /* = emptyString */, + const String& hdrType /* = emptyString */) + : m_width(width), + m_height(height), + m_aspect(aspect), + m_duration(duration), + m_codec(codec), + m_stereoMode(stereoMode), + m_language(language), + m_hdrType(hdrType) + { + } + + CStreamDetailVideo* VideoStreamDetail::ToStreamDetailVideo() const + { + auto streamDetail = new CStreamDetailVideo(); + streamDetail->m_iWidth = m_width; + streamDetail->m_iHeight = m_height; + streamDetail->m_fAspect = m_aspect; + streamDetail->m_iDuration = m_duration; + streamDetail->m_strCodec = m_codec; + streamDetail->m_strStereoMode = m_stereoMode; + streamDetail->m_strLanguage = m_language; + streamDetail->m_strHdrType = m_hdrType; + + return streamDetail; + } + + AudioStreamDetail::AudioStreamDetail(int channels /* = -1 */, + const String& codec /* = emptyString */, + const String& language /* = emptyString */) + : m_channels(channels), m_codec(codec), m_language(language) + { + } + + CStreamDetailAudio* AudioStreamDetail::ToStreamDetailAudio() const + { + auto streamDetail = new CStreamDetailAudio(); + streamDetail->m_iChannels = m_channels; + streamDetail->m_strCodec = m_codec; + streamDetail->m_strLanguage = m_language; + + return streamDetail; + } + + SubtitleStreamDetail::SubtitleStreamDetail(const String& language /* = emptyString */) + : m_language(language) + { + } + + CStreamDetailSubtitle* SubtitleStreamDetail::ToStreamDetailSubtitle() const + { + auto streamDetail = new CStreamDetailSubtitle(); + streamDetail->m_strLanguage = m_language; + + return streamDetail; + } + + InfoTagVideo::InfoTagVideo(bool offscreen /* = false */) + : infoTag(new CVideoInfoTag), offscreen(offscreen), owned(true) + { + } + + InfoTagVideo::InfoTagVideo(const CVideoInfoTag* tag) + : infoTag(new CVideoInfoTag(*tag)), offscreen(true), owned(true) + { + } + + InfoTagVideo::InfoTagVideo(CVideoInfoTag* tag, bool offscreen /* = false */) + : infoTag(tag), offscreen(offscreen), owned(false) + { + } + + InfoTagVideo::~InfoTagVideo() + { + if (owned) + delete infoTag; + } + + int InfoTagVideo::getDbId() + { + return infoTag->m_iDbId; + } + + String InfoTagVideo::getDirector() + { + return StringUtils::Join(infoTag->m_director, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + } + + std::vector<String> InfoTagVideo::getDirectors() + { + return infoTag->m_director; + } + + String InfoTagVideo::getWritingCredits() + { + return StringUtils::Join(infoTag->m_writingCredits, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + } + + std::vector<String> InfoTagVideo::getWriters() + { + return infoTag->m_writingCredits; + } + + String InfoTagVideo::getGenre() + { + return StringUtils::Join(infoTag->m_genre, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + } + + std::vector<String> InfoTagVideo::getGenres() + { + return infoTag->m_genre; + } + + String InfoTagVideo::getTagLine() + { + return infoTag->m_strTagLine; + } + + String InfoTagVideo::getPlotOutline() + { + return infoTag->m_strPlotOutline; + } + + String InfoTagVideo::getPlot() + { + return infoTag->m_strPlot; + } + + String InfoTagVideo::getPictureURL() + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + infoTag->m_strPictureURL.Parse(); + return infoTag->m_strPictureURL.GetFirstThumbUrl(); + } + + String InfoTagVideo::getTVShowTitle() + { + return infoTag->m_strShowTitle; + } + + String InfoTagVideo::getTitle() + { + return infoTag->m_strTitle; + } + + String InfoTagVideo::getMediaType() + { + return infoTag->m_type; + } + + String InfoTagVideo::getVotes() + { + CLog::Log( + LOGWARNING, + "InfoTagVideo.getVotes() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.getVotesAsInt()."); + + return std::to_string(getVotesAsInt()); + } + + int InfoTagVideo::getVotesAsInt(const String& type /* = "" */) + { + return infoTag->GetRating(type).votes; + } + + String InfoTagVideo::getCast() + { + return infoTag->GetCast(true); + } + + std::vector<Actor*> InfoTagVideo::getActors() + { + std::vector<Actor*> actors; + actors.reserve(infoTag->m_cast.size()); + + for (const auto& cast : infoTag->m_cast) + actors.push_back(new Actor(cast.strName, cast.strRole, cast.order, cast.thumbUrl.GetFirstUrlByType().m_url)); + + return actors; + } + + String InfoTagVideo::getFile() + { + return infoTag->m_strFile; + } + + String InfoTagVideo::getPath() + { + return infoTag->m_strPath; + } + + String InfoTagVideo::getFilenameAndPath() + { + return infoTag->m_strFileNameAndPath; + } + + String InfoTagVideo::getIMDBNumber() + { + return infoTag->GetUniqueID(); + } + + int InfoTagVideo::getSeason() + { + return infoTag->m_iSeason; + } + + int InfoTagVideo::getEpisode() + { + return infoTag->m_iEpisode; + } + + int InfoTagVideo::getYear() + { + return infoTag->GetYear(); + } + + double InfoTagVideo::getRating(const String& type /* = "" */) + { + return static_cast<double>(infoTag->GetRating(type).rating); + } + + int InfoTagVideo::getUserRating() + { + return infoTag->m_iUserRating; + } + + int InfoTagVideo::getPlayCount() + { + return infoTag->GetPlayCount(); + } + + String InfoTagVideo::getLastPlayed() + { + CLog::Log(LOGWARNING, "InfoTagVideo.getLastPlayed() is deprecated and might be removed in " + "future Kodi versions. Please use InfoTagVideo.getLastPlayedAsW3C()."); + + return infoTag->m_lastPlayed.GetAsLocalizedDateTime(); + } + + String InfoTagVideo::getLastPlayedAsW3C() + { + return infoTag->m_lastPlayed.GetAsW3CDateTime(); + } + + String InfoTagVideo::getOriginalTitle() + { + return infoTag->m_strOriginalTitle; + } + + String InfoTagVideo::getPremiered() + { + CLog::Log(LOGWARNING, "InfoTagVideo.getPremiered() is deprecated and might be removed in " + "future Kodi versions. Please use InfoTagVideo.getPremieredAsW3C()."); + + return infoTag->GetPremiered().GetAsLocalizedDate(); + } + + String InfoTagVideo::getPremieredAsW3C() + { + return infoTag->GetPremiered().GetAsW3CDate(); + } + + String InfoTagVideo::getFirstAired() + { + CLog::Log(LOGWARNING, "InfoTagVideo.getFirstAired() is deprecated and might be removed in " + "future Kodi versions. Please use InfoTagVideo.getFirstAiredAsW3C()."); + + return infoTag->m_firstAired.GetAsLocalizedDate(); + } + + String InfoTagVideo::getFirstAiredAsW3C() + { + return infoTag->m_firstAired.GetAsW3CDate(); + } + + String InfoTagVideo::getTrailer() + { + return infoTag->m_strTrailer; + } + + std::vector<std::string> InfoTagVideo::getArtist() + { + return infoTag->m_artist; + } + + String InfoTagVideo::getAlbum() + { + return infoTag->m_strAlbum; + } + + int InfoTagVideo::getTrack() + { + return infoTag->m_iTrack; + } + + unsigned int InfoTagVideo::getDuration() + { + return infoTag->GetDuration(); + } + + double InfoTagVideo::getResumeTime() + { + return infoTag->GetResumePoint().timeInSeconds; + } + + double InfoTagVideo::getResumeTimeTotal() + { + return infoTag->GetResumePoint().totalTimeInSeconds; + } + + String InfoTagVideo::getUniqueID(const char* key) + { + return infoTag->GetUniqueID(key); + } + + void InfoTagVideo::setUniqueID(const String& uniqueID, + const String& type /* = "" */, + bool isDefault /* = false */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setUniqueIDRaw(infoTag, uniqueID, type, isDefault); + } + + void InfoTagVideo::setUniqueIDs(const std::map<String, String>& uniqueIDs, + const String& defaultUniqueID /* = "" */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setUniqueIDsRaw(infoTag, uniqueIDs, defaultUniqueID); + } + + void InfoTagVideo::setDbId(int dbId) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDbIdRaw(infoTag, dbId); + } + + void InfoTagVideo::setYear(int year) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setYearRaw(infoTag, year); + } + + void InfoTagVideo::setEpisode(int episode) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setEpisodeRaw(infoTag, episode); + } + + void InfoTagVideo::setSeason(int season) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSeasonRaw(infoTag, season); + } + + void InfoTagVideo::setSortEpisode(int sortEpisode) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSortEpisodeRaw(infoTag, sortEpisode); + } + + void InfoTagVideo::setSortSeason(int sortSeason) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSortSeasonRaw(infoTag, sortSeason); + } + + void InfoTagVideo::setEpisodeGuide(const String& episodeGuide) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setEpisodeGuideRaw(infoTag, episodeGuide); + } + + void InfoTagVideo::setTop250(int top250) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTop250Raw(infoTag, top250); + } + + void InfoTagVideo::setSetId(int setId) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSetIdRaw(infoTag, setId); + } + + void InfoTagVideo::setTrackNumber(int trackNumber) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTrackNumberRaw(infoTag, trackNumber); + } + + void InfoTagVideo::setRating(float rating, + int votes /* = 0 */, + const String& type /* = "" */, + bool isDefault /* = false */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setRatingRaw(infoTag, rating, votes, type, isDefault); + } + + void InfoTagVideo::setRatings(const std::map<String, Tuple<float, int>>& ratings, + const String& defaultRating /* = "" */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setRatingsRaw(infoTag, ratings, defaultRating); + } + + void InfoTagVideo::setUserRating(int userRating) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setUserRatingRaw(infoTag, userRating); + } + + void InfoTagVideo::setPlaycount(int playcount) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPlaycountRaw(infoTag, playcount); + } + + void InfoTagVideo::setMpaa(const String& mpaa) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setMpaaRaw(infoTag, mpaa); + } + + void InfoTagVideo::setPlot(const String& plot) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPlotRaw(infoTag, plot); + } + + void InfoTagVideo::setPlotOutline(const String& plotOutline) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPlotOutlineRaw(infoTag, plotOutline); + } + + void InfoTagVideo::setTitle(const String& title) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTitleRaw(infoTag, title); + } + + void InfoTagVideo::setOriginalTitle(const String& originalTitle) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setOriginalTitleRaw(infoTag, originalTitle); + } + + void InfoTagVideo::setSortTitle(const String& sortTitle) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSortTitleRaw(infoTag, sortTitle); + } + + void InfoTagVideo::setTagLine(const String& tagLine) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTagLineRaw(infoTag, tagLine); + } + + void InfoTagVideo::setTvShowTitle(const String& tvshowTitle) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTvShowTitleRaw(infoTag, tvshowTitle); + } + + void InfoTagVideo::setTvShowStatus(const String& tvshowStatus) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTvShowStatusRaw(infoTag, tvshowStatus); + } + + void InfoTagVideo::setGenres(std::vector<String> genre) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setGenresRaw(infoTag, std::move(genre)); + } + + void InfoTagVideo::setCountries(std::vector<String> countries) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setCountriesRaw(infoTag, std::move(countries)); + } + + void InfoTagVideo::setDirectors(std::vector<String> directors) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDirectorsRaw(infoTag, std::move(directors)); + } + + void InfoTagVideo::setStudios(std::vector<String> studios) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setStudiosRaw(infoTag, std::move(studios)); + } + + void InfoTagVideo::setWriters(std::vector<String> writers) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setWritersRaw(infoTag, std::move(writers)); + } + + void InfoTagVideo::setDuration(int duration) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDurationRaw(infoTag, duration); + } + + void InfoTagVideo::setPremiered(const String& premiered) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPremieredRaw(infoTag, premiered); + } + + void InfoTagVideo::setSet(const String& set) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSetRaw(infoTag, set); + } + + void InfoTagVideo::setSetOverview(const String& setOverview) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setSetOverviewRaw(infoTag, setOverview); + } + + void InfoTagVideo::setTags(std::vector<String> tags) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTagsRaw(infoTag, std::move(tags)); + } + + void InfoTagVideo::setProductionCode(const String& productionCode) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setProductionCodeRaw(infoTag, productionCode); + } + + void InfoTagVideo::setFirstAired(const String& firstAired) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setFirstAiredRaw(infoTag, firstAired); + } + + void InfoTagVideo::setLastPlayed(const String& lastPlayed) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setLastPlayedRaw(infoTag, lastPlayed); + } + + void InfoTagVideo::setAlbum(const String& album) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setAlbumRaw(infoTag, album); + } + + void InfoTagVideo::setVotes(int votes) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setVotesRaw(infoTag, votes); + } + + void InfoTagVideo::setTrailer(const String& trailer) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setTrailerRaw(infoTag, trailer); + } + + void InfoTagVideo::setPath(const String& path) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setPathRaw(infoTag, path); + } + + void InfoTagVideo::setFilenameAndPath(const String& filenameAndPath) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setFilenameAndPathRaw(infoTag, filenameAndPath); + } + + void InfoTagVideo::setIMDBNumber(const String& imdbNumber) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setIMDBNumberRaw(infoTag, imdbNumber); + } + + void InfoTagVideo::setDateAdded(const String& dateAdded) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setDateAddedRaw(infoTag, dateAdded); + } + + void InfoTagVideo::setMediaType(const String& mediaType) + { + setMediaTypeRaw(infoTag, mediaType); + } + + void InfoTagVideo::setShowLinks(std::vector<String> showLinks) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setShowLinksRaw(infoTag, std::move(showLinks)); + } + + void InfoTagVideo::setArtists(std::vector<String> artists) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setArtistsRaw(infoTag, std::move(artists)); + } + + void InfoTagVideo::setCast(const std::vector<const Actor*>& actors) + { + std::vector<SActorInfo> cast; + cast.reserve(actors.size()); + for (const auto& actor : actors) + cast.push_back(actor->ToActorInfo()); + + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setCastRaw(infoTag, std::move(cast)); + } + } + + void InfoTagVideo::setResumePoint(double time, double totalTime /* = 0.0 */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + setResumePointRaw(infoTag, time, totalTime); + } + + void InfoTagVideo::addSeason(int number, std::string name /* = "" */) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addSeasonRaw(infoTag, number, std::move(name)); + } + + void InfoTagVideo::addSeasons(const std::vector<Tuple<int, std::string>>& namedSeasons) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addSeasonsRaw(infoTag, namedSeasons); + } + + void InfoTagVideo::addVideoStream(const VideoStreamDetail* stream) + { + if (stream == nullptr) + return; + + auto streamDetail = stream->ToStreamDetailVideo(); + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addStreamRaw(infoTag, streamDetail); + } + } + + void InfoTagVideo::addAudioStream(const AudioStreamDetail* stream) + { + if (stream == nullptr) + return; + + auto streamDetail = stream->ToStreamDetailAudio(); + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addStreamRaw(infoTag, streamDetail); + } + } + + void InfoTagVideo::addSubtitleStream(const SubtitleStreamDetail* stream) + { + if (stream == nullptr) + return; + + auto streamDetail = stream->ToStreamDetailSubtitle(); + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addStreamRaw(infoTag, streamDetail); + } + } + + void InfoTagVideo::addAvailableArtwork(const std::string& url, + const std::string& art_type, + const std::string& preview, + const std::string& referrer, + const std::string& cache, + bool post, + bool isgz, + int season) + { + XBMCAddonUtils::GuiLock lock(languageHook, offscreen); + addAvailableArtworkRaw(infoTag, url, art_type, preview, referrer, cache, post, isgz, season); + } + + void InfoTagVideo::setDbIdRaw(CVideoInfoTag* infoTag, int dbId) + { + infoTag->m_iDbId = dbId; + } + + void InfoTagVideo::setUniqueIDRaw(CVideoInfoTag* infoTag, + const String& uniqueID, + const String& type /* = "" */, + bool isDefault /* = false */) + { + infoTag->SetUniqueID(uniqueID, type, isDefault); + } + + void InfoTagVideo::setUniqueIDsRaw(CVideoInfoTag* infoTag, + std::map<String, String> uniqueIDs, + const String& defaultUniqueID /* = "" */) + { + infoTag->SetUniqueIDs(uniqueIDs); + auto defaultUniqueIDEntry = uniqueIDs.find(defaultUniqueID); + if (defaultUniqueIDEntry != uniqueIDs.end()) + infoTag->SetUniqueID(defaultUniqueIDEntry->second, defaultUniqueIDEntry->first, true); + } + + void InfoTagVideo::setYearRaw(CVideoInfoTag* infoTag, int year) + { + infoTag->SetYear(year); + } + + void InfoTagVideo::setEpisodeRaw(CVideoInfoTag* infoTag, int episode) + { + infoTag->m_iEpisode = episode; + } + + void InfoTagVideo::setSeasonRaw(CVideoInfoTag* infoTag, int season) + { + infoTag->m_iSeason = season; + } + + void InfoTagVideo::setSortEpisodeRaw(CVideoInfoTag* infoTag, int sortEpisode) + { + infoTag->m_iSpecialSortEpisode = sortEpisode; + } + + void InfoTagVideo::setSortSeasonRaw(CVideoInfoTag* infoTag, int sortSeason) + { + infoTag->m_iSpecialSortSeason = sortSeason; + } + + void InfoTagVideo::setEpisodeGuideRaw(CVideoInfoTag* infoTag, const String& episodeGuide) + { + infoTag->SetEpisodeGuide(episodeGuide); + } + + void InfoTagVideo::setTop250Raw(CVideoInfoTag* infoTag, int top250) + { + infoTag->m_iTop250 = top250; + } + + void InfoTagVideo::setSetIdRaw(CVideoInfoTag* infoTag, int setId) + { + infoTag->m_set.id = setId; + } + + void InfoTagVideo::setTrackNumberRaw(CVideoInfoTag* infoTag, int trackNumber) + { + infoTag->m_iTrack = trackNumber; + } + + void InfoTagVideo::setRatingRaw(CVideoInfoTag* infoTag, + float rating, + int votes /* = 0 */, + const std::string& type /* = "" */, + bool isDefault /* = false */) + { + infoTag->SetRating(rating, votes, type, isDefault); + } + + void InfoTagVideo::setRatingsRaw(CVideoInfoTag* infoTag, + const std::map<String, Tuple<float, int>>& ratings, + const String& defaultRating /* = "" */) + { + RatingMap ratingMap; + for (const auto& rating : ratings) + ratingMap.emplace(rating.first, CRating{rating.second.first(), rating.second.second()}); + + infoTag->SetRatings(std::move(ratingMap), defaultRating); + } + + void InfoTagVideo::setUserRatingRaw(CVideoInfoTag* infoTag, int userRating) + { + infoTag->m_iUserRating = userRating; + } + + void InfoTagVideo::setPlaycountRaw(CVideoInfoTag* infoTag, int playcount) + { + infoTag->SetPlayCount(playcount); + } + + void InfoTagVideo::setMpaaRaw(CVideoInfoTag* infoTag, const String& mpaa) + { + infoTag->SetMPAARating(mpaa); + } + + void InfoTagVideo::setPlotRaw(CVideoInfoTag* infoTag, const String& plot) + { + infoTag->SetPlot(plot); + } + + void InfoTagVideo::setPlotOutlineRaw(CVideoInfoTag* infoTag, const String& plotOutline) + { + infoTag->SetPlotOutline(plotOutline); + } + + void InfoTagVideo::setTitleRaw(CVideoInfoTag* infoTag, const String& title) + { + infoTag->SetTitle(title); + } + + void InfoTagVideo::setOriginalTitleRaw(CVideoInfoTag* infoTag, const String& originalTitle) + { + infoTag->SetOriginalTitle(originalTitle); + } + + void InfoTagVideo::setSortTitleRaw(CVideoInfoTag* infoTag, const String& sortTitle) + { + infoTag->SetSortTitle(sortTitle); + } + + void InfoTagVideo::setTagLineRaw(CVideoInfoTag* infoTag, const String& tagLine) + { + infoTag->SetTagLine(tagLine); + } + + void InfoTagVideo::setTvShowTitleRaw(CVideoInfoTag* infoTag, const String& tvshowTitle) + { + infoTag->SetShowTitle(tvshowTitle); + } + + void InfoTagVideo::setTvShowStatusRaw(CVideoInfoTag* infoTag, const String& tvshowStatus) + { + infoTag->SetStatus(tvshowStatus); + } + + void InfoTagVideo::setGenresRaw(CVideoInfoTag* infoTag, std::vector<String> genre) + { + infoTag->SetGenre(std::move(genre)); + } + + void InfoTagVideo::setCountriesRaw(CVideoInfoTag* infoTag, std::vector<String> countries) + { + infoTag->SetCountry(std::move(countries)); + } + + void InfoTagVideo::setDirectorsRaw(CVideoInfoTag* infoTag, std::vector<String> directors) + { + infoTag->SetDirector(std::move(directors)); + } + + void InfoTagVideo::setStudiosRaw(CVideoInfoTag* infoTag, std::vector<String> studios) + { + infoTag->SetStudio(std::move(studios)); + } + + void InfoTagVideo::setWritersRaw(CVideoInfoTag* infoTag, std::vector<String> writers) + { + infoTag->SetWritingCredits(std::move(writers)); + } + + void InfoTagVideo::setDurationRaw(CVideoInfoTag* infoTag, int duration) + { + infoTag->SetDuration(duration); + } + + void InfoTagVideo::setPremieredRaw(CVideoInfoTag* infoTag, const String& premiered) + { + CDateTime premieredDate; + premieredDate.SetFromDateString(premiered); + infoTag->SetPremiered(premieredDate); + } + + void InfoTagVideo::setSetRaw(CVideoInfoTag* infoTag, const String& set) + { + infoTag->SetSet(set); + } + + void InfoTagVideo::setSetOverviewRaw(CVideoInfoTag* infoTag, const String& setOverview) + { + infoTag->SetSetOverview(setOverview); + } + + void InfoTagVideo::setTagsRaw(CVideoInfoTag* infoTag, std::vector<String> tags) + { + infoTag->SetTags(std::move(tags)); + } + + void InfoTagVideo::setProductionCodeRaw(CVideoInfoTag* infoTag, const String& productionCode) + { + infoTag->SetProductionCode(productionCode); + } + + void InfoTagVideo::setFirstAiredRaw(CVideoInfoTag* infoTag, const String& firstAired) + { + CDateTime firstAiredDate; + firstAiredDate.SetFromDateString(firstAired); + infoTag->m_firstAired = firstAiredDate; + } + + void InfoTagVideo::setLastPlayedRaw(CVideoInfoTag* infoTag, const String& lastPlayed) + { + CDateTime lastPlayedDate; + lastPlayedDate.SetFromDBDateTime(lastPlayed); + infoTag->m_lastPlayed = lastPlayedDate; + } + + void InfoTagVideo::setAlbumRaw(CVideoInfoTag* infoTag, const String& album) + { + infoTag->SetAlbum(album); + } + + void InfoTagVideo::setVotesRaw(CVideoInfoTag* infoTag, int votes) + { + infoTag->SetVotes(votes); + } + + void InfoTagVideo::setTrailerRaw(CVideoInfoTag* infoTag, const String& trailer) + { + infoTag->SetTrailer(trailer); + } + + void InfoTagVideo::setPathRaw(CVideoInfoTag* infoTag, const String& path) + { + infoTag->SetPath(path); + } + + void InfoTagVideo::setFilenameAndPathRaw(CVideoInfoTag* infoTag, const String& filenameAndPath) + { + infoTag->SetFileNameAndPath(filenameAndPath); + } + + void InfoTagVideo::setIMDBNumberRaw(CVideoInfoTag* infoTag, const String& imdbNumber) + { + infoTag->SetUniqueID(imdbNumber); + } + + void InfoTagVideo::setDateAddedRaw(CVideoInfoTag* infoTag, const String& dateAdded) + { + CDateTime dateAddedDate; + dateAddedDate.SetFromDBDateTime(dateAdded); + infoTag->m_dateAdded = dateAddedDate; + } + + void InfoTagVideo::setMediaTypeRaw(CVideoInfoTag* infoTag, const String& mediaType) + { + if (CMediaTypes::IsValidMediaType(mediaType)) + infoTag->m_type = mediaType; + } + + void InfoTagVideo::setShowLinksRaw(CVideoInfoTag* infoTag, std::vector<String> showLinks) + { + infoTag->SetShowLink(std::move(showLinks)); + } + + void InfoTagVideo::setArtistsRaw(CVideoInfoTag* infoTag, std::vector<String> artists) + { + infoTag->m_artist = std::move(artists); + } + + void InfoTagVideo::setCastRaw(CVideoInfoTag* infoTag, std::vector<SActorInfo> cast) + { + infoTag->m_cast = std::move(cast); + } + + void InfoTagVideo::setResumePointRaw(CVideoInfoTag* infoTag, + double time, + double totalTime /* = 0.0 */) + { + auto resumePoint = infoTag->GetResumePoint(); + resumePoint.timeInSeconds = time; + if (totalTime > 0.0) + resumePoint.totalTimeInSeconds = totalTime; + infoTag->SetResumePoint(resumePoint); + } + + void InfoTagVideo::addSeasonRaw(CVideoInfoTag* infoTag, int number, std::string name /* = "" */) + { + infoTag->m_namedSeasons[number] = std::move(name); + } + + void InfoTagVideo::addSeasonsRaw(CVideoInfoTag* infoTag, + const std::vector<Tuple<int, std::string>>& namedSeasons) + { + for (const auto& season : namedSeasons) + addSeasonRaw(infoTag, season.first(), season.second()); + } + + void InfoTagVideo::addStreamRaw(CVideoInfoTag* infoTag, CStreamDetail* stream) + { + infoTag->m_streamDetails.AddStream(stream); + } + + void InfoTagVideo::finalizeStreamsRaw(CVideoInfoTag* infoTag) + { + infoTag->m_streamDetails.DetermineBestStreams(); + } + + void InfoTagVideo::addAvailableArtworkRaw(CVideoInfoTag* infoTag, + const std::string& url, + const std::string& art_type, + const std::string& preview, + const std::string& referrer, + const std::string& cache, + bool post, + bool isgz, + int season) + { + infoTag->m_strPictureURL.AddParsedUrl(url, art_type, preview, referrer, cache, post, isgz, + season); + } + } +} diff --git a/xbmc/interfaces/legacy/InfoTagVideo.h b/xbmc/interfaces/legacy/InfoTagVideo.h new file mode 100644 index 0000000..91f925e --- /dev/null +++ b/xbmc/interfaces/legacy/InfoTagVideo.h @@ -0,0 +1,2772 @@ +/* + * 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 "AddonClass.h" +#include "Tuple.h" +#include "utils/StreamDetails.h" +#include "video/VideoInfoTag.h" + +namespace XBMCAddon +{ + namespace xbmc + { + /// + /// \defgroup python_xbmc_actor Actor + /// \ingroup python_xbmc + /// @{ + /// @brief **Actor class used in combination with InfoTagVideo.** + /// + /// \python_class{ xbmc.Actor([name, role, order, thumbnail]) } + /// + /// Represents a single actor in the cast of a video item wrapped by InfoTagVideo. + /// + /// + ///------------------------------------------------------------------------- + /// @python_v20 New class added. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// actor = xbmc.Actor('Sean Connery', 'James Bond', order=1) + /// ... + /// ~~~~~~~~~~~~~ + /// + class Actor : public AddonClass + { + public: +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor Actor + /// @brief \python_func{ xbmc.Actor([name, role, order, thumbnail]) } + /// Creates a single actor for the cast of a video item wrapped by InfoTagVideo. + /// + /// @param name [opt] string - Name of the actor. + /// @param role [opt] string - Role of the actor in the specific video item. + /// @param order [opt] integer - Order of the actor in the cast of the specific video item. + /// @param thumbnail [opt] string - Path / URL to the thumbnail of the actor. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// actor = xbmc.Actor('Sean Connery', 'James Bond', order=1) + /// ... + /// ~~~~~~~~~~~~~ + /// + Actor(...); +#else + explicit Actor(const String& name = emptyString, + const String& role = emptyString, + int order = -1, + const String& thumbnail = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ getName() } + /// Get the name of the actor. + /// + /// @return [string] Name of the actor + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getName(); +#else + String getName() const { return m_name; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ getRole() } + /// Get the role of the actor in the specific video item. + /// + /// @return [string] Role of the actor in the specific video item + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getRole(); +#else + String getRole() const { return m_role; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ getOrder() } + /// Get the order of the actor in the cast of the specific video item. + /// + /// @return [integer] Order of the actor in the cast of the specific video item + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getOrder(); +#else + int getOrder() const { return m_order; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ getThumbnail() } + /// Get the path / URL to the thumbnail of the actor. + /// + /// @return [string] Path / URL to the thumbnail of the actor + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getThumbnail(); +#else + String getThumbnail() const { return m_thumbnail; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ setName(name) } + /// Set the name of the actor. + /// + /// @param name string - Name of the actor. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setName(...); +#else + void setName(const String& name) { m_name = name; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ setRole(role) } + /// Set the role of the actor in the specific video item. + /// + /// @param role string - Role of the actor in the specific video item. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setRole(...); +#else + void setRole(const String& role) { m_role = role; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ setOrder(order) } + /// Set the order of the actor in the cast of the specific video item. + /// + /// @param order integer - Order of the actor in the cast of the specific video item. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setOrder(...); +#else + void setOrder(int order) { m_order = order; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_actor + /// @brief \python_func{ setThumbnail(thumbnail) } + /// Set the path / URL to the thumbnail of the actor. + /// + /// @param thumbnail string - Path / URL to the thumbnail of the actor. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setThumbnail(...); +#else + void setThumbnail(const String& thumbnail) { m_thumbnail = thumbnail; } +#endif + +#ifndef SWIG + SActorInfo ToActorInfo() const; +#endif + + private: + String m_name; + String m_role; + int m_order; + String m_thumbnail; + }; + /// @} + + /// + /// \defgroup python_xbmc_videostreamdetail VideoStreamDetail + /// \ingroup python_xbmc + /// @{ + /// @brief **Video stream details class used in combination with InfoTagVideo.** + /// + /// \python_class{ xbmc.VideoStreamDetail([width, height, aspect, duration, codec, stereoMode, language, hdrType]) } + /// + /// Represents a single selectable video stream for a video item wrapped by InfoTagVideo. + /// + /// + ///------------------------------------------------------------------------- + /// @python_v20 New class added. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// videostream = xbmc.VideoStreamDetail(1920, 1080, language='English') + /// ... + /// ~~~~~~~~~~~~~ + /// + class VideoStreamDetail : public AddonClass + { + public: +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ xbmc.VideoStreamDetail([width, height, aspect, duration, codec, stereomode, language, hdrtype]) } + /// Creates a single video stream details class for a video item wrapped by InfoTagVideo. + /// + /// @param width [opt] integer - Width of the video stream in pixel. + /// @param height [opt] integer - Height of the video stream in pixel. + /// @param aspect [opt] float - Aspect ratio of the video stream. + /// @param duration [opt] integer - Duration of the video stream in seconds. + /// @param codec [opt] string - Codec of the video stream. + /// @param stereomode [opt] string - Stereo mode of the video stream. + /// @param language [opt] string - Language of the video stream. + /// @param hdrtype [opt] string - HDR type of the video stream. + /// The following types are supported: + /// dolbyvision, hdr10, hlg + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// videostream = xbmc.VideoStreamDetail(1920, 1080, language='English') + /// ... + /// ~~~~~~~~~~~~~ + /// + VideoStreamDetail(...); +#else + explicit VideoStreamDetail(int width = 0, + int height = 0, + float aspect = 0.0f, + int duration = 0, + const String& codec = emptyString, + const String& stereomode = emptyString, + const String& language = emptyString, + const String& hdrtype = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getWidth() } + /// Get the width of the video stream in pixel. + /// + /// @return [integer] Width of the video stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getWidth(); +#else + int getWidth() const { return m_width; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getHeight() } + /// Get the height of the video stream in pixel. + /// + /// @return [integer] Height of the video stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getHeight(); +#else + int getHeight() const { return m_height; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getAspect() } + /// Get the aspect ratio of the video stream. + /// + /// @return [float] Aspect ratio of the video stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getAspect(); +#else + float getAspect() const { return m_aspect; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getDuration() } + /// Get the duration of the video stream in seconds. + /// + /// @return [float] Duration of the video stream in seconds + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getDuration(); +#else + int getDuration() const { return m_duration; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getCodec() } + /// Get the codec of the stream. + /// + /// @return [string] Codec of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getCodec(); +#else + String getCodec() const { return m_codec; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getStereoMode() } + /// Get the stereo mode of the video stream. + /// + /// @return [string] Stereo mode of the video stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getStereoMode(); +#else + String getStereoMode() const { return m_stereoMode; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getLanguage() } + /// Get the language of the stream. + /// + /// @return [string] Language of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getLanguage(); +#else + String getLanguage() const { return m_language; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ getHDRType() } + /// Get the HDR type of the stream. + /// + /// @return [string] HDR type of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getHDRType(); +#else + String getHDRType() const { return m_hdrType; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setWidth(width) } + /// Set the width of the video stream in pixel. + /// + /// @param width integer - Width of the video stream in pixel. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setWidth(...); +#else + void setWidth(int width) { m_width = width; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setHeight(height) } + /// Set the height of the video stream in pixel. + /// + /// @param height integer - Height of the video stream in pixel. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setHeight(...); +#else + void setHeight(int height) { m_height = height; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setAspect(aspect) } + /// Set the aspect ratio of the video stream. + /// + /// @param aspect float - Aspect ratio of the video stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setAspect(...); +#else + void setAspect(float aspect) { m_aspect = aspect; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setDuration(duration) } + /// Set the duration of the video stream in seconds. + /// + /// @param duration integer - Duration of the video stream in seconds. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDuration(...); +#else + void setDuration(int duration) { m_duration = duration; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setCodec(codec) } + /// Set the codec of the stream. + /// + /// @param codec string - Codec of the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setCodec(...); +#else + void setCodec(const String& codec) { m_codec = codec; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setStereoMode(stereomode) } + /// Set the stereo mode of the video stream. + /// + /// @param stereomode string - Stereo mode of the video stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setStereoMode(...); +#else + void setStereoMode(const String& stereomode) { m_stereoMode = stereomode; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setLanguage(language) } + /// Set the language of the stream. + /// + /// @param language string - Language of the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLanguage(...); +#else + void setLanguage(const String& language) { m_language = language; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_videostreamdetail + /// @brief \python_func{ setHDRType(hdrtype) } + /// Set the HDR type of the stream. + /// + /// @param hdrtype string - HDR type of the stream. + /// The following types are supported: + /// dolbyvision, hdr10, hlg + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setHDRType(...); +#else + void setHDRType(const String& hdrtype) { m_hdrType = hdrtype; } +#endif + +#ifndef SWIG + CStreamDetailVideo* ToStreamDetailVideo() const; +#endif + + private: + int m_width; + int m_height; + float m_aspect; + int m_duration; + String m_codec; + String m_stereoMode; + String m_language; + String m_hdrType; + }; + /// @} + + /// + /// \defgroup python_xbmc_audiostreamdetail AudioStreamDetail + /// \ingroup python_xbmc + /// @{ + /// @brief **Audio stream details class used in combination with InfoTagVideo.** + /// + /// \python_class{ xbmc.AudioStreamDetail([channels, codec, language]) } + /// + /// Represents a single selectable audio stream for a video item wrapped by InfoTagVideo. + /// + /// + ///------------------------------------------------------------------------- + /// @python_v20 New class added. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// audiostream = xbmc.AudioStreamDetail(6, 'DTS', 'English') + /// ... + /// ~~~~~~~~~~~~~ + /// + class AudioStreamDetail : public AddonClass + { + public: +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail AudioStreamDetail + /// @brief \python_func{ xbmc.AudioStreamDetail([channels, codec, language]) } + /// Creates a single audio stream details class for a video item wrapped by InfoTagVideo. + /// + /// @param channels [opt] integer - Number of channels in the audio stream. + /// @param codec [opt] string - Codec of the audio stream. + /// @param language [opt] string - Language of the audio stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// audiostream = xbmc.AudioStreamDetail(6, 'DTS', 'English') + /// ... + /// ~~~~~~~~~~~~~ + /// + AudioStreamDetail(...); +#else + explicit AudioStreamDetail(int channels = -1, + const String& codec = emptyString, + const String& language = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ getChannels() } + /// Get the number of channels in the stream. + /// + /// @return [integer] Number of channels in the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getChannels(); +#else + int getChannels() const { return m_channels; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ getCodec() } + /// Get the codec of the stream. + /// + /// @return [string] Codec of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getCodec(); +#else + String getCodec() const { return m_codec; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ getLanguage() } + /// Get the language of the stream. + /// + /// @return [string] Language of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getLanguage(); +#else + String getLanguage() const { return m_language; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ setChannels(channels) } + /// Set the number of channels in the stream. + /// + /// @param channels integer - Number of channels in the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setChannels(...); +#else + void setChannels(int channels) { m_channels = channels; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ setCodec(codec) } + /// Set the codec of the stream. + /// + /// @param codec string - Codec of the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setCodec(...); +#else + void setCodec(const String& codec) { m_codec = codec; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_audiostreamdetail + /// @brief \python_func{ setLanguage(language) } + /// Set the language of the stream. + /// + /// @param language string - Language of the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLanguage(...); +#else + void setLanguage(const String& language) { m_language = language; } +#endif + +#ifndef SWIG + CStreamDetailAudio* ToStreamDetailAudio() const; +#endif + + private: + int m_channels; + String m_codec; + String m_language; + }; + /// @} + + /// + /// \defgroup python_xbmc_subtitlestreamdetail SubtitleStreamDetail + /// \ingroup python_xbmc + /// @{ + /// @brief **Subtitle stream details class used in combination with InfoTagVideo.** + /// + /// \python_class{ xbmc.SubtitleStreamDetail([language]) } + /// + /// Represents a single selectable subtitle stream for a video item wrapped by InfoTagVideo. + /// + /// + ///------------------------------------------------------------------------- + /// @python_v20 New class added. + /// + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// subtitlestream = xbmc.SubtitleStreamDetail('English') + /// ... + /// ~~~~~~~~~~~~~ + /// + class SubtitleStreamDetail : public AddonClass + { + public: +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_subtitlestreamdetail SubtitleStreamDetail + /// @brief \python_func{ xbmc.SubtitleStreamDetail([language]) } + /// Creates a single subtitle stream details class for a video item wrapped by InfoTagVideo. + /// + /// @param language [opt] string - Language of the subtitle. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// subtitlestream = xbmc.SubtitleStreamDetail('English') + /// ... + /// ~~~~~~~~~~~~~ + /// + SubtitleStreamDetail(...); +#else + explicit SubtitleStreamDetail(const String& language = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_subtitlestreamdetail + /// @brief \python_func{ getLanguage() } + /// Get the language of the stream. + /// + /// @return [string] Language of the stream + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getLanguage(); +#else + String getLanguage() const { return m_language; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_subtitlestreamdetail + /// @brief \python_func{ setLanguage(language) } + /// Set the language of the stream. + /// + /// @param language string - Language of the stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLanguage(...); +#else + void setLanguage(const String& language) { m_language = language; } +#endif + +#ifndef SWIG + CStreamDetailSubtitle* ToStreamDetailSubtitle() const; +#endif + + private: + String m_language; + }; + /// @} + + /// + /// \defgroup python_InfoTagVideo InfoTagVideo + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's video info tag class.** + /// + /// \python_class{ xbmc.InfoTagVideo([offscreen]) } + /// + /// Access and / or modify the video metadata of a ListItem. + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// tag = xbmc.Player().getVideoInfoTag() + /// + /// title = tag.getTitle() + /// file = tag.getFile() + /// ... + /// ~~~~~~~~~~~~~ + /// + class InfoTagVideo : public AddonClass + { + private: + CVideoInfoTag* infoTag; + bool offscreen; + bool owned; + + public: +#ifndef SWIG + explicit InfoTagVideo(const CVideoInfoTag* tag); + explicit InfoTagVideo(CVideoInfoTag* tag, bool offscreen = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ xbmc.InfoTagVideo([offscreen]) } + /// Create a video info tag. + /// + /// @param offscreen [opt] bool (default `False`) - if GUI based locks should be + /// avoided. Most of the times listitems are created + /// offscreen and added later to a container + /// for display (e.g. plugins) or they are not + /// even displayed (e.g. python scrapers). + /// In such cases, there is no need to lock the + /// GUI when creating the items (increasing your addon + /// performance). + /// Note however, that if you are creating listitems + /// and managing the container itself (e.g using + /// WindowXML or WindowXMLDialog classes) subsquent + /// modifications to the item will require locking. + /// Thus, in such cases, use the default value (`False`). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Added **offscreen** argument. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// videoinfo = xbmc.InfoTagVideo(offscreen=False) + /// ... + /// ~~~~~~~~~~~~~ + /// + InfoTagVideo(...); +#else + explicit InfoTagVideo(bool offscreen = false); +#endif + ~InfoTagVideo() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getDbId() } + /// Get identification number of tag in database + /// + /// @return [integer] database id + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getDbId(); +#else + int getDbId(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getDirector() } + /// Get [film director](https://en.wikipedia.org/wiki/Film_director) + /// who has made the film (if present). + /// + /// @return [string] Film director name. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getDirectors()** instead. + /// + getDirector(); +#else + String getDirector(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getDirectors() } + /// Get a list of [film directors](https://en.wikipedia.org/wiki/Film_director) + /// who have made the film (if present). + /// + /// @return [list] List of film director names. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getDirectors(); +#else + std::vector<String> getDirectors(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getWritingCredits() } + /// Get the writing credits if present from video info tag. + /// + /// @return [string] Writing credits + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getWriters()** instead. + /// + getWritingCredits(); +#else + String getWritingCredits(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getWriters() } + /// Get the list of writers (if present) from video info tag. + /// + /// @return [list] List of writers + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getWriters(); +#else + std::vector<String> getWriters(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getGenre() } + /// To get the [Video Genre](https://en.wikipedia.org/wiki/Film_genre) + /// if available. + /// + /// @return [string] Genre name + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getGenres()** instead. + /// + getGenre(); +#else + String getGenre(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getGenres() } + /// Get the list of [Video Genres](https://en.wikipedia.org/wiki/Film_genre) + /// if available. + /// + /// @return [list] List of genres + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getGenres(); +#else + std::vector<String> getGenres(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getTagLine() } + /// Get video tag line if available. + /// + /// @return [string] Video tag line + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getTagLine(); +#else + String getTagLine(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPlotOutline() } + /// Get the outline plot of the video if present. + /// + /// @return [string] Outline plot + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPlotOutline(); +#else + String getPlotOutline(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPlot() } + /// Get the plot of the video if present. + /// + /// @return [string] Plot + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPlot(); +#else + String getPlot(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPictureURL() } + /// Get a picture URL of the video to show as screenshot. + /// + /// @return [string] Picture URL + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPictureURL(); +#else + String getPictureURL(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getTitle() } + /// Get the video title. + /// + /// @return [string] Video title + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getTitle(); +#else + String getTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getTVShowTitle() } + /// Get the video TV show title. + /// + /// @return [string] TV show title + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getTVShowTitle(); +#else + String getTVShowTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getMediaType() } + /// Get the media type of the video. + /// + /// @return [string] media type + /// + /// Available strings about media type for video: + /// | String | Description | + /// |---------------:|:--------------------------------------------------| + /// | video | For normal video + /// | set | For a selection of video + /// | musicvideo | To define it as music video + /// | movie | To define it as normal movie + /// | tvshow | If this is it defined as tvshow + /// | season | The type is used as a series season + /// | episode | The type is used as a series episode + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getMediaType(); +#else + String getMediaType(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getVotes() } + /// Get the video votes if available from video info tag. + /// + /// @return [string] Votes + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getVotesAsInt()** instead. + /// + getVotes(); +#else + String getVotes(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getVotesAsInt([type]) } + /// Get the votes of the rating (if available) as an integer. + /// + /// @param type [opt] string - the type of the rating. + /// - Some rating type values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - type name + /// | tvdb | string - type name + /// | tmdb | string - type name + /// | anidb | string - type name + /// + /// @return [integer] Votes + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getVotesAsInt(type); +#else + int getVotesAsInt(const String& type = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getCast() } + /// To get the cast of the video when available. + /// + /// @return [string] Video casts + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getActors()** instead. + /// + getCast(); +#else + String getCast(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getActors() } + /// Get the cast of the video if available. + /// + /// @return [list] List of actors + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getActors(); +#else + std::vector<Actor*> getActors(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getFile() } + /// To get the video file name. + /// + /// @return [string] File name + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getFile(); +#else + String getFile(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPath() } + /// To get the path where the video is stored. + /// + /// @return [string] Path + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPath(); +#else + String getPath(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getFilenameAndPath() } + /// To get the full path with filename where the video is stored. + /// + /// @return [string] File name and Path + /// + /// + ///----------------------------------------------------------------------- + /// @python_v19 New function added. + /// + getFilenameAndPath(); +#else + String getFilenameAndPath(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getIMDBNumber() } + /// To get the [IMDb](https://en.wikipedia.org/wiki/Internet_Movie_Database) + /// number of the video (if present). + /// + /// @return [string] IMDb number + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getIMDBNumber(); +#else + String getIMDBNumber(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getSeason() } + /// To get season number of a series + /// + /// @return [integer] season number + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getSeason(); +#else + int getSeason(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getEpisode() } + /// To get episode number of a series + /// + /// @return [integer] episode number + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getEpisode(); +#else + int getEpisode(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getYear() } + /// Get production year of video if present. + /// + /// @return [integer] Production Year + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getYear(); +#else + int getYear(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getRating([type]) } + /// Get the video rating if present as float (double where supported). + /// + /// @param type [opt] string - the type of the rating. + /// - Some rating type values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - type name + /// | tvdb | string - type name + /// | tmdb | string - type name + /// | anidb | string - type name + /// + /// @return [float] The rating of the video + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Optional `type` parameter added. + /// + getRating(type); +#else + double getRating(const String& type = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getUserRating() } + /// Get the user rating if present as integer. + /// + /// @return [integer] The user rating of the video + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getUserRating(); +#else + int getUserRating(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPlayCount() } + /// To get the number of plays of the video. + /// + /// @return [integer] Play Count + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getPlayCount(); +#else + int getPlayCount(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getLastPlayed() } + /// Get the last played date / time as string. + /// + /// @return [string] Last played date / time + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getLastPlayedAsW3C()** instead. + /// + getLastPlayed(); +#else + String getLastPlayed(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getLastPlayedAsW3C() } + /// Get last played datetime as string in W3C format (YYYY-MM-DDThh:mm:ssTZD). + /// + /// @return [string] Last played datetime (W3C) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getLastPlayedAsW3C(); +#else + String getLastPlayedAsW3C(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getOriginalTitle() } + /// To get the original title of the video. + /// + /// @return [string] Original title + /// + /// + ///----------------------------------------------------------------------- + /// + /// + getOriginalTitle(); +#else + String getOriginalTitle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPremiered() } + /// To get [premiered](https://en.wikipedia.org/wiki/Premiere) date + /// of the video, if available. + /// + /// @return [string] + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getPremieredAsW3C()** instead. + /// + getPremiered(); +#else + String getPremiered(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getPremieredAsW3C() } + /// Get [premiered](https://en.wikipedia.org/wiki/Premiere) date as string in W3C format (YYYY-MM-DD). + /// + /// @return [string] Premiered date (W3C) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getPremieredAsW3C(); +#else + String getPremieredAsW3C(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getFirstAired() } + /// Returns first aired date as string from info tag. + /// + /// @return [string] First aired date + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **getFirstAiredAsW3C()** instead. + /// + getFirstAired(); +#else + String getFirstAired(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getFirstAiredAsW3C() } + /// Get first aired date as string in W3C format (YYYY-MM-DD). + /// + /// @return [string] First aired date (W3C) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getFirstAiredAsW3C(); +#else + String getFirstAiredAsW3C(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getTrailer() } + /// To get the path where the trailer is stored. + /// + /// @return [string] Trailer path + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + getTrailer(); +#else + String getTrailer(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getArtist() } + /// To get the artist name (for musicvideos) + /// + /// @return [std::vector<std::string>] Artist name + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getArtist(); +#else + std::vector<std::string> getArtist(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getAlbum() } + /// To get the album name (for musicvideos) + /// + /// @return [string] Album name + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getAlbum(); +#else + String getAlbum(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getTrack() } + /// To get the track number (for musicvideos) + /// + /// @return [int] Track number + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getTrack(); +#else + int getTrack(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getDuration() } + /// To get the duration + /// + /// @return [unsigned int] Duration + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getDuration(); +#else + unsigned int getDuration(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getResumeTime()) } + /// Gets the resume time of the video item. + /// + /// @return [double] Resume time + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getResumeTime(...); +#else + double getResumeTime(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getResumeTimeTotal()) } + /// Gets the total duration stored with the resume time of the video item. + /// + /// @return [double] Total duration stored with the resume time + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getResumeTimeTotal(...); +#else + double getResumeTimeTotal(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ getUniqueID(key) } + /// Get the unique ID of the given key. + /// A unique ID is an identifier used by a (online) video database used to + /// identify a video in its database. + /// + /// @param key string - uniqueID name. + /// - Some default uniqueID values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - uniqueid name + /// | tvdb | string - uniqueid name + /// | tmdb | string - uniqueid name + /// | anidb | string - uniqueid name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getUniqueID(key); +#else + String getUniqueID(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setUniqueID(uniqueid, [type], [isdefault]) } + /// Set the given unique ID. + /// A unique ID is an identifier used by a (online) video database used to + /// identify a video in its database. + /// + /// @param uniqueid string - value of the unique ID. + /// @param type [opt] string - type / label of the unique ID. + /// @param isdefault [opt] bool - whether the given unique ID is the default unique ID. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setUniqueID(...); +#else + void setUniqueID(const String& uniqueid, const String& type = "", bool isdefault = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setUniqueIDs(values, defaultuniqueid) } + /// Set the given unique IDs. + /// A unique ID is an identifier used by a (online) video database used to + /// identify a video in its database. + /// + /// @param values dictionary - pairs of `{ 'label': 'value' }`. + /// @param defaultuniqueid [opt] string - the name of default uniqueID. + /// + /// - Some example values (any string possible): + /// | Label | Type | + /// |:-------------:|:--------------------------------------------------| + /// | imdb | string - uniqueid name + /// | tvdb | string - uniqueid name + /// | tmdb | string - uniqueid name + /// | anidb | string - uniqueid name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setUniqueIDs(...); +#else + void setUniqueIDs(const std::map<String, String>& uniqueIDs, + const String& defaultuniqueid = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setDbId(dbid) } + /// Set the database identifier of the video item. + /// + /// @param dbid integer - Database identifier. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDbId(...); +#else + void setDbId(int dbid); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setYear(year) } + /// Set the year of the video item. + /// + /// @param year integer - Year. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setYear(...); +#else + void setYear(int year); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setEpisode(episode) } + /// Set the episode number of the episode. + /// + /// @param episode integer - Episode number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setEpisode(...); +#else + void setEpisode(int episode); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSeason(season) } + /// Set the season number of the video item. + /// + /// @param season integer - Season number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSeason(...); +#else + void setSeason(int season); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSortEpisode(sortepisode) } + /// Set the episode sort number of the episode. + /// + /// @param sortepisode integer - Episode sort number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSortEpisode(...); +#else + void setSortEpisode(int sortepisode); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSortSeason(sortseason) } + /// Set the season sort number of the season. + /// + /// @param sortseason integer - Season sort number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSortSeason(...); +#else + void setSortSeason(int sortseason); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setEpisodeGuide(episodeguide) } + /// Set the episode guide of the video item. + /// + /// @param episodeguide string - Episode guide. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setEpisodeGuide(...); +#else + void setEpisodeGuide(const String& episodeguide); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTop250(top250) } + /// Set the top 250 number of the video item. + /// + /// @param top250 integer - Top 250 number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTop250(...); +#else + void setTop250(int top250); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSetId(setid) } + /// Set the movie set identifier of the video item. + /// + /// @param setid integer - Set identifier. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSetId(...); +#else + void setSetId(int setid); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTrackNumber(tracknumber) } + /// Set the track number of the music video item. + /// + /// @param tracknumber integer - Track number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTrackNumber(...); +#else + void setTrackNumber(int tracknumber); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setRating(rating, [votes], [type], [isdefault]) } + /// Set the rating of the video item. + /// + /// @param rating float - Rating number. + /// @param votes integer - Number of votes. + /// @param type string - Type of the rating. + /// @param isdefault bool - Whether the rating is the default or not. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setRating(...); +#else + void setRating(float rating, int votes = 0, const String& type = "", bool isdefault = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setRatings(ratings, [defaultrating]) } + /// Set the ratings of the video item. + /// + /// @param ratings dictionary - `{ 'type': (rating, votes) }`. + /// @param defaultrating string - Type / Label of the default rating. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setRatings(...); +#else + void setRatings(const std::map<String, Tuple<float, int>>& ratings, + const String& defaultrating = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setUserRating(userrating) } + /// Set the user rating of the video item. + /// + /// @param userrating integer - User rating. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setUserRating(...); +#else + void setUserRating(int userrating); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setPlaycount(playcount) } + /// Set the playcount of the video item. + /// + /// @param playcount integer - Playcount. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPlaycount(...); +#else + void setPlaycount(int playcount); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setMpaa(mpaa) } + /// Set the MPAA rating of the video item. + /// + /// @param mpaa string - MPAA rating. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMpaa(...); +#else + void setMpaa(const String& mpaa); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setPlot(plot) } + /// Set the plot of the video item. + /// + /// @param plot string - Plot. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPlot(...); +#else + void setPlot(const String& plot); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setPlotOutline(plotoutline) } + /// Set the plot outline of the video item. + /// + /// @param plotoutline string - Plot outline. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPlotOutline(...); +#else + void setPlotOutline(const String& plotoutline); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTitle(title) } + /// Set the title of the video item. + /// + /// @param title string - Title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTitle(...); +#else + void setTitle(const String& title); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setOriginalTitle(originaltitle) } + /// Set the original title of the video item. + /// + /// @param originaltitle string - Original title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setOriginalTitle(...); +#else + void setOriginalTitle(const String& originaltitle); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSortTitle(sorttitle) } + /// Set the sort title of the video item. + /// + /// @param sorttitle string - Sort title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSortTitle(...); +#else + void setSortTitle(const String& sorttitle); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTagLine(tagline) } + /// Set the tagline of the video item. + /// + /// @param tagline string - Tagline. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTagLine(...); +#else + void setTagLine(const String& tagline); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTvShowTitle(tvshowtitle) } + /// Set the TV show title of the video item. + /// + /// @param tvshowtitle string - TV show title. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTvShowTitle(...); +#else + void setTvShowTitle(const String& tvshowtitle); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTvShowStatus(tvshowstatus) } + /// Set the TV show status of the video item. + /// + /// @param status string - TV show status. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTvShowStatus(...); +#else + void setTvShowStatus(const String& status); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setGenres(genre) } + /// Set the genres of the video item. + /// + /// @param genre list - Genres. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setGenres(...); +#else + void setGenres(std::vector<String> genre); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setCountries(countries) } + /// Set the countries of the video item. + /// + /// @param countries list - Countries. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setCountries(...); +#else + void setCountries(std::vector<String> countries); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setDirectors(directors) } + /// Set the directors of the video item. + /// + /// @param directors list - Directors. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDirectors(...); +#else + void setDirectors(std::vector<String> directors); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setStudios(studios) } + /// Set the studios of the video item. + /// + /// @param studios list - Studios. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setStudios(...); +#else + void setStudios(std::vector<String> studios); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setWriters(writers) } + /// Set the writers of the video item. + /// + /// @param writers list - Writers. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setWriters(...); +#else + void setWriters(std::vector<String> writers); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setDuration(duration) } + /// Set the duration of the video item. + /// + /// @param duration integer - Duration in seconds. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDuration(...); +#else + void setDuration(int duration); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setPremiered(premiered) } + /// Set the premiere date of the video item. + /// + /// @param premiered string - Premiere date. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPremiered(...); +#else + void setPremiered(const String& premiered); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSet(set) } + /// Set the movie set (name) of the video item. + /// + /// @param set string - Movie set (name). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSet(...); +#else + void setSet(const String& set); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setSetOverview(setoverview) } + /// Set the movie set overview of the video item. + /// + /// @param setoverview string - Movie set overview. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setSetOverview(...); +#else + void setSetOverview(const String& setoverview); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTags(tags) } + /// Set the tags of the video item. + /// + /// @param tags list - Tags. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTags(...); +#else + void setTags(std::vector<String> tags); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setProductionCode(const String& productioncode) } + /// Set the production code of the video item. + /// + /// @param productioncode string - Production code. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setProductionCode(...); +#else + void setProductionCode(const String& productioncode); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setFirstAired(firstaired) } + /// Set the first aired date of the video item. + /// + /// @param firstaired string - First aired date. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setFirstAired(...); +#else + void setFirstAired(const String& firstaired); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setLastPlayed(lastplayed) } + /// Set the last played date of the video item. + /// + /// @param lastplayed string - Last played date (YYYY-MM-DD HH:MM:SS). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setLastPlayed(...); +#else + void setLastPlayed(const String& lastplayed); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setAlbum(album) } + /// Set the album of the video item. + /// + /// @param album string - Album. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setAlbum(...); +#else + void setAlbum(const String& album); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setVotes(votes) } + /// Set the number of votes of the video item. + /// + /// @param votes integer - Number of votes. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setVotes(...); +#else + void setVotes(int votes); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setTrailer(trailer) } + /// Set the trailer of the video item. + /// + /// @param trailer string - Trailer. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setTrailer(...); +#else + void setTrailer(const String& trailer); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setPath(path) } + /// Set the path of the video item. + /// + /// @param path string - Path. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setPath(...); +#else + void setPath(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setFilenameAndPath(filenameandpath) } + /// Set the filename and path of the video item. + /// + /// @param filenameandpath string - Filename and path. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setFilenameAndPath(...); +#else + void setFilenameAndPath(const String& filenameandpath); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setIMDBNumber(imdbnumber) } + /// Set the IMDb number of the video item. + /// + /// @param imdbnumber string - IMDb number. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setIMDBNumber(...); +#else + void setIMDBNumber(const String& imdbnumber); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setDateAdded(dateadded) } + /// Set the date added of the video item. + /// + /// @param dateadded string - Date added (YYYY-MM-DD HH:MM:SS). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setDateAdded(...); +#else + void setDateAdded(const String& dateadded); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setMediaType(mediatype) } + /// Set the media type of the video item. + /// + /// @param mediatype string - Media type. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setMediaType(...); +#else + void setMediaType(const String& mediatype); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setShowLinks(showlinks) } + /// Set the TV show links of the movie. + /// + /// @param showlinks list - TV show links. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setShowLinks(...); +#else + void setShowLinks(std::vector<String> showlinks); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setArtists(artists) } + /// Set the artists of the music video item. + /// + /// @param artists list - Artists. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setArtists(...); +#else + void setArtists(std::vector<String> artists); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setCast(actors) } + /// Set the cast / actors of the video item. + /// + /// @param actors list - Cast / Actors. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setCast(...); +#else + void setCast(const std::vector<const Actor*>& actors); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ setResumePoint(time, [totaltime]) } + /// Set the resume point of the video item. + /// + /// @param time float - Resume point in seconds. + /// @param totaltime float - Total duration in seconds. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + setResumePoint(...); +#else + void setResumePoint(double time, double totaltime = 0.0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addSeason(number, [name]) } + /// Add a season with name. It needs at least the season number. + /// + /// @param number int - the number of the season. + /// @param name string - the name of the season. Default "". + /// + /// + ///----------------------------------------------------------------------- + /// + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # addSeason(number, name)) + /// infotagvideo.addSeason(1, "Murder House") + /// ... + /// ~~~~~~~~~~~~~ + /// + addSeason(...); +#else + void addSeason(int number, std::string name = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addSeasons(namedseasons) } + /// Add named seasons to the TV show. + /// + /// @param namedseasons list - `[ (season, name) ]`. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + addSeasons(...); +#else + void addSeasons(const std::vector<Tuple<int, std::string>>& namedseasons); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addVideoStream(stream) } + /// Add a video stream to the video item. + /// + /// @param stream VideoStreamDetail - Video stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + addVideoStream(...); +#else + void addVideoStream(const VideoStreamDetail* stream); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addAudioStream(stream) } + /// Add an audio stream to the video item. + /// + /// @param stream AudioStreamDetail - Audio stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + addAudioStream(...); +#else + void addAudioStream(const AudioStreamDetail* stream); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addSubtitleStream(stream) } + /// Add a subtitle stream to the video item. + /// + /// @param stream SubtitleStreamDetail - Subtitle stream. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + addSubtitleStream(...); +#else + void addSubtitleStream(const SubtitleStreamDetail* stream); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_InfoTagVideo + /// @brief \python_func{ addAvailableArtwork(images) } + /// Add an image to available artworks (needed for video scrapers) + /// + /// @param url string - image path url + /// @param arttype string - image type + /// @param preview [opt] string - image preview path url + /// @param referrer [opt] string - referrer url + /// @param cache [opt] string - filename in cache + /// @param post [opt] bool - use post to retrieve the image (default false) + /// @param isgz [opt] bool - use gzip to retrieve the image (default false) + /// @param season [opt] integer - number of season in case of season thumb + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// infotagvideo.addAvailableArtwork(path_to_image_1, "thumb") + /// ... + /// ~~~~~~~~~~~~~ + /// + addAvailableArtwork(...); +#else + void addAvailableArtwork(const std::string& url, + const std::string& arttype = "", + const std::string& preview = "", + const std::string& referrer = "", + const std::string& cache = "", + bool post = false, + bool isgz = false, + int season = -1); +#endif + /// @} + +#ifndef SWIG + static void setDbIdRaw(CVideoInfoTag* infoTag, int dbId); + static void setUniqueIDRaw(CVideoInfoTag* infoTag, + const String& uniqueID, + const String& type = "", + bool isDefault = false); + static void setUniqueIDsRaw(CVideoInfoTag* infoTag, + std::map<String, String> uniqueIDs, + const String& defaultUniqueID = ""); + static void setYearRaw(CVideoInfoTag* infoTag, int year); + static void setEpisodeRaw(CVideoInfoTag* infoTag, int episode); + static void setSeasonRaw(CVideoInfoTag* infoTag, int season); + static void setSortEpisodeRaw(CVideoInfoTag* infoTag, int sortEpisode); + static void setSortSeasonRaw(CVideoInfoTag* infoTag, int sortSeason); + static void setEpisodeGuideRaw(CVideoInfoTag* infoTag, const String& episodeGuide); + static void setTop250Raw(CVideoInfoTag* infoTag, int top250); + static void setSetIdRaw(CVideoInfoTag* infoTag, int setId); + static void setTrackNumberRaw(CVideoInfoTag* infoTag, int trackNumber); + static void setRatingRaw(CVideoInfoTag* infoTag, + float rating, + int votes = 0, + const std::string& type = "", + bool isDefault = false); + static void setRatingsRaw(CVideoInfoTag* infoTag, + const std::map<String, Tuple<float, int>>& ratings, + const String& defaultRating = ""); + static void setUserRatingRaw(CVideoInfoTag* infoTag, int userRating); + static void setPlaycountRaw(CVideoInfoTag* infoTag, int playcount); + static void setMpaaRaw(CVideoInfoTag* infoTag, const String& mpaa); + static void setPlotRaw(CVideoInfoTag* infoTag, const String& plot); + static void setPlotOutlineRaw(CVideoInfoTag* infoTag, const String& plotOutline); + static void setTitleRaw(CVideoInfoTag* infoTag, const String& title); + static void setOriginalTitleRaw(CVideoInfoTag* infoTag, const String& originalTitle); + static void setSortTitleRaw(CVideoInfoTag* infoTag, const String& sortTitle); + static void setTagLineRaw(CVideoInfoTag* infoTag, const String& tagLine); + static void setTvShowTitleRaw(CVideoInfoTag* infoTag, const String& tvshowTitle); + static void setTvShowStatusRaw(CVideoInfoTag* infoTag, const String& tvshowStatus); + static void setGenresRaw(CVideoInfoTag* infoTag, std::vector<String> genre); + static void setCountriesRaw(CVideoInfoTag* infoTag, std::vector<String> countries); + static void setDirectorsRaw(CVideoInfoTag* infoTag, std::vector<String> directors); + static void setStudiosRaw(CVideoInfoTag* infoTag, std::vector<String> studios); + static void setWritersRaw(CVideoInfoTag* infoTag, std::vector<String> writers); + static void setDurationRaw(CVideoInfoTag* infoTag, int duration); + static void setPremieredRaw(CVideoInfoTag* infoTag, const String& premiered); + static void setSetRaw(CVideoInfoTag* infoTag, const String& set); + static void setSetOverviewRaw(CVideoInfoTag* infoTag, const String& setOverview); + static void setTagsRaw(CVideoInfoTag* infoTag, std::vector<String> tags); + static void setProductionCodeRaw(CVideoInfoTag* infoTag, const String& productionCode); + static void setFirstAiredRaw(CVideoInfoTag* infoTag, const String& firstAired); + static void setLastPlayedRaw(CVideoInfoTag* infoTag, const String& lastPlayed); + static void setAlbumRaw(CVideoInfoTag* infoTag, const String& album); + static void setVotesRaw(CVideoInfoTag* infoTag, int votes); + static void setTrailerRaw(CVideoInfoTag* infoTag, const String& trailer); + static void setPathRaw(CVideoInfoTag* infoTag, const String& path); + static void setFilenameAndPathRaw(CVideoInfoTag* infoTag, const String& filenameAndPath); + static void setIMDBNumberRaw(CVideoInfoTag* infoTag, const String& imdbNumber); + static void setDateAddedRaw(CVideoInfoTag* infoTag, const String& dateAdded); + static void setMediaTypeRaw(CVideoInfoTag* infoTag, const String& mediaType); + static void setShowLinksRaw(CVideoInfoTag* infoTag, std::vector<String> showLinks); + static void setArtistsRaw(CVideoInfoTag* infoTag, std::vector<String> artists); + static void setCastRaw(CVideoInfoTag* infoTag, std::vector<SActorInfo> cast); + static void setResumePointRaw(CVideoInfoTag* infoTag, double time, double totalTime = 0.0); + + static void addSeasonRaw(CVideoInfoTag* infoTag, int number, std::string name = ""); + static void addSeasonsRaw(CVideoInfoTag* infoTag, + const std::vector<Tuple<int, std::string>>& namedSeasons); + + static void addStreamRaw(CVideoInfoTag* infoTag, CStreamDetail* stream); + static void finalizeStreamsRaw(CVideoInfoTag* infoTag); + + static void addAvailableArtworkRaw(CVideoInfoTag* infoTag, + const std::string& url, + const std::string& art_type = "", + const std::string& preview = "", + const std::string& referrer = "", + const std::string& cache = "", + bool post = false, + bool isgz = false, + int season = -1); +#endif + }; + } +} diff --git a/xbmc/interfaces/legacy/Keyboard.cpp b/xbmc/interfaces/legacy/Keyboard.cpp new file mode 100644 index 0000000..2b7e08c --- /dev/null +++ b/xbmc/interfaces/legacy/Keyboard.cpp @@ -0,0 +1,66 @@ +/* + * 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 "Keyboard.h" + +#include "LanguageHook.h" +#include "guilib/GUIKeyboardFactory.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/Variant.h" + +using namespace KODI::MESSAGING; + +namespace XBMCAddon +{ + namespace xbmc + { + + Keyboard::Keyboard(const String& line /* = nullString*/, const String& heading/* = nullString*/, bool hidden/* = false*/) + : strDefault(line), strHeading(heading), bHidden(hidden) + { + } + + Keyboard::~Keyboard() = default; + + void Keyboard::doModal(int autoclose) + { + DelayedCallGuard dg(languageHook); + // using keyboardfactory method to get native keyboard if there is. + strText = strDefault; + std::string text(strDefault); + bConfirmed = CGUIKeyboardFactory::ShowAndGetInput(text, CVariant{strHeading}, true, bHidden, autoclose * 1000); + strText = text; + } + + void Keyboard::setDefault(const String& line) + { + strDefault = line; + } + + void Keyboard::setHiddenInput(bool hidden) + { + bHidden = hidden; + } + + void Keyboard::setHeading(const String& heading) + { + strHeading = heading; + } + + String Keyboard::getText() + { + return strText; + } + + bool Keyboard::isConfirmed() + { + return bConfirmed; + } + } +} + diff --git a/xbmc/interfaces/legacy/Keyboard.h b/xbmc/interfaces/legacy/Keyboard.h new file mode 100644 index 0000000..94f6421 --- /dev/null +++ b/xbmc/interfaces/legacy/Keyboard.h @@ -0,0 +1,215 @@ +/* + * 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 "AddonClass.h" +#include "AddonString.h" +#include "Exception.h" + +class CGUIDialogKeyboardGeneric; + +namespace XBMCAddon +{ + namespace xbmc + { + XBMCCOMMONS_STANDARD_EXCEPTION(KeyboardException); + + /// + /// \defgroup python_keyboard Keyboard + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's keyboard class.** + /// + /// \python_class{ xbmc.Keyboard([default, heading, hidden]) } + /// + /// Creates a new Keyboard object with default text + /// heading and hidden input flag if supplied. + /// + /// @param default : [opt] string - default text entry. + /// @param heading : [opt] string - keyboard heading. + /// @param hidden : [opt] boolean - True for hidden text entry. + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// kb = xbmc.Keyboard('default', 'heading', True) + /// kb.setDefault('password') # optional + /// kb.setHeading('Enter password') # optional + /// kb.setHiddenInput(True) # optional + /// kb.doModal() + /// if (kb.isConfirmed()): + /// text = kb.getText() + /// .. + /// ~~~~~~~~~~~~~ + /// + class Keyboard : public AddonClass + { + public: +#ifndef SWIG + String strDefault; + String strHeading; + bool bHidden; + String strText; + bool bConfirmed = false; +#endif + + Keyboard(const String& line = emptyString, const String& heading = emptyString, bool hidden = false); + ~Keyboard() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_keyboard + /// @brief \python_func{ doModal([autoclose]) } + /// Show keyboard and wait for user action. + /// + /// @param autoclose [opt] integer - milliseconds to autoclose + /// dialog. (default=do not autoclose) + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// kb.doModal(30000) + /// .. + /// ~~~~~~~~~~~~~ + /// + doModal(...); +#else + void doModal(int autoclose = 0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + // setDefault() Method + /// + /// \ingroup python_keyboard + /// @brief \python_func{ setDefault(line) } + /// Set the default text entry. + /// + /// @param line string - default text entry. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// kb.setDefault('password') + /// .. + /// ~~~~~~~~~~~~~ + /// + setDefault(...); +#else + void setDefault(const String& line = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_keyboard + /// @brief \python_func{ setHiddenInput(hidden) } + /// Allows hidden text entry. + /// + /// @param hidden boolean - True for hidden text entry. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// kb.setHiddenInput(True) + /// .. + /// ~~~~~~~~~~~~~ + /// + setHiddenInput(...); +#else + void setHiddenInput(bool hidden = false); +#endif + + // setHeading() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_keyboard + /// @brief \python_func{ setHeading(heading) } + /// Set the keyboard heading. + /// + /// @param heading string - keyboard heading. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// kb.setHeading('Enter password') + /// .. + /// ~~~~~~~~~~~~~ + /// + setHeading(...); +#else + void setHeading(const String& heading); +#endif + + // getText() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_keyboard + /// @brief \python_func{ getText() } + /// Returns the user input as a string. + /// + /// @note This will always return the text entry even if you cancel the keyboard. + /// Use the isConfirmed() method to check if user cancelled the keyboard. + /// + /// @return get the in keyboard entered text + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// text = kb.getText() + /// .. + /// ~~~~~~~~~~~~~ + /// + getText(); +#else + String getText(); +#endif + + // isConfirmed() Method +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_keyboard + /// @brief \python_func{ isConfirmed() } + /// Returns False if the user cancelled the input. + /// + /// @return true if confirmed, if cancelled false + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// if (kb.isConfirmed()): + /// .. + /// ~~~~~~~~~~~~~ + /// + isConfirmed(); +#else + bool isConfirmed(); +#endif + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/LanguageHook.cpp b/xbmc/interfaces/legacy/LanguageHook.cpp new file mode 100644 index 0000000..c3b70c6 --- /dev/null +++ b/xbmc/interfaces/legacy/LanguageHook.cpp @@ -0,0 +1,41 @@ +/* + * 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 "LanguageHook.h" + +#include "utils/GlobalsHandling.h" + +namespace XBMCAddon +{ + // just need a place for the vtab + LanguageHook::~LanguageHook() = default; + + static thread_local LanguageHook* addonLanguageHookTls; + static bool threadLocalInitialized = false; + static xbmcutil::InitFlag initer(threadLocalInitialized); + + void LanguageHook::SetLanguageHook(LanguageHook* languageHook) + { + XBMC_TRACE; + languageHook->Acquire(); + addonLanguageHookTls = languageHook; + } + + LanguageHook* LanguageHook::GetLanguageHook() + { + return threadLocalInitialized ? addonLanguageHookTls : NULL; + } + + void LanguageHook::ClearLanguageHook() + { + LanguageHook* lh = addonLanguageHookTls; + addonLanguageHookTls = NULL; + if (lh) + lh->Release(); + } +} diff --git a/xbmc/interfaces/legacy/LanguageHook.h b/xbmc/interfaces/legacy/LanguageHook.h new file mode 100644 index 0000000..86abdac --- /dev/null +++ b/xbmc/interfaces/legacy/LanguageHook.h @@ -0,0 +1,151 @@ +/* + * 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 "AddonClass.h" +#include "CallbackHandler.h" +#include "threads/Event.h" + +/** + * This class is an interface that can be used to define programming language + * specific hooks. + */ + +class IPlayerCallback; + +namespace XBMCAddon +{ + namespace xbmc + { + class Monitor; + } + + class LanguageHook : public AddonClass + { + protected: + inline LanguageHook() = default; + + public: + ~LanguageHook() override; + + /** + * If the scripting language needs special handling for calls + * that block or are delayed in any other means, this should + * be overloaded. + * + * In Python when control is passed to a native + * method that blocks, you need to allow other threads in + * Python to run by using Py_BEGIN_ALLOW_THREADS. This is + * the place to put that functionality + */ + virtual void DelayedCallOpen() { } + + /** + * If the scripting language needs special handling for calls + * that block or are delayed in any other means, this should + * be overloaded. + * + * In Python when control is passed to a native + * method that blocks, you need to allow other threads in + * Python to run by using Py_BEGIN_ALLOW_THREADS. When that + * delayed method ends you need to restore the Python thread + * state using Py_END_ALLOW_THREADS. This is the place to put + * that functionality + */ + virtual void DelayedCallClose() { } + + virtual void MakePendingCalls() {} + + /** + * For scripting languages that need a global callback handler, this + * method should be overloaded to supply one. + */ + virtual CallbackHandler* GetCallbackHandler() { return NULL; } + + /** + * This is a callback method that can be overridden to receive a callback + * when an AddonClass that has this language hook is on is being constructed. + * It is called from the constructor of AddonClass so the implementor + * cannot assume the subclasses have been built or that calling a + * virtual function on the AddonClass will work as expected. + */ + virtual void Constructing(AddonClass* beingConstructed) { } + + /** + * This is a callback method that can be overridden to receive a callback + * when an AddonClass that has this language hook is on is being destructed. + * It is called from the destructor of AddonClass so the implementor + * should assume the subclasses have been torn down and that calling a + * virtual function on the AddonClass will not work as expected. + */ + virtual void Destructing(AddonClass* beingDestructed) { } + + /** + * This method should be done a different way but since the only other way + * I can think to do it requires an InheritableThreadLocal C++ equivalent, + * I'm going to defer to this technique for now. + * + * Currently (for python) the scripting language has the Addon id injected + * into it as a global variable. Therefore the only way to retrieve it is + * to use scripting language specific calls. So until I figure out a + * better way to do this, this is how I need to retrieve it. + */ + virtual String GetAddonId() { return emptyString; } + virtual String GetAddonVersion() { return emptyString; } + virtual long GetInvokerId() { return -1; } + + virtual void RegisterPlayerCallback(IPlayerCallback* player) = 0; + virtual void UnregisterPlayerCallback(IPlayerCallback* player) = 0; + virtual void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; + virtual void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* player) = 0; + virtual bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds) = 0; + + static void SetLanguageHook(LanguageHook* languageHook); + static LanguageHook* GetLanguageHook(); + static void ClearLanguageHook(); + }; + + /** + * This class can be used to access the language hook's DelayedCallOpen + * and DelayedCallClose. It should be used whenever an API method + * is written such that it can block for an indefinite amount of time + * since certain scripting languages (like Python) need to do extra + * work for delayed calls (like free the python locks and handle + * callbacks). + */ + class DelayedCallGuard + { + LanguageHook* languageHook; + bool clearOnExit = false; + + public: + inline explicit DelayedCallGuard(LanguageHook* languageHook_) : languageHook(languageHook_) + { if (languageHook) languageHook->DelayedCallOpen(); } + + inline DelayedCallGuard() : languageHook(LanguageHook::GetLanguageHook()) + { if (languageHook) languageHook->DelayedCallOpen(); } + + inline ~DelayedCallGuard() + { + if (clearOnExit) LanguageHook::ClearLanguageHook(); + if (languageHook) languageHook->DelayedCallClose(); + } + + inline LanguageHook* getLanguageHook() { return languageHook; } + }; + + class SetLanguageHookGuard + { + public: + inline explicit SetLanguageHookGuard(LanguageHook* languageHook) { LanguageHook::SetLanguageHook(languageHook); } + inline ~SetLanguageHookGuard() { LanguageHook::ClearLanguageHook(); } + }; + +} + diff --git a/xbmc/interfaces/legacy/List.h b/xbmc/interfaces/legacy/List.h new file mode 100644 index 0000000..932fa4e --- /dev/null +++ b/xbmc/interfaces/legacy/List.h @@ -0,0 +1,20 @@ +/* + * 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 <list> + +namespace XBMCAddon +{ + template <class T> class List : public std::list<T> + { + public: + static List<T> nullList; + }; +} diff --git a/xbmc/interfaces/legacy/ListItem.cpp b/xbmc/interfaces/legacy/ListItem.cpp new file mode 100644 index 0000000..64410ad --- /dev/null +++ b/xbmc/interfaces/legacy/ListItem.cpp @@ -0,0 +1,1056 @@ +/* + * 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 "ListItem.h" + +#include "AddonUtils.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "games/GameTypes.h" +#include "games/tags/GameInfoTag.h" +#include "music/tags/MusicInfoTag.h" +#include "pictures/PictureInfoTag.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" + +#include <cstdlib> +#include <sstream> +#include <utility> + +namespace XBMCAddon +{ + namespace xbmcgui + { + ListItem::ListItem(const String& label, + const String& label2, + const String& path, + bool offscreen) : + m_offscreen(offscreen) + { + item.reset(); + + // create CFileItem + item.reset(new CFileItem()); + if (!item) // not sure if this is really possible + return; + + if (!label.empty()) + item->SetLabel( label ); + if (!label2.empty()) + item->SetLabel2( label2 ); + if (!path.empty()) + item->SetPath(path); + } + + ListItem::~ListItem() + { + item.reset(); + } + + String ListItem::getLabel() + { + if (!item) return ""; + + String ret; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + ret = item->GetLabel(); + } + + return ret; + } + + String ListItem::getLabel2() + { + if (!item) return ""; + + String ret; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + ret = item->GetLabel2(); + } + + return ret; + } + + void ListItem::setLabel(const String& label) + { + if (!item) return; + // set label + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + item->SetLabel(label); + } + } + + void ListItem::setLabel2(const String& label) + { + if (!item) return; + // set label + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + item->SetLabel2(label); + } + } + + String ListItem::getDateTime() + { + if (!item) + return ""; + + String ret; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + if (item->m_dateTime.IsValid()) + ret = item->m_dateTime.GetAsW3CDateTime(); + } + + return ret; + } + + void ListItem::setDateTime(const String& dateTime) + { + if (!item) + return; + // set datetime + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + setDateTimeRaw(dateTime); + } + } + + void ListItem::setArt(const Properties& dictionary) + { + if (!item) return; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + for (const auto& it : dictionary) + addArtRaw(it.first, it.second); + } + } + + void ListItem::setIsFolder(bool isFolder) + { + if (!item) + return; + + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + setIsFolderRaw(isFolder); + } + } + + void ListItem::setUniqueIDs(const Properties& dictionary, const String& defaultrating /* = "" */) + { + CLog::Log( + LOGWARNING, + "ListItem.setUniqueIDs() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.setUniqueIDs()."); + + if (!item) + return; + + std::map<String, String> uniqueIDs; + for (const auto& it : dictionary) + uniqueIDs.emplace(it.first, it.second); + + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + xbmc::InfoTagVideo::setUniqueIDsRaw(GetVideoInfoTag(), uniqueIDs, defaultrating); + } + } + + void ListItem::setRating(const std::string& type, + float rating, + int votes /* = 0 */, + bool defaultt /* = false */) + { + CLog::Log(LOGWARNING, + "ListItem.setRating() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.setRating()."); + + if (!item) return; + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + xbmc::InfoTagVideo::setRatingRaw(GetVideoInfoTag(), rating, votes, type, defaultt); + } + + void ListItem::addSeason(int number, std::string name /* = "" */) + { + CLog::Log(LOGWARNING, + "ListItem.addSeason() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.addSeason()."); + + if (!item) return; + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + xbmc::InfoTagVideo::addSeasonRaw(GetVideoInfoTag(), number, std::move(name)); + } + + void ListItem::select(bool selected) + { + if (!item) return; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + item->Select(selected); + } + } + + + bool ListItem::isSelected() + { + if (!item) return false; + + bool ret; + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + ret = item->IsSelected(); + } + + return ret; + } + + void ListItem::setProperty(const char * key, const String& value) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + String lowerKey = key; + StringUtils::ToLower(lowerKey); + if (lowerKey == "startoffset") + { // special case for start offset - don't actually store in a property + setStartOffsetRaw(strtod(value.c_str(), nullptr)); + } + else if (lowerKey == "mimetype") + { // special case for mime type - don't actually stored in a property, + item->SetMimeType(value); + } + else if (lowerKey == "totaltime") + { + CLog::Log(LOGWARNING, + "\"{}\" in ListItem.setProperty() is deprecated and might be removed in future " + "Kodi versions. Please use InfoTagVideo.setResumePoint().", + lowerKey); + + CBookmark resumePoint(GetVideoInfoTag()->GetResumePoint()); + resumePoint.totalTimeInSeconds = atof(value.c_str()); + GetVideoInfoTag()->SetResumePoint(resumePoint); + } + else if (lowerKey == "resumetime") + { + CLog::Log(LOGWARNING, + "\"{}\" in ListItem.setProperty() is deprecated and might be removed in future " + "Kodi versions. Please use InfoTagVideo.setResumePoint().", + lowerKey); + + xbmc::InfoTagVideo::setResumePointRaw(GetVideoInfoTag(), atof(value.c_str())); + } + else if (lowerKey == "specialsort") + setSpecialSortRaw(value); + else if (lowerKey == "fanart_image") + item->SetArt("fanart", value); + else + addPropertyRaw(lowerKey, value); + } + + void ListItem::setProperties(const Properties& dictionary) + { + for (const auto& it : dictionary) + setProperty(it.first.c_str(), it.second); + } + + String ListItem::getProperty(const char* key) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + String lowerKey = key; + StringUtils::ToLower(lowerKey); + std::string value; + if (lowerKey == "startoffset") + { // special case for start offset - don't actually store in a property, + // we store it in item.m_lStartOffset instead + value = StringUtils::Format("{:f}", CUtil::ConvertMilliSecsToSecs(item->GetStartOffset())); + } + else if (lowerKey == "totaltime") + { + CLog::Log(LOGWARNING, + "\"{}\" in ListItem.getProperty() is deprecated and might be removed in future " + "Kodi versions. Please use InfoTagVideo.getResumeTimeTotal().", + lowerKey); + + value = StringUtils::Format("{:f}", GetVideoInfoTag()->GetResumePoint().totalTimeInSeconds); + } + else if (lowerKey == "resumetime") + { + CLog::Log(LOGWARNING, + "\"{}\" in ListItem.getProperty() is deprecated and might be removed in future " + "Kodi versions. Please use InfoTagVideo.getResumeTime().", + lowerKey); + + value = StringUtils::Format("{:f}", GetVideoInfoTag()->GetResumePoint().timeInSeconds); + } + else if (lowerKey == "fanart_image") + value = item->GetArt("fanart"); + else + value = item->GetProperty(lowerKey).asString(); + + return value; + } + + String ListItem::getArt(const char* key) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return item->GetArt(key); + } + + bool ListItem::isFolder() const + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return item->m_bIsFolder; + } + + String ListItem::getUniqueID(const char* key) + { + CLog::Log( + LOGWARNING, + "ListItem.getUniqueID() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.getUniqueID()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return GetVideoInfoTag()->GetUniqueID(key); + } + + float ListItem::getRating(const char* key) + { + CLog::Log(LOGWARNING, + "ListItem.getRating() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.getRating()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return GetVideoInfoTag()->GetRating(key).rating; + } + + int ListItem::getVotes(const char* key) + { + CLog::Log(LOGWARNING, + "ListItem.getVotes() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.getVotesAsInt()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return GetVideoInfoTag()->GetRating(key).votes; + } + + void ListItem::setPath(const String& path) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + setPathRaw(path); + } + + void ListItem::setMimeType(const String& mimetype) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + setMimeTypeRaw(mimetype); + } + + void ListItem::setContentLookup(bool enable) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + setContentLookupRaw(enable); + } + + String ListItem::getPath() + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return item->GetPath(); + } + + void ListItem::setInfo(const char* type, const InfoLabelDict& infoLabels) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + + bool hasDeprecatedInfoLabel = false; + if (StringUtils::CompareNoCase(type, "video") == 0) + { + using InfoTagVideo = xbmc::InfoTagVideo; + auto videotag = GetVideoInfoTag(); + for (const auto& it : infoLabels) + { + const auto key = StringUtils::ToLower(it.first); + const InfoLabelValue& alt = it.second; + const String value(alt.which() == first ? alt.former() : emptyString); + + if (key == "count") + setCountRaw(strtol(value.c_str(), nullptr, 10)); + else if (key == "size") + setSizeRaw(static_cast<int64_t>(strtoll(value.c_str(), nullptr, 10))); + else if (key == "overlay") + { + long overlay = strtol(value.c_str(), nullptr, 10); + if (overlay >= 0 && overlay <= 8) + item->SetOverlayImage(static_cast<CGUIListItem::GUIIconOverlay>(overlay)); + } + else if (key == "date") + setDateTimeRaw(value); + else + { + hasDeprecatedInfoLabel = true; + + if (key == "dbid") + InfoTagVideo::setDbIdRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "year") + InfoTagVideo::setYearRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "episode") + InfoTagVideo::setEpisodeRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "season") + InfoTagVideo::setSeasonRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "sortepisode") + InfoTagVideo::setSortEpisodeRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "sortseason") + InfoTagVideo::setSortSeasonRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "episodeguide") + InfoTagVideo::setEpisodeGuideRaw(videotag, value); + else if (key == "showlink") + InfoTagVideo::setShowLinksRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "top250") + InfoTagVideo::setTop250Raw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "setid") + InfoTagVideo::setSetIdRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "tracknumber") + InfoTagVideo::setTrackNumberRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "rating") + InfoTagVideo::setRatingRaw(videotag, + static_cast<float>(strtod(value.c_str(), nullptr))); + else if (key == "userrating") + InfoTagVideo::setUserRatingRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "watched") // backward compat - do we need it? + InfoTagVideo::setPlaycountRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "playcount") + InfoTagVideo::setPlaycountRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "cast" || key == "castandrole") + { + if (alt.which() != second) + throw WrongTypeException("When using \"cast\" or \"castandrole\" you need to " + "supply a list of tuples for the value in the dictionary"); + + std::vector<SActorInfo> cast; + cast.reserve(alt.later().size()); + for (const auto& castEntry : alt.later()) + { + // castEntry can be a string meaning it's the actor or it can be a tuple meaning it's the + // actor and the role. + const String& actor = + castEntry.which() == first ? castEntry.former() : castEntry.later().first(); + SActorInfo info; + info.strName = actor; + if (castEntry.which() == second) + info.strRole = static_cast<const String&>(castEntry.later().second()); + cast.push_back(std::move(info)); + } + InfoTagVideo::setCastRaw(videotag, std::move(cast)); + } + else if (key == "artist") + { + if (alt.which() != second) + throw WrongTypeException("When using \"artist\" you need to supply a list of " + "strings for the value in the dictionary"); + + std::vector<String> artists; + artists.reserve(alt.later().size()); + for (const auto& castEntry : alt.later()) + { + auto actor = + castEntry.which() == first ? castEntry.former() : castEntry.later().first(); + artists.push_back(std::move(actor)); + } + InfoTagVideo::setArtistsRaw(videotag, artists); + } + else if (key == "genre") + InfoTagVideo::setGenresRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "country") + InfoTagVideo::setCountriesRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "director") + InfoTagVideo::setDirectorsRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "mpaa") + InfoTagVideo::setMpaaRaw(videotag, value); + else if (key == "plot") + InfoTagVideo::setPlotRaw(videotag, value); + else if (key == "plotoutline") + InfoTagVideo::setPlotOutlineRaw(videotag, value); + else if (key == "title") + InfoTagVideo::setTitleRaw(videotag, value); + else if (key == "originaltitle") + InfoTagVideo::setOriginalTitleRaw(videotag, value); + else if (key == "sorttitle") + InfoTagVideo::setSortTitleRaw(videotag, value); + else if (key == "duration") + InfoTagVideo::setDurationRaw(videotag, strtol(value.c_str(), nullptr, 10)); + else if (key == "studio") + InfoTagVideo::setStudiosRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "tagline") + InfoTagVideo::setTagLineRaw(videotag, value); + else if (key == "writer" || key == "credits") + InfoTagVideo::setWritersRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "tvshowtitle") + InfoTagVideo::setTvShowTitleRaw(videotag, value); + else if (key == "premiered") + InfoTagVideo::setPremieredRaw(videotag, value); + else if (key == "status") + InfoTagVideo::setTvShowStatusRaw(videotag, value); + else if (key == "set") + InfoTagVideo::setSetRaw(videotag, value); + else if (key == "setoverview") + InfoTagVideo::setSetOverviewRaw(videotag, value); + else if (key == "tag") + InfoTagVideo::setTagsRaw(videotag, getVideoStringArray(alt, key, value)); + else if (key == "imdbnumber") + InfoTagVideo::setIMDBNumberRaw(videotag, value); + else if (key == "code") + InfoTagVideo::setProductionCodeRaw(videotag, value); + else if (key == "aired") + InfoTagVideo::setFirstAiredRaw(videotag, value); + else if (key == "lastplayed") + InfoTagVideo::setLastPlayedRaw(videotag, value); + else if (key == "album") + InfoTagVideo::setAlbumRaw(videotag, value); + else if (key == "votes") + InfoTagVideo::setVotesRaw(videotag, StringUtils::ReturnDigits(value)); + else if (key == "trailer") + InfoTagVideo::setTrailerRaw(videotag, value); + else if (key == "path") + InfoTagVideo::setPathRaw(videotag, value); + else if (key == "filenameandpath") + InfoTagVideo::setFilenameAndPathRaw(videotag, value); + else if (key == "dateadded") + InfoTagVideo::setDateAddedRaw(videotag, value); + else if (key == "mediatype") + InfoTagVideo::setMediaTypeRaw(videotag, value); + else + CLog::Log(LOGERROR, "NEWADDON Unknown Video Info Key \"{}\"", key); + } + } + + if (hasDeprecatedInfoLabel) + { + CLog::Log( + LOGWARNING, + "Setting most video properties through ListItem.setInfo() is deprecated and might be " + "removed in future Kodi versions. Please use the respective setter in InfoTagVideo."); + } + } + else if (StringUtils::CompareNoCase(type, "music") == 0) + { + String mediaType; + int dbId = -1; + + using InfoTagMusic = xbmc::InfoTagMusic; + auto musictag = GetMusicInfoTag(); + for (const auto& it : infoLabels) + { + const auto key = StringUtils::ToLower(it.first); + const auto& alt = it.second; + const String value(alt.which() == first ? alt.former() : emptyString); + + //! @todo add the rest of the infolabels + if (key == "count") + setCountRaw(strtol(value.c_str(), nullptr, 10)); + else if (key == "size") + setSizeRaw(static_cast<int64_t>(strtoll(value.c_str(), nullptr, 10))); + else if (key == "date") + setDateTimeRaw(value); + else + { + hasDeprecatedInfoLabel = true; + + if (key == "dbid") + dbId = static_cast<int>(strtol(value.c_str(), NULL, 10)); + else if (key == "mediatype") + mediaType = value; + else if (key == "tracknumber") + InfoTagMusic::setTrackRaw(musictag, strtol(value.c_str(), NULL, 10)); + else if (key == "discnumber") + InfoTagMusic::setDiscRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "duration") + InfoTagMusic::setDurationRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "year") + InfoTagMusic::setYearRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "listeners") + InfoTagMusic::setListenersRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "playcount") + InfoTagMusic::setPlayCountRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "genre") + InfoTagMusic::setGenresRaw(musictag, getMusicStringArray(alt, key, value)); + else if (key == "album") + InfoTagMusic::setAlbumRaw(musictag, value); + else if (key == "artist") + InfoTagMusic::setArtistRaw(musictag, value); + else if (key == "title") + InfoTagMusic::setTitleRaw(musictag, value); + else if (key == "rating") + InfoTagMusic::setRatingRaw(musictag, + static_cast<float>(strtod(value.c_str(), nullptr))); + else if (key == "userrating") + InfoTagMusic::setUserRatingRaw(musictag, strtol(value.c_str(), nullptr, 10)); + else if (key == "lyrics") + InfoTagMusic::setLyricsRaw(musictag, value); + else if (key == "lastplayed") + InfoTagMusic::setLastPlayedRaw(musictag, value); + else if (key == "musicbrainztrackid") + InfoTagMusic::setMusicBrainzTrackIDRaw(musictag, value); + else if (key == "musicbrainzartistid") + InfoTagMusic::setMusicBrainzArtistIDRaw(musictag, + getMusicStringArray(alt, key, value)); + else if (key == "musicbrainzalbumid") + InfoTagMusic::setMusicBrainzAlbumIDRaw(musictag, value); + else if (key == "musicbrainzalbumartistid") + InfoTagMusic::setMusicBrainzAlbumArtistIDRaw(musictag, + getMusicStringArray(alt, key, value)); + else if (key == "comment") + InfoTagMusic::setCommentRaw(musictag, value); + else + CLog::Log(LOGERROR, "NEWADDON Unknown Music Info Key \"{}\"", key); + } + + // This should probably be set outside of the loop but since the original + // implementation set it inside of the loop, I'll leave it that way. - Jim C. + musictag->SetLoaded(true); + } + + if (dbId > 0 && !mediaType.empty()) + InfoTagMusic::setDbIdRaw(musictag, dbId, mediaType); + + if (hasDeprecatedInfoLabel) + { + CLog::Log( + LOGWARNING, + "Setting most music properties through ListItem.setInfo() is deprecated and might be " + "removed in future Kodi versions. Please use the respective setter in InfoTagMusic."); + } + } + else if (StringUtils::CompareNoCase(type, "pictures") == 0) + { + for (const auto& it : infoLabels) + { + const auto key = StringUtils::ToLower(it.first); + const auto& alt = it.second; + const String value(alt.which() == first ? alt.former() : emptyString); + + if (key == "count") + setCountRaw(strtol(value.c_str(), nullptr, 10)); + else if (key == "size") + setSizeRaw(static_cast<int64_t>(strtoll(value.c_str(), nullptr, 10))); + else if (key == "title") + setTitleRaw(value); + else if (key == "picturepath") + setPathRaw(value); + else if (key == "date") + setDateTimeRaw(value); + else + { + hasDeprecatedInfoLabel = true; + + String exifkey = key; + if (!StringUtils::StartsWithNoCase(exifkey, "exif:") || exifkey.length() < 6) + { + CLog::Log(LOGWARNING, "ListItem.setInfo: unknown pictures info key \"{}\"", key); + continue; + } + + exifkey = StringUtils::Mid(exifkey, 5); + if (exifkey == "resolution") + xbmc::InfoTagPicture::setResolutionRaw(item->GetPictureInfoTag(), value); + else if (exifkey == "exiftime") + xbmc::InfoTagPicture::setDateTimeTakenRaw(item->GetPictureInfoTag(), value); + else + CLog::Log(LOGWARNING, "ListItem.setInfo: unknown pictures info key \"{}\"", key); + } + } + + if (hasDeprecatedInfoLabel) + { + CLog::Log(LOGWARNING, "Setting most picture properties through ListItem.setInfo() is " + "deprecated and might be removed in future Kodi versions. Please " + "use the respective setter in InfoTagPicture."); + } + } + else if (StringUtils::EqualsNoCase(type, "game")) + { + auto gametag = item->GetGameInfoTag(); + for (const auto& it : infoLabels) + { + const auto key = StringUtils::ToLower(it.first); + const auto& alt = it.second; + const String value(alt.which() == first ? alt.former() : emptyString); + + if (key == "title") + { + setTitleRaw(value); + xbmc::InfoTagGame::setTitleRaw(gametag, value); + } + else if (key == "platform") + xbmc::InfoTagGame::setPlatformRaw(gametag, value); + else if (key == "genres") + xbmc::InfoTagGame::setGenresRaw(gametag, getStringArray(alt, key, value, ",")); + else if (key == "publisher") + xbmc::InfoTagGame::setPublisherRaw(gametag, value); + else if (key == "developer") + xbmc::InfoTagGame::setDeveloperRaw(gametag, value); + else if (key == "overview") + xbmc::InfoTagGame::setOverviewRaw(gametag, value); + else if (key == "year") + xbmc::InfoTagGame::setYearRaw(gametag, strtoul(value.c_str(), nullptr, 10)); + else if (key == "gameclient") + xbmc::InfoTagGame::setGameClientRaw(gametag, value); + } + + if (!infoLabels.empty()) + { + CLog::Log( + LOGWARNING, + "Setting game properties through ListItem.setInfo() is deprecated and might be " + "removed in future Kodi versions. Please use the respective setter in InfoTagGame."); + } + } + else + CLog::Log(LOGWARNING, "ListItem.setInfo: unknown \"type\" parameter value: {}", type); + } // end ListItem::setInfo + + void ListItem::setCast(const std::vector<Properties>& actors) + { + CLog::Log(LOGWARNING, + "ListItem.setCast() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.setCast()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + std::vector<SActorInfo> cast; + cast.reserve(actors.size()); + for (const auto& dictionary : actors) + { + SActorInfo info; + for (auto it = dictionary.begin(); it != dictionary.end(); ++it) + { + const String& key = it->first; + const String& value = it->second; + if (key == "name") + info.strName = value; + else if (key == "role") + info.strRole = value; + else if (key == "thumbnail") + { + info.thumbUrl = CScraperUrl(value); + if (!info.thumbUrl.GetFirstThumbUrl().empty()) + info.thumb = CScraperUrl::GetThumbUrl(info.thumbUrl.GetFirstUrlByType()); + } + else if (key == "order") + info.order = strtol(value.c_str(), nullptr, 10); + } + cast.push_back(std::move(info)); + } + xbmc::InfoTagVideo::setCastRaw(GetVideoInfoTag(), std::move(cast)); + } + + void ListItem::setAvailableFanart(const std::vector<Properties>& images) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + auto infoTag = GetVideoInfoTag(); + infoTag->m_fanart.Clear(); + for (const auto& dictionary : images) + { + std::string image; + std::string preview; + std::string colors; + for (const auto& it : dictionary) + { + const String& key = it.first; + const String& value = it.second; + if (key == "image") + image = value; + else if (key == "preview") + preview = value; + else if (key == "colors") + colors = value; + } + infoTag->m_fanart.AddFanart(image, preview, colors); + } + infoTag->m_fanart.Pack(); + } + + void ListItem::addAvailableArtwork(const std::string& url, + const std::string& art_type, + const std::string& preview, + const std::string& referrer, + const std::string& cache, + bool post, + bool isgz, + int season) + { + CLog::Log(LOGWARNING, "ListItem.addAvailableArtwork() is deprecated and might be removed in " + "future Kodi versions. Please use InfoTagVideo.addAvailableArtwork()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + xbmc::InfoTagVideo::addAvailableArtworkRaw(GetVideoInfoTag(), url, art_type, preview, + referrer, cache, post, isgz, season); + } + + void ListItem::addStreamInfo(const char* cType, const Properties& dictionary) + { + CLog::Log( + LOGWARNING, + "ListItem.addStreamInfo() is deprecated and might be removed in future Kodi versions. " + "Please use InfoTagVideo.addVideoStream(), InfoTagVideo.addAudioStream() and " + "InfoTagVideo.addSubtitleStream()."); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + + auto infoTag = GetVideoInfoTag(); + if (StringUtils::CompareNoCase(cType, "video") == 0) + { + CStreamDetailVideo* video = new CStreamDetailVideo; + for (const auto& it : dictionary) + { + const String& key = it.first; + const String value(it.second.c_str()); + + if (key == "codec") + video->m_strCodec = value; + else if (key == "aspect") + video->m_fAspect = static_cast<float>(atof(value.c_str())); + else if (key == "width") + video->m_iWidth = strtol(value.c_str(), nullptr, 10); + else if (key == "height") + video->m_iHeight = strtol(value.c_str(), nullptr, 10); + else if (key == "duration") + video->m_iDuration = strtol(value.c_str(), nullptr, 10); + else if (key == "stereomode") + video->m_strStereoMode = value; + else if (key == "language") + video->m_strLanguage = value; + } + xbmc::InfoTagVideo::addStreamRaw(infoTag, video); + } + else if (StringUtils::CompareNoCase(cType, "audio") == 0) + { + CStreamDetailAudio* audio = new CStreamDetailAudio; + for (const auto& it : dictionary) + { + const String& key = it.first; + const String& value = it.second; + + if (key == "codec") + audio->m_strCodec = value; + else if (key == "language") + audio->m_strLanguage = value; + else if (key == "channels") + audio->m_iChannels = strtol(value.c_str(), nullptr, 10); + } + xbmc::InfoTagVideo::addStreamRaw(infoTag, audio); + } + else if (StringUtils::CompareNoCase(cType, "subtitle") == 0) + { + CStreamDetailSubtitle* subtitle = new CStreamDetailSubtitle; + for (const auto& it : dictionary) + { + const String& key = it.first; + const String& value = it.second; + + if (key == "language") + subtitle->m_strLanguage = value; + } + xbmc::InfoTagVideo::addStreamRaw(infoTag, subtitle); + } + xbmc::InfoTagVideo::finalizeStreamsRaw(infoTag); + } // end ListItem::addStreamInfo + + void ListItem::addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems /* = false */) + { + for (size_t i = 0; i < items.size(); ++i) + { + auto& tuple = items[i]; + if (tuple.GetNumValuesSet() != 2) + throw ListItemException("Must pass in a list of tuples of pairs of strings. One entry in the list only has %d elements.",tuple.GetNumValuesSet()); + + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + item->SetProperty(StringUtils::Format("contextmenulabel({})", i), tuple.first()); + item->SetProperty(StringUtils::Format("contextmenuaction({})", i), tuple.second()); + } + } + + void ListItem::setSubtitles(const std::vector<String>& paths) + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + addSubtitlesRaw(paths); + } + + xbmc::InfoTagVideo* ListItem::getVideoInfoTag() + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return new xbmc::InfoTagVideo(GetVideoInfoTag(), m_offscreen); + } + + xbmc::InfoTagMusic* ListItem::getMusicInfoTag() + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return new xbmc::InfoTagMusic(GetMusicInfoTag(), m_offscreen); + } + + xbmc::InfoTagPicture* ListItem::getPictureInfoTag() + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return new xbmc::InfoTagPicture(item->GetPictureInfoTag(), m_offscreen); + } + + xbmc::InfoTagGame* ListItem::getGameInfoTag() + { + XBMCAddonUtils::GuiLock lock(languageHook, m_offscreen); + return new xbmc::InfoTagGame(item->GetGameInfoTag(), m_offscreen); + } + + std::vector<std::string> ListItem::getStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value, + const std::string& separator) + { + if (alt.which() == first) + { + if (value.empty()) + value = alt.former(); + return StringUtils::Split(value, separator); + } + + std::vector<std::string> els; + for (const auto& el : alt.later()) + { + if (el.which() == second) + throw WrongTypeException("When using \"%s\" you need to supply a string or list of strings for the value in the dictionary", tag.c_str()); + els.emplace_back(el.former()); + } + return els; + } + + std::vector<std::string> ListItem::getVideoStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value /* = "" */) + { + return getStringArray( + alt, tag, std::move(value), + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + } + + std::vector<std::string> ListItem::getMusicStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value /* = "" */) + { + return getStringArray( + alt, tag, std::move(value), + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_musicItemSeparator); + } + + CVideoInfoTag* ListItem::GetVideoInfoTag() + { + return item->GetVideoInfoTag(); + } + + const CVideoInfoTag* ListItem::GetVideoInfoTag() const + { + return item->GetVideoInfoTag(); + } + + MUSIC_INFO::CMusicInfoTag* ListItem::GetMusicInfoTag() + { + return item->GetMusicInfoTag(); + } + + const MUSIC_INFO::CMusicInfoTag* ListItem::GetMusicInfoTag() const + { + return item->GetMusicInfoTag(); + } + + void ListItem::setTitleRaw(std::string title) + { + item->m_strTitle = std::move(title); + } + + void ListItem::setPathRaw(const std::string& path) + { + item->SetPath(path); + } + + void ListItem::setCountRaw(int count) + { + item->m_iprogramCount = count; + } + + void ListItem::setSizeRaw(int64_t size) + { + item->m_dwSize = size; + } + + void ListItem::setDateTimeRaw(const std::string& dateTime) + { + if (dateTime.length() == 10) + { + int year = strtol(dateTime.substr(dateTime.size() - 4).c_str(), nullptr, 10); + int month = strtol(dateTime.substr(3, 4).c_str(), nullptr, 10); + int day = strtol(dateTime.substr(0, 2).c_str(), nullptr, 10); + item->m_dateTime.SetDate(year, month, day); + } + else + item->m_dateTime.SetFromW3CDateTime(dateTime); + } + + void ListItem::setIsFolderRaw(bool isFolder) + { + item->m_bIsFolder = isFolder; + } + + void ListItem::setStartOffsetRaw(double startOffset) + { + // we store the offset in frames, or 1/75th of a second + item->SetStartOffset(CUtil::ConvertSecsToMilliSecs(startOffset)); + } + + void ListItem::setMimeTypeRaw(const std::string& mimetype) + { + item->SetMimeType(mimetype); + } + + void ListItem::setSpecialSortRaw(std::string specialSort) + { + StringUtils::ToLower(specialSort); + + if (specialSort == "bottom") + item->SetSpecialSort(SortSpecialOnBottom); + else if (specialSort == "top") + item->SetSpecialSort(SortSpecialOnTop); + } + + void ListItem::setContentLookupRaw(bool enable) + { + item->SetContentLookup(enable); + } + + void ListItem::addArtRaw(std::string type, const std::string& url) + { + StringUtils::ToLower(type); + item->SetArt(type, url); + } + + void ListItem::addPropertyRaw(std::string type, const CVariant& value) + { + StringUtils::ToLower(type); + item->SetProperty(type, value); + } + + void ListItem::addSubtitlesRaw(const std::vector<std::string>& subtitles) + { + for (size_t i = 0; i < subtitles.size(); ++i) + // subtitle:{} index starts from 1 + addPropertyRaw(StringUtils::Format("subtitle:{}", i + 1), subtitles[i]); + } + } +} diff --git a/xbmc/interfaces/legacy/ListItem.h b/xbmc/interfaces/legacy/ListItem.h new file mode 100644 index 0000000..82513e0 --- /dev/null +++ b/xbmc/interfaces/legacy/ListItem.h @@ -0,0 +1,1291 @@ +/* + * 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 "AddonClass.h" +#include "AddonString.h" +#include "Alternative.h" +#include "Dictionary.h" +#include "FileItem.h" +#include "InfoTagGame.h" +#include "InfoTagMusic.h" +#include "InfoTagPicture.h" +#include "InfoTagVideo.h" +#include "ListItem.h" +#include "Tuple.h" +#include "commons/Exception.h" + +#include <map> +#include <utility> +#include <vector> + + +namespace XBMCAddon +{ + namespace xbmcgui + { + XBMCCOMMONS_STANDARD_EXCEPTION(ListItemException); + + // This is a type that represents either a String or a String Tuple + typedef Alternative<StringOrInt,Tuple<String, StringOrInt> > InfoLabelStringOrTuple; + + // This type is either a String or a list of InfoLabelStringOrTuple types + typedef Alternative<StringOrInt, std::vector<InfoLabelStringOrTuple> > InfoLabelValue; + + // The type contains the dictionary values for the ListItem::setInfo call. + // The values in the dictionary can be either a String, or a list of items. + // If it's a list of items then the items can be either a String or a Tuple. + typedef Dictionary<InfoLabelValue> InfoLabelDict; + + // + /// \defgroup python_xbmcgui_listitem ListItem + /// \ingroup python_xbmcgui + /// @{ + /// @brief **Selectable window list item.** + /// + class ListItem : public AddonClass + { + public: +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + CFileItemPtr item; + bool m_offscreen; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief Selectable window list item. + /// + /// The list item control is used for creating item lists in Kodi + /// + /// \python_class{ ListItem([label, label2, path, offscreen]) } + /// + /// @param label [opt] string (default `""`) - the label to display on the item + /// @param label2 [opt] string (default `""`) - the label2 of the item + /// @param path [opt] string (default `""`) - the path for the item + /// @param offscreen [opt] bool (default `False`) - if GUI based locks should be + /// avoided. Most of the times listitems are created + /// offscreen and added later to a container + /// for display (e.g. plugins) or they are not + /// even displayed (e.g. python scrapers). + /// In such cases, there is no need to lock the + /// GUI when creating the items (increasing your addon + /// performance). + /// Note however, that if you are creating listitems + /// and managing the container itself (e.g using + /// WindowXML or WindowXMLDialog classes) subsquent + /// modifications to the item will require locking. + /// Thus, in such cases, use the default value (`False`). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v16 **iconImage** and **thumbnailImage** are deprecated. Use **setArt()**. + /// @python_v18 Added **offscreen** argument. + /// @python_v19 Removed **iconImage** and **thumbnailImage**. Use **setArt()**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem = xbmcgui.ListItem('Casino Royale') + /// ... + /// ~~~~~~~~~~~~~ + /// + ListItem([label, label2, path, offscreen]); +#else + ListItem(const String& label = emptyString, + const String& label2 = emptyString, + const String& path = emptyString, + bool offscreen = false); +#endif + +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + inline explicit ListItem(CFileItemPtr pitem) : item(std::move(pitem)), m_offscreen(false) {} + + static inline AddonClass::Ref<ListItem> fromString(const String& str) + { + AddonClass::Ref<ListItem> ret = AddonClass::Ref<ListItem>(new ListItem()); + ret->item.reset(new CFileItem(str)); + return ret; + } +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + ~ListItem() override; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getLabel() } + /// Returns the listitem label. + /// + /// @return Label of item + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getLabel() + /// label = listitem.getLabel() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel(); +#else + String getLabel(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getLabel2() } + /// Returns the second listitem label. + /// + /// @return Second label of item + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getLabel2() + /// label = listitem.getLabel2() + /// ... + /// ~~~~~~~~~~~~~ + /// + getLabel2(); +#else + String getLabel2(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setLabel(label) } + /// Sets the listitem's label. + /// + /// @param label string or unicode - text string. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setLabel(label) + /// listitem.setLabel('Casino Royale') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel(...); +#else + void setLabel(const String& label); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setLabel2(label) } + /// Sets the listitem's label2. + /// + /// @param label string or unicode - text string. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setLabel2(label) + /// listitem.setLabel2('Casino Royale') + /// ... + /// ~~~~~~~~~~~~~ + /// + setLabel2(...); +#else + void setLabel2(const String& label); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getDateTime() } + /// Returns the list item's datetime in W3C format (YYYY-MM-DDThh:mm:ssTZD). + /// + /// @return string or unicode - datetime string (W3C). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # getDateTime() + /// strDateTime = listitem.getDateTime() + /// ... + /// ~~~~~~~~~~~~~ + /// + getDateTime(); +#else + String getDateTime(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setDateTime(dateTime) } + /// Sets the list item's datetime in W3C format. + /// The following formats are supported: + /// - YYYY + /// - YYYY-MM-DD + /// - YYYY-MM-DDThh:mm[TZD] + /// - YYYY-MM-DDThh:mm:ss[TZD] + /// where the timezone (TZD) is always optional and can be in one of the + /// following formats: + /// - Z (for UTC) + /// - +hh:mm + /// - -hh:mm + /// + /// @param label string or unicode - datetime string (W3C). + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setDate(dateTime) + /// listitem.setDateTime('2021-03-09T12:30:00Z') + /// ... + /// ~~~~~~~~~~~~~ + /// + setDateTime(...); +#else + void setDateTime(const String& dateTime); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setArt(values) } + /// Sets the listitem's art + /// + /// @param values dictionary - pairs of `{ label: value }`. + /// - Some default art values (any string possible): + /// | Label | Type | + /// |:-------------:|:--------------------------------------------------| + /// | thumb | string - image filename + /// | poster | string - image filename + /// | banner | string - image filename + /// | fanart | string - image filename + /// | clearart | string - image filename + /// | clearlogo | string - image filename + /// | landscape | string - image filename + /// | icon | string - image filename + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 New function added. + /// @python_v16 Added new label **icon**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setArt(values) + /// listitem.setArt({ 'poster': 'poster.png', 'banner' : 'banner.png' }) + /// ... + /// ~~~~~~~~~~~~~ + /// + setArt(...); +#else + void setArt(const Properties& dictionary); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setIsFolder(isFolder) } + /// Sets if this listitem is a folder. + /// + /// @param isFolder bool - True=folder / False=not a folder (default). + /// + /// + ///----------------------------------------------------------------------- + /// + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setIsFolder(isFolder) + /// listitem.setIsFolder(True) + /// ... + /// ~~~~~~~~~~~~~ + /// + setIsFolder(...); +#else + void setIsFolder(bool isFolder); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setUniqueIDs(values, defaultrating) } + /// Sets the listitem's uniqueID + /// + /// @param values dictionary - pairs of `{ label: value }`. + /// @param defaultrating [opt] string - the name of default rating. + /// + /// - Some example values (any string possible): + /// | Label | Type | + /// |:-------------:|:--------------------------------------------------| + /// | imdb | string - uniqueid name + /// | tvdb | string - uniqueid name + /// | tmdb | string - uniqueid name + /// | anidb | string - uniqueid name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.setUniqueIDs()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setUniqueIDs(values, defaultrating) + /// listitem.setUniqueIDs({ 'imdb': 'tt8938399', 'tmdb' : '9837493' }, "imdb") + /// ... + /// ~~~~~~~~~~~~~ + /// + setUniqueIDs(...); +#else + void setUniqueIDs(const Properties& dictionary, const String& defaultrating = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setRating(type, rating, votes = 0, defaultt = False) } + /// Sets a listitem's rating. It needs at least type and rating param + /// + /// @param type string - the type of the rating. Any string. + /// @param rating float - the value of the rating. + /// @param votes int - the number of votes. Default 0. + /// @param defaultt bool - is the default rating?. Default False. + /// - Some example type (any string possible): + /// | Label | Type | + /// |:-------------:|:--------------------------------------------------| + /// | imdb | string - rating type + /// | tvdb | string - rating type + /// | tmdb | string - rating type + /// | anidb | string - rating type + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.setRating()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setRating(type, rating, votes, defaultt)) + /// listitem.setRating("imdb", 4.6, 8940, True) + /// ... + /// ~~~~~~~~~~~~~ + /// + setRating(...); +#else + void setRating(const std::string& type, float rating, int votes = 0, bool defaultt = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ addSeason(number, name = "") } + /// Add a season with name to a listitem. It needs at least the season number + /// + /// @param number int - the number of the season. + /// @param name string - the name of the season. Default "". + /// + /// + ///----------------------------------------------------------------------- + /// + /// @python_v18 New function added. + /// @python_v20 Deprecated. Use **InfoTagVideo.addSeason()** or **InfoTagVideo.addSeasons()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # addSeason(number, name)) + /// listitem.addSeason(1, "Murder House") + /// ... + /// ~~~~~~~~~~~~~ + /// + addSeason(...); +#else + void addSeason(int number, std::string name = ""); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getArt(key) } + /// Returns a listitem art path as a string, similar to an infolabel.\n + /// + /// @param key string - art name. + /// - Some default art values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | thumb | string - image path + /// | poster | string - image path + /// | banner | string - image path + /// | fanart | string - image path + /// | clearart | string - image path + /// | clearlogo | string - image path + /// | landscape | string - image path + /// | icon | string - image path + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// poster = listitem.getArt('poster') + /// ... + /// ~~~~~~~~~~~~~ + /// + getArt(key); +#else + String getArt(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ isFolder() } + /// Returns whether the item is a folder or not. + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// isFolder = listitem.isFolder() + /// ... + /// ~~~~~~~~~~~~~ + /// + isFolder(); +#else + bool isFolder() const; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getUniqueID(key) } + /// Returns a listitem uniqueID as a string, similar to an infolabel.\n + /// + /// @param key string - uniqueID name. + /// - Some default uniqueID values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - uniqueid name + /// | tvdb | string - uniqueid name + /// | tmdb | string - uniqueid name + /// | anidb | string - uniqueid name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.getUniqueID()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// uniqueID = listitem.getUniqueID('imdb') + /// ... + /// ~~~~~~~~~~~~~ + /// + getUniqueID(key); +#else + String getUniqueID(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getRating(key) } + /// Returns a listitem rating as a float.\n + /// + /// @param key string - rating type. + /// - Some default key values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - type name + /// | tvdb | string - type name + /// | tmdb | string - type name + /// | anidb | string - type name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.getRating()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// rating = listitem.getRating('imdb') + /// ... + /// ~~~~~~~~~~~~~ + /// + getRating(key); +#else + float getRating(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getVotes(key) } + /// Returns a listitem votes as a integer.\n + /// + /// @param key string - rating type. + /// - Some default key values (any string possible): + /// | Label | Type | + /// |---------------|--------------------------------------------------| + /// | imdb | string - type name + /// | tvdb | string - type name + /// | tmdb | string - type name + /// | anidb | string - type name + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.getVotesAsInt()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// votes = listitem.getVotes('imdb') + /// ... + /// ~~~~~~~~~~~~~ + /// + getVotes(key); +#else + int getVotes(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ select(selected) } + /// Sets the listitem's selected status. + /// + /// @param selected bool - True=selected/False=not selected + /// + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # select(selected) + /// listitem.select(True) + /// ... + /// ~~~~~~~~~~~~~ + /// + select(...); +#else + void select(bool selected); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ isSelected() } + /// Returns the listitem's selected status. + /// + /// + /// @return bool - true if selected, otherwise false + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # isSelected() + /// selected = listitem.isSelected() + /// ... + /// ~~~~~~~~~~~~~ + /// + isSelected(); +#else + bool isSelected(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setInfo(type, infoLabels) } + /// Sets the listitem's infoLabels. + /// + /// @param type string - type of info labels + /// @param infoLabels dictionary - pairs of `{ label: value }` + /// + /// __Available types__ + /// | Command name | Description | + /// |:------------:|:----------------------| + /// | video | Video information + /// | music | Music information + /// | pictures | Pictures informanion + /// | game | Game information + /// + /// @note To set pictures exif info, prepend `exif:` to the label. Exif values must be passed + /// as strings, separate value pairs with a comma. <b>(eg. <c>{'exif:resolution': '720,480'}</c></b> + /// See \ref kodi_pictures_infotag for valid strings.\n + /// \n + /// You can use the above as keywords for arguments and skip certain optional arguments. + /// Once you use a keyword, all following arguments require the keyword. + /// + /// __General Values__ (that apply to all types): + /// | Info label | Description | + /// |--------------:|:---------------------------------------------------| + /// | count | integer (12) - can be used to store an id for later, or for sorting purposes + /// | size | long (1024) - size in bytes + /// | date | string (%d.%m.%Y / 01.01.2009) - file date + /// + /// __Video Values__: + /// | Info label | Description | + /// |--------------:|:---------------------------------------------------| + /// | genre | string (Comedy) or list of strings (["Comedy", "Animation", "Drama"]) + /// | country | string (Germany) or list of strings (["Germany", "Italy", "France"]) + /// | year | integer (2009) + /// | episode | integer (4) + /// | season | integer (1) + /// | sortepisode | integer (4) + /// | sortseason | integer (1) + /// | episodeguide | string (Episode guide) + /// | showlink | string (Battlestar Galactica) or list of strings (["Battlestar Galactica", "Caprica"]) + /// | top250 | integer (192) + /// | setid | integer (14) + /// | tracknumber | integer (3) + /// | rating | float (6.4) - range is 0..10 + /// | userrating | integer (9) - range is 1..10 (0 to reset) + /// | watched | deprecated - use playcount instead + /// | playcount | integer (2) - number of times this item has been played + /// | overlay | integer (2) - range is `0..7`. See \ref kodi_guilib_listitem_iconoverlay "Overlay icon types" for values + /// | cast | list (["Michal C. Hall","Jennifer Carpenter"]) - if provided a list of tuples cast will be interpreted as castandrole + /// | castandrole | list of tuples ([("Michael C. Hall","Dexter"),("Jennifer Carpenter","Debra")]) + /// | director | string (Dagur Kari) or list of strings (["Dagur Kari", "Quentin Tarantino", "Chrstopher Nolan"]) + /// | mpaa | string (PG-13) + /// | plot | string (Long Description) + /// | plotoutline | string (Short Description) + /// | title | string (Big Fan) + /// | originaltitle | string (Big Fan) + /// | sorttitle | string (Big Fan) + /// | duration | integer (245) - duration in seconds + /// | studio | string (Warner Bros.) or list of strings (["Warner Bros.", "Disney", "Paramount"]) + /// | tagline | string (An awesome movie) - short description of movie + /// | writer | string (Robert D. Siegel) or list of strings (["Robert D. Siegel", "Jonathan Nolan", "J.K. Rowling"]) + /// | tvshowtitle | string (Heroes) + /// | premiered | string (2005-03-04) + /// | status | string (Continuing) - status of a TVshow + /// | set | string (Batman Collection) - name of the collection + /// | setoverview | string (All Batman movies) - overview of the collection + /// | tag | string (cult) or list of strings (["cult", "documentary", "best movies"]) - movie tag + /// | imdbnumber | string (tt0110293) - IMDb code + /// | code | string (101) - Production code + /// | aired | string (2008-12-07) + /// | credits | string (Andy Kaufman) or list of strings (["Dagur Kari", "Quentin Tarantino", "Chrstopher Nolan"]) - writing credits + /// | lastplayed | string (%Y-%m-%d %h:%m:%s = 2009-04-05 23:16:04) + /// | album | string (The Joshua Tree) + /// | artist | list (['U2']) + /// | votes | string (12345 votes) + /// | path | string (/home/user/movie.avi) + /// | trailer | string (/home/user/trailer.avi) + /// | dateadded | string (%Y-%m-%d %h:%m:%s = 2009-04-05 23:16:04) + /// | mediatype | string - "video", "movie", "tvshow", "season", "episode" or "musicvideo" + /// | dbid | integer (23) - Only add this for items which are part of the local db. You also need to set the correct 'mediatype'! + /// + /// __Music Values__: + /// | Info label | Description | + /// |-------------------------:|:---------------------------------------------------| + /// | tracknumber | integer (8) + /// | discnumber | integer (2) + /// | duration | integer (245) - duration in seconds + /// | year | integer (1998) + /// | genre | string (Rock) + /// | album | string (Pulse) + /// | artist | string (Muse) + /// | title | string (American Pie) + /// | rating | float - range is between 0 and 10 + /// | userrating | integer - range is 1..10 + /// | lyrics | string (On a dark desert highway...) + /// | playcount | integer (2) - number of times this item has been played + /// | lastplayed | string (%Y-%m-%d %h:%m:%s = 2009-04-05 23:16:04) + /// | mediatype | string - "music", "song", "album", "artist" + /// | dbid | integer (23) - Only add this for items which are part of the local db. You also need to set the correct 'mediatype'! + /// | listeners | integer (25614) + /// | musicbrainztrackid | string (cd1de9af-0b71-4503-9f96-9f5efe27923c) + /// | musicbrainzartistid | string (d87e52c5-bb8d-4da8-b941-9f4928627dc8) + /// | musicbrainzalbumid | string (24944755-2f68-3778-974e-f572a9e30108) + /// | musicbrainzalbumartistid | string (d87e52c5-bb8d-4da8-b941-9f4928627dc8) + /// | comment | string (This is a great song) + /// + /// __Picture Values__: + /// | Info label | Description | + /// |--------------:|:---------------------------------------------------| + /// | title | string (In the last summer-1) + /// | picturepath | string (`/home/username/pictures/img001.jpg`) + /// | exif* | string (See \ref kodi_pictures_infotag for valid strings) + /// + /// __Game Values__: + /// | Info label | Description | + /// |--------------:|:---------------------------------------------------| + /// | title | string (Super Mario Bros.) + /// | platform | string (Atari 2600) + /// | genres | list (["Action","Strategy"]) + /// | publisher | string (Nintendo) + /// | developer | string (Square) + /// | overview | string (Long Description) + /// | year | integer (1980) + /// | gameclient | string (game.libretro.fceumm) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 Added new label **discnumber**. + /// @python_v15 **duration** has to be set in seconds. + /// @python_v16 Added new label **mediatype**. + /// @python_v17 + /// Added labels **setid**, **set**, **imdbnumber**, **code**, **dbid** (video), **path** and **userrating**. + /// Expanded the possible infoLabels for the option **mediatype**. + /// @python_v18 Added new **game** type and associated infolabels. + /// Added labels **dbid** (music), **setoverview**, **tag**, **sortepisode**, **sortseason**, **episodeguide**, **showlink**. + /// Extended labels **genre**, **country**, **director**, **studio**, **writer**, **tag**, **credits** to also use a list of strings. + /// @python_v20 Partially deprecated. Use explicit setters in **InfoTagVideo**, **InfoTagMusic**, **InfoTagPicture** or **InfoTagGame** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.setInfo('video', { 'genre': 'Comedy' }) + /// ... + /// ~~~~~~~~~~~~~ + /// + setInfo(...); +#else + void setInfo(const char* type, const InfoLabelDict& infoLabels); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setCast(actors) } + /// Set cast including thumbnails + /// + /// @param actors list of dictionaries (see below for relevant keys) + /// + /// - Keys: + /// | Label | Description | + /// |--------------:|:------------------------------------------------| + /// | name | string (Michael C. Hall) + /// | role | string (Dexter) + /// | thumbnail | string (http://www.someurl.com/someimage.png) + /// | order | integer (1) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// @python_v20 Deprecated. Use **InfoTagVideo.setCast()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// actors = [{"name": "Actor 1", "role": "role 1"}, {"name": "Actor 2", "role": "role 2"}] + /// listitem.setCast(actors) + /// ... + /// ~~~~~~~~~~~~~ + /// + setCast(...); +#else + void setCast(const std::vector<Properties>& actors); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setAvailableFanart(images) } + /// Set available images (needed for video scrapers) + /// + /// @param images list of dictionaries (see below for relevant keys) + /// + /// - Keys: + /// | Label | Description | + /// |--------------:|:------------------------------------------------| + /// | image | string (http://www.someurl.com/someimage.png) + /// | preview | [opt] string (http://www.someurl.com/somepreviewimage.png) + /// | colors | [opt] string (either comma separated Kodi hex values ("FFFFFFFF,DDDDDDDD") or TVDB RGB Int Triplets ("|68,69,59|69,70,58|78,78,68|")) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// fanart = [{"image": path_to_image_1, "preview": path_to_preview_1}, {"image": path_to_image_2, "preview": path_to_preview_2}] + /// listitem.setAvailableFanart(fanart) + /// ... + /// ~~~~~~~~~~~~~ + /// + setAvailableFanart(...); +#else + void setAvailableFanart(const std::vector<Properties>& images); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ addAvailableArtwork(images) } + /// Add an image to available artworks (needed for video scrapers) + /// + /// @param url string (image path url) + /// @param art_type string (image type) + /// @param preview [opt] string (image preview path url) + /// @param referrer [opt] string (referrer url) + /// @param cache [opt] string (filename in cache) + /// @param post [opt] bool (use post to retrieve the image, default false) + /// @param isgz [opt] bool (use gzip to retrieve the image, default false) + /// @param season [opt] integer (number of season in case of season thumb) + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// @python_v19 New param added (preview). + /// @python_v20 Deprecated. Use **InfoTagVideo.addAvailableArtwork()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.addAvailableArtwork(path_to_image_1, "thumb") + /// ... + /// ~~~~~~~~~~~~~ + /// + addAvailableArtwork(...); +#else + void addAvailableArtwork(const std::string& url, + const std::string& art_type = "", + const std::string& preview = "", + const std::string& referrer = "", + const std::string& cache = "", + bool post = false, + bool isgz = false, + int season = -1); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ addStreamInfo(type, values) } + /// Add a stream with details. + /// + /// @param type string - type of stream(video/audio/subtitle). + /// @param values dictionary - pairs of { label: value }. + /// + /// - Video Values: + /// | Label | Description | + /// |--------------:|:------------------------------------------------| + /// | codec | string (h264) + /// | aspect | float (1.78) + /// | width | integer (1280) + /// | height | integer (720) + /// | duration | integer (seconds) + /// + /// - Audio Values: + /// | Label | Description | + /// |--------------:|:------------------------------------------------| + /// | codec | string (dts) + /// | language | string (en) + /// | channels | integer (2) + /// + /// - Subtitle Values: + /// | Label | Description | + /// |--------------:|:------------------------------------------------| + /// | language | string (en) + /// + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 Deprecated. Use **InfoTagVideo.addVideoStream()**, **InfoTagVideo.addAudioStream()** or **InfoTagVideo.addSubtitleStream()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.addStreamInfo('video', { 'codec': 'h264', 'width' : 1280 }) + /// ... + /// ~~~~~~~~~~~~~ + /// + addStreamInfo(...); +#else + void addStreamInfo(const char* cType, const Properties& dictionary); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ addContextMenuItems([(label, action),*]) } + /// Adds item(s) to the context menu for media lists. + /// + /// @param items list - [(label, action),*] A list of tuples consisting of label and action pairs. + /// - label string or unicode - item's label. + /// - action string or unicode - any available \link page_List_of_built_in_functions built-in function \endlink . + /// + /// @note You can use the above as keywords for arguments and skip certain optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 Completely removed previously available argument **replaceItems**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.addContextMenuItems([('Theater Showtimes', 'RunScript(script.myaddon,title=Iron Man)')]) + /// ... + /// ~~~~~~~~~~~~~ + /// + addContextMenuItems(...); +#else + void addContextMenuItems(const std::vector<Tuple<String,String> >& items, bool replaceItems = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setProperty(key, value) } + /// Sets a listitem property, similar to an infolabel. + /// + /// @param key string - property name. + /// @param value string or unicode - value of property. + /// + /// @note Key is NOT case sensitive.\n + /// You can use the above as keywords for arguments and skip certain optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword.\n + /// \n + /// Some of these are treated internally by Kodi, such as the 'StartOffset' property, which is + /// the offset in seconds at which to start playback of an item. Others may be used in the skin + /// to add extra information, such as 'WatchedCount' for tvshow items + /// + /// - **Internal Properties** + /// | Key | Description | + /// |--------------:|:------------------------------------------------| + /// | inputstream | string (inputstream.adaptive) - Set the inputstream add-on that will be used to play the item + /// | IsPlayable | string - "true", "false" - Mark the item as playable, **mandatory for playable items** + /// | MimeType | string (application/x-mpegURL) - Set the MimeType of the item before playback + /// | ResumeTime | float (1962.0) - Set the resume point of the item in seconds + /// | SpecialSort | string - "top", "bottom" - The item will remain at the top or bottom of the current list + /// | StartOffset | float (60.0) - Set the offset in seconds at which to start playback of the item + /// | StartPercent | float (15.0) - Set the percentage at which to start playback of the item + /// | StationName | string ("My Station Name") - Used to enforce/override MusicPlayer.StationName infolabel from addons (e.g. in radio addons) + /// | TotalTime | float (7848.0) - Set the total time of the item in seconds + /// | OverrideInfotag | string - "true", "false" - When true will override all info from previous listitem + /// | ForceResolvePlugin | string - "true", "false" - When true ensures that a plugin will always receive callbacks to resolve paths (useful for playlist cases) + /// | rtsp_transport | string - "udp", "udp_multicast" or "tcp" - Allow to force the rtsp transport mode for rtsp streams + /// + ///----------------------------------------------------------------------- + /// @python_v20 OverrideInfotag property added + /// @python_v20 **ResumeTime** and **TotalTime** deprecated. Use **InfoTagVideo.setResumePoint()** instead. + /// @python_v20 ForceResolvePlugin property added + /// @python_v20 rtsp_transport property added + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.setProperty('AspectRatio', '1.85 : 1') + /// listitem.setProperty('StartOffset', '256.4') + /// ... + /// ~~~~~~~~~~~~~ + /// + setProperty(...); +#else + void setProperty(const char * key, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setProperties(values) } + /// Sets multiple properties for listitem's + /// + /// @param values dictionary - pairs of `{ label: value }`. + /// + /// @python_v18 New function added. + /// + ///----------------------------------------------------------------------- + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// # setProperties(values) + /// listitem.setProperties({ 'AspectRatio': '1.85', 'StartOffset' : '256.4' }) + /// ... + /// ~~~~~~~~~~~~~ + /// + setProperties(...); +#else + void setProperties(const Properties& dictionary); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getProperty(key) } + /// Returns a listitem property as a string, similar to an infolabel. + /// + /// @param key string - property name. + /// + /// @note Key is NOT case sensitive.\n + /// You can use the above as keywords for arguments and skip certain optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 **ResumeTime** and **TotalTime** deprecated. Use **InfoTagVideo.getResumeTime()** and **InfoTagVideo.getResumeTimeTotal()** instead. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// AspectRatio = listitem.getProperty('AspectRatio') + /// ... + /// ~~~~~~~~~~~~~ + /// + getProperty(...); +#else + String getProperty(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setPath(path) } + /// Sets the listitem's path. + /// + /// @param path string or unicode - path, activated when item is clicked. + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.setPath(path='/path/to/some/file.ext') + /// ... + /// ~~~~~~~~~~~~~ + /// + setPath(...); +#else + void setPath(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setMimeType(mimetype) } + /// Sets the listitem's mimetype if known. + /// + /// @param mimetype string or unicode - mimetype + /// + /// If known prehand, this can (but does not have to) avoid HEAD requests + /// being sent to HTTP servers to figure out file type. + /// + setMimeType(...); +#else + void setMimeType(const String& mimetype); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setContentLookup(enable) } + /// Enable or disable content lookup for item. + /// + /// If disabled, HEAD requests to e.g determine mime type will not be sent. + /// + /// @param enable bool to enable content lookup + /// + /// + ///----------------------------------------------------------------------- + /// @python_v16 New function added. + /// + setContentLookup(...); +#else + void setContentLookup(bool enable); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ setSubtitles(subtitleFiles) } + /// Sets subtitles for this listitem. + /// + /// @param subtitleFiles list with path to subtitle files + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem.setSubtitles(['special://temp/example.srt', 'http://example.com/example.srt']) + /// ... + /// ~~~~~~~~~~~~~ + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + setSubtitles(...); +#else + void setSubtitles(const std::vector<String>& subtitleFiles); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getPath() } + /// Returns the path of this listitem. + /// + /// @return [string] filename + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 New function added. + /// + /// + getPath(); +#else + String getPath(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getVideoInfoTag() } + /// Returns the VideoInfoTag for this item. + /// + /// @return video info tag + /// + /// + ///----------------------------------------------------------------------- + /// @python_v15 New function added. + /// + getVideoInfoTag(); +#else + xbmc::InfoTagVideo* getVideoInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getMusicInfoTag() } + /// Returns the MusicInfoTag for this item. + /// + /// @return music info tag + /// + /// + ///----------------------------------------------------------------------- + /// @python_v15 New function added. + /// + getMusicInfoTag(); +#else + xbmc::InfoTagMusic* getMusicInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getPictureInfoTag() } + /// Returns the InfoTagPicture for this item. + /// + /// @return picture info tag + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getPictureInfoTag(); +#else + xbmc::InfoTagPicture* getPictureInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_listitem + /// @brief \python_func{ getGameInfoTag() } + /// Returns the InfoTagGame for this item. + /// + /// @return game info tag + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getGameInfoTag(); +#else + xbmc::InfoTagGame* getGameInfoTag(); +#endif + +private: + std::vector<std::string> getStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value, + const std::string& separator); + std::vector<std::string> getVideoStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value = ""); + std::vector<std::string> getMusicStringArray(const InfoLabelValue& alt, + const std::string& tag, + std::string value = ""); + + CVideoInfoTag* GetVideoInfoTag(); + const CVideoInfoTag* GetVideoInfoTag() const; + + MUSIC_INFO::CMusicInfoTag* GetMusicInfoTag(); + const MUSIC_INFO::CMusicInfoTag* GetMusicInfoTag() const; + + void setTitleRaw(std::string title); + void setPathRaw(const std::string& path); + void setCountRaw(int count); + void setSizeRaw(int64_t size); + void setDateTimeRaw(const std::string& dateTime); + void setIsFolderRaw(bool isFolder); + void setStartOffsetRaw(double startOffset); + void setMimeTypeRaw(const std::string& mimetype); + void setSpecialSortRaw(std::string specialSort); + void setContentLookupRaw(bool enable); + void addArtRaw(std::string type, const std::string& url); + void addPropertyRaw(std::string type, const CVariant& value); + void addSubtitlesRaw(const std::vector<std::string>& subtitles); + }; + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + typedef std::vector<ListItem*> ListItemList; +#endif + } +} diff --git a/xbmc/interfaces/legacy/ModuleXbmc.cpp b/xbmc/interfaces/legacy/ModuleXbmc.cpp new file mode 100644 index 0000000..b9e4c05 --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmc.cpp @@ -0,0 +1,600 @@ +/* + * 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. + */ + +//! @todo Need a uniform way of returning an error status + +#include "ModuleXbmc.h" + +#include "AddonUtils.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "LangInfo.h" +#include "LanguageHook.h" +#include "ServiceBroker.h" +#include "Util.h" +#include "aojsonrpc.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPowerHandling.h" +#include "cores/AudioEngine/Interfaces/AE.h" +#include "guilib/GUIAudioManager.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/TextureManager.h" +#include "input/WindowTranslator.h" +#include "messaging/ApplicationMessenger.h" +#include "network/Network.h" +#include "network/NetworkServices.h" +#include "playlists/PlayListTypes.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "storage/MediaManager.h" +#include "storage/discs/IDiscDriveHandler.h" +#include "threads/SystemClock.h" +#include "utils/Crc32.h" +#include "utils/ExecString.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/LangCodeExpander.h" +#include "utils/MemUtils.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" + +#include <vector> + +using namespace KODI; + +namespace XBMCAddon +{ + + namespace xbmc + { + /***************************************************************** + * start of xbmc methods + *****************************************************************/ + void log(const char* msg, int level) + { + // check for a valid loglevel + if (level < LOGDEBUG || level > LOGNONE) + level = LOGDEBUG; + CLog::Log(level, "{}", msg); + } + + void shutdown() + { + XBMC_TRACE; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_SHUTDOWN); + } + + void restart() + { + XBMC_TRACE; + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_RESTART); + } + + void executescript(const char* script) + { + XBMC_TRACE; + if (! script) + return; + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_SCRIPT, -1, -1, nullptr, script); + } + + void executebuiltin(const char* function, bool wait /* = false*/) + { + XBMC_TRACE; + if (! function) + return; + + // builtins is no anarchy + // enforce some rules here + // DialogBusy must not be activated, it is modal dialog + const CExecString exec(function); + if (!exec.IsValid()) + return; + + const std::string execute = exec.GetFunction(); + const std::vector<std::string> params = exec.GetParams(); + + if (StringUtils::EqualsNoCase(execute, "activatewindow") || + StringUtils::EqualsNoCase(execute, "closedialog")) + { + int win = CWindowTranslator::TranslateWindow(params[0]); + if (win == WINDOW_DIALOG_BUSY) + { + CLog::Log(LOGWARNING, "addons must not activate DialogBusy"); + return; + } + } + + if (wait) + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + function); + else + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + function); + } + + String executeJSONRPC(const char* jsonrpccommand) + { + XBMC_TRACE; + DelayedCallGuard dg; + String ret; + + if (! jsonrpccommand) + return ret; + + // String method = jsonrpccommand; + + CAddOnTransport transport; + CAddOnTransport::CAddOnClient client; + + return JSONRPC::CJSONRPC::MethodCall(/*method*/ jsonrpccommand, &transport, &client); + } + + void sleep(long timemillis) + { + XBMC_TRACE; + + XbmcThreads::EndTime<> endTime{std::chrono::milliseconds(timemillis)}; + while (!endTime.IsTimePast()) + { + LanguageHook* lh = NULL; + { + DelayedCallGuard dcguard; + lh = dcguard.getLanguageHook(); // borrow this + long nextSleep = endTime.GetTimeLeft().count(); + if (nextSleep > 100) + nextSleep = 100; // only sleep for 100 millis + KODI::TIME::Sleep(std::chrono::milliseconds(nextSleep)); + } + if (lh != NULL) + lh->MakePendingCalls(); + } + } + + String getLocalizedString(int id) + { + XBMC_TRACE; + String label; + if (id >= 30000 && id <= 30999) + label = g_localizeStringsTemp.Get(id); + else if (id >= 32000 && id <= 32999) + label = g_localizeStringsTemp.Get(id); + else + label = g_localizeStrings.Get(id); + + return label; + } + + String getSkinDir() + { + XBMC_TRACE; + return CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_LOOKANDFEEL_SKIN); + } + + String getLanguage(int format /* = CLangCodeExpander::ENGLISH_NAME */, bool region /*= false*/) + { + XBMC_TRACE; + std::string lang = g_langInfo.GetEnglishLanguageName(); + + switch (format) + { + case CLangCodeExpander::ENGLISH_NAME: + { + if (region) + { + std::string region = "-" + g_langInfo.GetCurrentRegion(); + return (lang += region); + } + return lang; + } + case CLangCodeExpander::ISO_639_1: + { + std::string langCode; + g_LangCodeExpander.ConvertToISO6391(lang, langCode); + if (region) + { + std::string region = g_langInfo.GetRegionLocale(); + std::string region2Code; + g_LangCodeExpander.ConvertToISO6391(region, region2Code); + region2Code = "-" + region2Code; + return (langCode += region2Code); + } + return langCode; + } + case CLangCodeExpander::ISO_639_2: + { + std::string langCode; + g_LangCodeExpander.ConvertToISO6392B(lang, langCode); + if (region) + { + std::string region = g_langInfo.GetRegionLocale(); + std::string region3Code; + g_LangCodeExpander.ConvertToISO6392B(region, region3Code); + region3Code = "-" + region3Code; + return (langCode += region3Code); + } + + return langCode; + } + default: + return ""; + } + } + + String getIPAddress() + { + XBMC_TRACE; + char cTitleIP[32]; + sprintf(cTitleIP, "127.0.0.1"); + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface) + return iface->GetCurrentIPAddress(); + + return cTitleIP; + } + + long getDVDState() + { + XBMC_TRACE; + return static_cast<long>(CServiceBroker::GetMediaManager().GetDriveStatus()); + } + + long getFreeMem() + { + XBMC_TRACE; + KODI::MEMORY::MemoryStatus stat; + KODI::MEMORY::GetMemoryStatus(&stat); + return static_cast<long>(stat.availPhys / ( 1024 * 1024 )); + } + + // getCpuTemp() method + // ## Doesn't work right, use getInfoLabel('System.CPUTemperature') instead. + /*PyDoc_STRVAR(getCpuTemp__doc__, + "getCpuTemp() -- Returns the current cpu temperature as an integer." + "" + "example:" + " - cputemp = xbmc.getCpuTemp()"); + + PyObject* XBMC_GetCpuTemp(PyObject *self, PyObject *args) + { + unsigned short cputemp; + unsigned short cpudec; + + _outp(0xc004, (0x4c<<1)|0x01); + _outp(0xc008, 0x01); + _outpw(0xc000, _inpw(0xc000)); + _outp(0xc002, (0) ? 0x0b : 0x0a); + while ((_inp(0xc000) & 8)); + cputemp = _inpw(0xc006); + + _outp(0xc004, (0x4c<<1)|0x01); + _outp(0xc008, 0x10); + _outpw(0xc000, _inpw(0xc000)); + _outp(0xc002, (0) ? 0x0b : 0x0a); + while ((_inp(0xc000) & 8)); + cpudec = _inpw(0xc006); + + if (cpudec<10) cpudec = cpudec * 100; + if (cpudec<100) cpudec = cpudec *10; + + return PyInt_FromLong((long)(cputemp + cpudec / 1000.0f)); + }*/ + + String getInfoLabel(const char* cLine) + { + XBMC_TRACE; + if (!cLine) + { + String ret; + return ret; + } + + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + int ret = infoMgr.TranslateString(cLine); + //doesn't seem to be a single InfoTag? + //try full blown GuiInfoLabel then + if (ret == 0) + return GUILIB::GUIINFO::CGUIInfoLabel::GetLabel(cLine, INFO::DEFAULT_CONTEXT); + else + return infoMgr.GetLabel(ret, INFO::DEFAULT_CONTEXT); + } + + String getInfoImage(const char * infotag) + { + XBMC_TRACE; + if (!infotag) + { + String ret; + return ret; + } + + CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); + int ret = infoMgr.TranslateString(infotag); + return infoMgr.GetImage(ret, WINDOW_INVALID); + } + + void playSFX(const char* filename, bool useCached) + { + XBMC_TRACE; + if (!filename) + return; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (CFileUtils::Exists(filename) && gui) + { + gui->GetAudioManager().PlayPythonSound(filename,useCached); + } + } + + void stopSFX() + { + XBMC_TRACE; + DelayedCallGuard dg; + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Stop(); + } + + void enableNavSounds(bool yesNo) + { + XBMC_TRACE; + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (gui) + gui->GetAudioManager().Enable(yesNo); + } + + bool getCondVisibility(const char *condition) + { + XBMC_TRACE; + if (!condition) + return false; + + bool ret; + { + XBMCAddonUtils::GuiLock lock(nullptr, false); + + int id = CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(); + if (id == WINDOW_INVALID) + id = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + ret = CServiceBroker::GetGUI()->GetInfoManager().EvaluateBool(condition,id); + } + + return ret; + } + + int getGlobalIdleTime() + { + XBMC_TRACE; + auto& components = CServiceBroker::GetAppComponents(); + const auto appPower = components.GetComponent<CApplicationPowerHandling>(); + return appPower->GlobalIdleTime(); + } + + String getCacheThumbName(const String& path) + { + XBMC_TRACE; + auto crc = Crc32::ComputeFromLowerCase(path); + return StringUtils::Format("{:08x}.tbn", crc); + } + + Tuple<String,String> getCleanMovieTitle(const String& path, bool usefoldername) + { + XBMC_TRACE; + CFileItem item(path, false); + std::string strName = item.GetMovieName(usefoldername); + + std::string strTitleAndYear; + std::string strTitle; + std::string strYear; + CUtil::CleanString(strName, strTitle, strTitleAndYear, strYear, usefoldername); + return Tuple<String,String>(strTitle,strYear); + } + + String getRegion(const char* id) + { + XBMC_TRACE; + std::string result; + + if (StringUtils::CompareNoCase(id, "datelong") == 0) + { + result = g_langInfo.GetDateFormat(true); + StringUtils::Replace(result, "DDDD", "%A"); + StringUtils::Replace(result, "MMMM", "%B"); + StringUtils::Replace(result, "D", "%d"); + StringUtils::Replace(result, "YYYY", "%Y"); + } + else if (StringUtils::CompareNoCase(id, "dateshort") == 0) + { + result = g_langInfo.GetDateFormat(false); + StringUtils::Replace(result, "MM", "%m"); + StringUtils::Replace(result, "DD", "%d"); +#ifdef TARGET_WINDOWS + StringUtils::Replace(result, "M", "%#m"); + StringUtils::Replace(result, "D", "%#d"); +#else + StringUtils::Replace(result, "M", "%-m"); + StringUtils::Replace(result, "D", "%-d"); +#endif + StringUtils::Replace(result, "YYYY", "%Y"); + } + else if (StringUtils::CompareNoCase(id, "tempunit") == 0) + result = g_langInfo.GetTemperatureUnitString(); + else if (StringUtils::CompareNoCase(id, "speedunit") == 0) + result = g_langInfo.GetSpeedUnitString(); + else if (StringUtils::CompareNoCase(id, "time") == 0) + { + result = g_langInfo.GetTimeFormat(); + if (StringUtils::StartsWith(result, "HH")) + StringUtils::Replace(result, "HH", "%H"); + else + StringUtils::Replace(result, "H", "%H"); + StringUtils::Replace(result, "h", "%I"); + StringUtils::Replace(result, "mm", "%M"); + StringUtils::Replace(result, "ss", "%S"); + StringUtils::Replace(result, "xx", "%p"); + } + else if (StringUtils::CompareNoCase(id, "meridiem") == 0) + result = StringUtils::Format("{}/{}", g_langInfo.GetMeridiemSymbol(MeridiemSymbolAM), + g_langInfo.GetMeridiemSymbol(MeridiemSymbolPM)); + + return result; + } + + //! @todo Add a mediaType enum + String getSupportedMedia(const char* mediaType) + { + XBMC_TRACE; + String result; + if (StringUtils::CompareNoCase(mediaType, "video") == 0) + result = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + else if (StringUtils::CompareNoCase(mediaType, "music") == 0) + result = CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + else if (StringUtils::CompareNoCase(mediaType, "picture") == 0) + result = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + + //! @todo implement + // else + // return an error + + return result; + } + + bool skinHasImage(const char* image) + { + XBMC_TRACE; + return CServiceBroker::GetGUI()->GetTextureManager().HasTexture(image); + } + + + bool startServer(int iTyp, bool bStart) + { + XBMC_TRACE; + DelayedCallGuard dg; + return CServiceBroker::GetNetwork().GetServices().StartServer( + static_cast<CNetworkServices::ESERVERS>(iTyp), bStart != 0); + } + + void audioSuspend() + { + IAE *ae = CServiceBroker::GetActiveAE(); + if (ae) + ae->Suspend(); + } + + void audioResume() + { + IAE *ae = CServiceBroker::GetActiveAE(); + if (ae) + ae->Resume(); + } + + String convertLanguage(const char* language, int format) + { + std::string convertedLanguage; + switch (format) + { + case CLangCodeExpander::ENGLISH_NAME: + { + g_LangCodeExpander.Lookup(language, convertedLanguage); + // maybe it's a check whether the language exists or not + if (convertedLanguage.empty()) + { + g_LangCodeExpander.ConvertToISO6392B(language, convertedLanguage); + g_LangCodeExpander.Lookup(convertedLanguage, convertedLanguage); + } + break; + } + case CLangCodeExpander::ISO_639_1: + g_LangCodeExpander.ConvertToISO6391(language, convertedLanguage); + break; + case CLangCodeExpander::ISO_639_2: + g_LangCodeExpander.ConvertToISO6392B(language, convertedLanguage); + break; + default: + return ""; + } + return convertedLanguage; + } + + String getUserAgent() + { + return CSysInfo::GetUserAgent(); + } + + int getSERVER_WEBSERVER() + { + return CNetworkServices::ES_WEBSERVER; + } + int getSERVER_AIRPLAYSERVER() + { + return CNetworkServices::ES_AIRPLAYSERVER; + } + int getSERVER_UPNPSERVER() + { + return CNetworkServices::ES_UPNPSERVER; + } + int getSERVER_UPNPRENDERER() + { + return CNetworkServices::ES_UPNPRENDERER; + } + int getSERVER_EVENTSERVER() + { + return CNetworkServices::ES_EVENTSERVER; + } + int getSERVER_JSONRPCSERVER() + { + return CNetworkServices::ES_JSONRPCSERVER; + } + int getSERVER_ZEROCONF() + { + return CNetworkServices::ES_ZEROCONF; + } + + int getPLAYLIST_MUSIC() + { + return PLAYLIST::TYPE_MUSIC; + } + int getPLAYLIST_VIDEO() + { + return PLAYLIST::TYPE_VIDEO; + } + int getTRAY_OPEN() + { + return static_cast<int>(TrayState::OPEN); + } + int getDRIVE_NOT_READY() + { + return static_cast<int>(DriveState::NOT_READY); + } + int getTRAY_CLOSED_NO_MEDIA() + { + return static_cast<int>(TrayState::CLOSED_NO_MEDIA); + } + int getTRAY_CLOSED_MEDIA_PRESENT() + { + return static_cast<int>(TrayState::CLOSED_MEDIA_PRESENT); + } + int getLOGDEBUG() { return LOGDEBUG; } + int getLOGINFO() { return LOGINFO; } + int getLOGWARNING() { return LOGWARNING; } + int getLOGERROR() { return LOGERROR; } + int getLOGFATAL() { return LOGFATAL; } + int getLOGNONE() { return LOGNONE; } + + // language string formats + int getISO_639_1() { return CLangCodeExpander::ISO_639_1; } + int getISO_639_2(){ return CLangCodeExpander::ISO_639_2; } + int getENGLISH_NAME() { return CLangCodeExpander::ENGLISH_NAME; } + + const int lLOGDEBUG = LOGDEBUG; + } +} diff --git a/xbmc/interfaces/legacy/ModuleXbmc.h b/xbmc/interfaces/legacy/ModuleXbmc.h new file mode 100644 index 0000000..0720d8a --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmc.h @@ -0,0 +1,898 @@ +/* + * 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 "AddonString.h" +#include "Tuple.h" + +#include "utils/LangCodeExpander.h" +#include "swighelper.h" +#include <vector> + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace XBMCAddon +{ + namespace xbmc + { +#ifndef SWIG + // This is a bit of a hack to get around a SWIG problem + extern const int lLOGDEBUG; +#endif +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + // + /// \defgroup python_xbmc Library - xbmc + /// @{ + /// @brief **General functions on Kodi.** + /// + /// Offers classes and functions that provide information about the media + /// currently playing and that allow manipulation of the media player (such + /// as starting a new song). You can also find system information using the + /// functions available in this library. + // + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.log(msg[, level]) } + /// Write a string to Kodi's log file and the debug window. + /// + /// @param msg string - text to output. + /// @param level [opt] integer - log level to output at. + /// <em>(default=LOGDEBUG)</em> + /// | Value: | Description: | + /// |----------------:|---------------------------------------------------| + /// | xbmc.LOGDEBUG | In depth information about the status of Kodi. This information can pretty much only be deciphered by a developer or long time Kodi power user. + /// | xbmc.LOGINFO | Something has happened. It's not a problem, we just thought you might want to know. Fairly excessive output that most people won't care about. + /// | xbmc.LOGWARNING | Something potentially bad has happened. If Kodi did something you didn't expect, this is probably why. Watch for errors to follow. + /// | xbmc.LOGERROR | This event is bad. Something has failed. You likely noticed problems with the application be it skin artifacts, failure of playback a crash, etc. + /// | xbmc.LOGFATAL | We're screwed. Kodi is about to crash. + /// + /// @note Addon developers are advised to keep `LOGDEBUG` as the default + /// logging level and to use conservative logging (log only if needed). + /// Excessive logging makes it harder to debug kodi itself. + /// + /// Logging in kodi has a global configuration level that controls how text + /// is written to the log. This global logging behaviour can be changed in + /// the GUI (**Settings -> System -> Logging**) (debug toggle) or furthered + /// configured in advancedsettings (loglevel setting). + /// + /// Text is written to the log for the following conditions: + /// - loglevel == -1 (NONE, nothing at all is logged to the log) + /// - loglevel == 0 (NORMAL, shows `LOGINFO`, `LOGWARNING`, `LOGERROR` and `LOGFATAL`) - Default kodi behaviour + /// - loglevel == 1 (DEBUG, shows all) - Behaviour if you toggle debug log in the GUI + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v17 Default level changed from `LOGNOTICE` to `LOGDEBUG` + /// @python_v19 Removed `LOGNOTICE` (use `LOGINFO`) and `LOGSEVERE` (use `LOGFATAL`) + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.log(msg='This is a test string.', level=xbmc.LOGDEBUG); + /// .. + /// ~~~~~~~~~~~~~ + /// + log(...); +#else + void log(const char* msg, int level = lLOGDEBUG); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.shutdown() } + /// Shutdown the htpc. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.shutdown() + /// .. + /// ~~~~~~~~~~~~~ + /// + shutdown(); +#else + void shutdown(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.restart() } + /// Restart the htpc. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.restart() + /// .. + /// ~~~~~~~~~~~~~ + /// + restart(); +#else + void restart(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.executescript(script) } + /// Execute a python script. + /// + /// @param script string - script filename to execute. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.executescript('special://home/scripts/update.py') + /// .. + /// ~~~~~~~~~~~~~ + /// + executescript(...); +#else + void executescript(const char* script); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.executebuiltin(function) } + /// Execute a built in Kodi function. + /// + /// @param function string - builtin function to execute. + /// @param wait [opt] bool - If Kodi should wait for the + /// builtin function execution to finish + /// (default False) + /// + /// + /// \ref page_List_of_built_in_functions "List of builtin functions" + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.executebuiltin('Skin.SetString(abc,def)') + /// .. + /// ~~~~~~~~~~~~~ + /// + executebuiltin(...); +#else + void executebuiltin(const char* function, bool wait = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.executeJSONRPC(jsonrpccommand) } + /// Execute an JSONRPC command. + /// + /// @param jsonrpccommand string - jsonrpc command to execute. + /// @return jsonrpc return string + /// + /// + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// response = xbmc.executeJSONRPC('{ "jsonrpc": "2.0", "method": "JSONRPC.Introspect", "id": 1 }') + /// .. + /// ~~~~~~~~~~~~~ + /// + executeJSONRPC(...); +#else + String executeJSONRPC(const char* jsonrpccommand); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.sleep(time) } + /// Sleeps for 'time' (msec). + /// \anchor xbmc_Sleep + /// + /// @param time integer - number of msec to sleep. + /// + /// @throws PyExc_TypeError If time is not an integer. + /// + /// @warning This is useful if you need to sleep for a small amount of time + /// (milisecond range) somewhere in your addon logic. Please note that Kodi + /// will attempt to stop any running scripts when signaled to exit and wait for a maximum + /// of 5 seconds before trying to force stop your script. If your addon makes use + /// of \ref xbmc_Sleep "xbmc.sleep()" incorrectly (long periods of time, e.g. that exceed + /// the force stop waiting time) it may lead to Kodi hanging on shutdown. + /// In case your addon needs long sleep/idle periods use + /// \ref xbmc_Monitor_waitForAbort "xbmc.Monitor().waitForAbort(secs)" + /// instead. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.sleep(2000) # sleeps for 2 seconds + /// .. + /// ~~~~~~~~~~~~~ + /// + sleep(...); +#else + void sleep(long timemillis); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getLocalizedString(id) } + /// Get a localized 'unicode string'. + /// + /// @param id integer - id# for string you want to + /// localize. + /// @return Localized 'unicode string' + /// + /// @note See strings.po in `\language\{yourlanguage}\` for which id + /// you need for a string. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// locstr = xbmc.getLocalizedString(6) + /// .. + /// ~~~~~~~~~~~~~ + /// + getLocalizedString(...); +#else + String getLocalizedString(int id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getSkinDir() } + /// Get the active skin directory. + /// + /// @return The active skin directory as a string + /// + /// + /// @note This is not the full path like 'special://home/addons/MediaCenter', + /// but only 'MediaCenter'. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// skindir = xbmc.getSkinDir() + /// .. + /// ~~~~~~~~~~~~~ + /// + getSkinDir(); +#else + String getSkinDir(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getLanguage([format], [region]) } + /// Get the active language. + /// + /// @param format [opt] format of the returned language + /// string + /// | Value | Description + /// |------------------:|:-------------------------------------------------| + /// | xbmc.ISO_639_1 | Two letter code as defined in ISO 639-1 + /// | xbmc.ISO_639_2 | Three letter code as defined in ISO 639-2/T or ISO 639-2/B + /// | xbmc.ENGLISH_NAME | Full language name in English (default) + /// @param region [opt] append the region delimited by "-" + /// of the language (setting) to the + /// returned language string + /// @return The active language as a string + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v13 Added new options **format** and **region**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// language = xbmc.getLanguage(xbmc.ENGLISH_NAME) + /// .. + /// ~~~~~~~~~~~~~ + /// + getLanguage(...); +#else + String getLanguage(int format = CLangCodeExpander::ENGLISH_NAME, bool region = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getIPAddress() } + /// Get the current ip address. + /// + /// @return The current ip address as a string + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// ip = xbmc.getIPAddress() + /// .. + /// ~~~~~~~~~~~~~ + /// + getIPAddress(); +#else + String getIPAddress(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getDVDState() } + /// Returns the dvd state as an integer. + /// + /// @return Values for state are: + /// | Value | Name | + /// |------:|:-------------------------------| + /// | 1 | xbmc.DRIVE_NOT_READY + /// | 16 | xbmc.TRAY_OPEN + /// | 64 | xbmc.TRAY_CLOSED_NO_MEDIA + /// | 96 | xbmc.TRAY_CLOSED_MEDIA_PRESENT + /// + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dvdstate = xbmc.getDVDState() + /// .. + /// ~~~~~~~~~~~~~ + /// + getDVDState(); +#else + long getDVDState(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getFreeMem() } + /// Get amount of free memory in MB. + /// + /// @return The amount of free memory in MB as an integer + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// freemem = xbmc.getFreeMem() + /// .. + /// ~~~~~~~~~~~~~ + /// + getFreeMem(); +#else + long getFreeMem(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getInfoLabel(infotag) } + /// Get a info label + /// + /// @param infotag string - infoTag for value you want + /// returned. + /// @return InfoLabel as a string + /// + /// \ref modules__infolabels_boolean_conditions "List of InfoTags" + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// label = xbmc.getInfoLabel('Weather.Conditions') + /// .. + /// ~~~~~~~~~~~~~ + /// + getInfoLabel(...); +#else + String getInfoLabel(const char* cLine); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getInfoImage(infotag) } + /// Get filename including path to the InfoImage's thumbnail. + /// + /// @param infotag string - infotag for value you want + /// returned + /// @return Filename including path to the + /// InfoImage's thumbnail as a string + /// + /// + /// List of InfoTags - http://kodi.wiki/view/InfoLabels + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// filename = xbmc.getInfoImage('Weather.Conditions') + /// .. + /// ~~~~~~~~~~~~~ + /// + getInfoImage(...); +#else + String getInfoImage(const char * infotag); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.playSFX(filename,[useCached]) } + /// Plays a wav file by filename + /// + /// @param filename string - filename of the wav file to + /// play + /// @param useCached [opt] bool - False = Dump any + /// previously cached wav associated with + /// filename + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v14 Added new option **useCached**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.playSFX('special://xbmc/scripts/dingdong.wav') + /// xbmc.playSFX('special://xbmc/scripts/dingdong.wav',False) + /// .. + /// ~~~~~~~~~~~~~ + /// + playSFX(...); +#else + void playSFX(const char* filename, bool useCached = true); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.stopSFX() } + /// Stops wav file + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v14 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.stopSFX() + /// .. + /// ~~~~~~~~~~~~~ + /// + stopSFX(); +#else + void stopSFX(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.enableNavSounds(yesNo) } + /// Enables/Disables nav sounds + /// + /// @param yesNo bool - enable (True) or disable + /// (False) nav sounds + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.enableNavSounds(True) + /// .. + /// ~~~~~~~~~~~~~ + /// + enableNavSounds(...); +#else + void enableNavSounds(bool yesNo); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getCondVisibility(condition) } + /// Get visibility conditions + /// + /// @param condition string - condition to check + /// @return True (if the condition is verified) or False (otherwise) + /// + /// \ref modules__infolabels_boolean_conditions "List of boolean conditions" + /// + /// @note You can combine two (or more) of the above settings by using <b>"+"</b> as an AND operator, + /// <b>"|"</b> as an OR operator, <b>"!"</b> as a NOT operator, and <b>"["</b> and <b>"]"</b> to bracket expressions. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// visible = xbmc.getCondVisibility('[Control.IsVisible(41) + !Control.IsVisible(12)]') + /// .. + /// ~~~~~~~~~~~~~ + /// + getCondVisibility(...); +#else + bool getCondVisibility(const char *condition); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getGlobalIdleTime() } + /// Get the elapsed idle time in seconds. + /// + /// @return Elapsed idle time in seconds as an integer + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// t = xbmc.getGlobalIdleTime() + /// .. + /// ~~~~~~~~~~~~~ + /// + getGlobalIdleTime(); +#else + int getGlobalIdleTime(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getCacheThumbName(path) } + /// Get thumb cache filename. + /// + /// @param path string - path to file + /// @return Thumb cache filename + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// thumb = xbmc.getCacheThumbName('f:\\videos\\movie.avi') + /// .. + /// ~~~~~~~~~~~~~ + /// + getCacheThumbName(...); +#else + String getCacheThumbName(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getCleanMovieTitle(path[, usefoldername]) } + /// Get clean movie title and year string if available. + /// + /// @param path string - String to clean + /// @param usefoldername [opt] bool - use folder names (defaults + /// to false) + /// @return Clean movie title and year string if + /// available. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// title, year = xbmc.getCleanMovieTitle('/path/to/moviefolder/test.avi', True) + /// .. + /// ~~~~~~~~~~~~~ + /// + getCleanMovieTitle(...); +#else + Tuple<String,String> getCleanMovieTitle(const String& path, bool usefoldername = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getRegion(id) } + /// Returns your regions setting as a string for the specified id. + /// + /// @param id string - id of setting to return + /// @return Region setting + /// + /// @note choices are (dateshort, datelong, time, meridiem, tempunit, speedunit) + /// You can use the above as keywords for arguments. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// date_long_format = xbmc.getRegion('datelong') + /// .. + /// ~~~~~~~~~~~~~ + /// + getRegion(...); +#else + String getRegion(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getSupportedMedia(media) } + /// Get the supported file types for the specific media. + /// + /// @param media string - media type + /// @return Supported file types for the specific + /// media as a string + /// + /// + /// @note Media type can be (video, music, picture). + /// The return value is a pipe separated string of filetypes + /// (eg. '.mov |.avi').\n + /// You can use the above as keywords for arguments. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// mTypes = xbmc.getSupportedMedia('video') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSupportedMedia(...); +#else + String getSupportedMedia(const char* mediaType); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.skinHasImage(image) } + /// Check skin for presence of Image. + /// + /// @param image string - image filename + /// @return True if the image file exists in the skin + /// + /// + /// @note If the media resides in a subfolder include it. (eg. home-myfiles\\home-myfiles2.png). + /// You can use the above as keywords for arguments. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// exists = xbmc.skinHasImage('ButtonFocusedTexture.png') + /// .. + /// ~~~~~~~~~~~~~ + /// + skinHasImage(...); +#else + bool skinHasImage(const char* image); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmc + /// + /// @brief \python_func{ xbmc.startServer(typ, bStart, bWait) } + /// Start or stop a server. + /// + /// @param typ integer - use SERVER_* constants + /// - Used format of the returned language string + /// | Value | Description | + /// |--------------------------:|------------------------------------------------------------| + /// | xbmc.SERVER_WEBSERVER | [To control Kodi's builtin webserver](http://kodi.wiki/view/Webserver) + /// | xbmc.SERVER_AIRPLAYSERVER | [AirPlay is a proprietary protocol stack/suite developed by Apple Inc.](http://kodi.wiki/view/AirPlay) + /// | xbmc.SERVER_JSONRPCSERVER | [Control JSON-RPC HTTP/TCP socket-based interface](http://kodi.wiki/view/JSON-RPC_API) + /// | xbmc.SERVER_UPNPRENDERER | [UPnP client (aka UPnP renderer)](http://kodi.wiki/view/UPnP/Client) + /// | xbmc.SERVER_UPNPSERVER | [Control built-in UPnP A/V media server (UPnP-server)](http://kodi.wiki/view/UPnP/Server) + /// | xbmc.SERVER_EVENTSERVER | [Set eventServer part that accepts remote device input on all platforms](http://kodi.wiki/view/EventServer) + /// | xbmc.SERVER_ZEROCONF | [Control Kodi's Avahi Zeroconf](http://kodi.wiki/view/Zeroconf) + /// @param bStart bool - start (True) or stop (False) a server + /// @return bool - True or False + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v20 Removed option **bWait**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.startServer(xbmc.SERVER_AIRPLAYSERVER, False) + /// .. + /// ~~~~~~~~~~~~~ + /// + startServer(...); +#else + bool startServer(int iTyp, bool bStart); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.audioSuspend() } + /// Suspend Audio engine. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.audioSuspend() + /// .. + /// ~~~~~~~~~~~~~ + /// + audioSuspend(); +#else + void audioSuspend(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.audioResume() } + /// Resume Audio engine. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.audioResume() + /// .. + /// ~~~~~~~~~~~~~ + /// + audioResume(); +#else + void audioResume(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.getUserAgent() } + /// @brief Returns Kodi's HTTP UserAgent string + /// + /// @return HTTP user agent + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmc.getUserAgent() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// example output: + /// Kodi/17.0-ALPHA1 (X11; Linux x86_64) Ubuntu/15.10 App_Bitness/64 Version/17.0-ALPHA1-Git:2015-12-23-5770d28 + /// + getUserAgent(); +#else + String getUserAgent(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc + /// @brief \python_func{ xbmc.convertLanguage(language, format) } + /// @brief Returns the given language converted to the given format as a + /// string. + /// + /// @param language string either as name in English, two + /// letter code (ISO 639-1), or three + /// letter code (ISO 639-2/T(B) + /// @param format format of the returned language string + /// | Value | Description + /// |------------------:|:-------------------------------------------------| + /// | xbmc.ISO_639_1 | Two letter code as defined in ISO 639-1 + /// | xbmc.ISO_639_2 | Three letter code as defined in ISO 639-2/T or ISO 639-2/B + /// | xbmc.ENGLISH_NAME | Full language name in English (default) + /// @return Converted Language string + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v13 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// language = xbmc.convertLanguage(English, xbmc.ISO_639_2) + /// .. + /// ~~~~~~~~~~~~~ + /// + convertLanguage(...); +#else + String convertLanguage(const char* language, int format); +#endif + //@} +#ifndef DOXYGEN_SHOULD_SKIP_THIS + SWIG_CONSTANT_FROM_GETTER(int, SERVER_WEBSERVER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_AIRPLAYSERVER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_UPNPSERVER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_UPNPRENDERER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_EVENTSERVER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_JSONRPCSERVER); + SWIG_CONSTANT_FROM_GETTER(int, SERVER_ZEROCONF); + + SWIG_CONSTANT_FROM_GETTER(int, PLAYLIST_MUSIC); + SWIG_CONSTANT_FROM_GETTER(int, PLAYLIST_VIDEO); + SWIG_CONSTANT_FROM_GETTER(int, TRAY_OPEN); + SWIG_CONSTANT_FROM_GETTER(int, DRIVE_NOT_READY); + SWIG_CONSTANT_FROM_GETTER(int, TRAY_CLOSED_NO_MEDIA); + SWIG_CONSTANT_FROM_GETTER(int, TRAY_CLOSED_MEDIA_PRESENT); + SWIG_CONSTANT_FROM_GETTER(int, LOGDEBUG); + SWIG_CONSTANT_FROM_GETTER(int, LOGINFO); + SWIG_CONSTANT_FROM_GETTER(int, LOGWARNING); + SWIG_CONSTANT_FROM_GETTER(int, LOGERROR); + SWIG_CONSTANT_FROM_GETTER(int, LOGFATAL); + SWIG_CONSTANT_FROM_GETTER(int, LOGNONE); + + SWIG_CONSTANT_FROM_GETTER(int, ISO_639_1); + SWIG_CONSTANT_FROM_GETTER(int, ISO_639_2); + SWIG_CONSTANT_FROM_GETTER(int, ENGLISH_NAME); +#if 0 + void registerMonitor(Monitor* monitor); + void unregisterMonitor(Monitor* monitor); +#endif + } +} +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/xbmc/interfaces/legacy/ModuleXbmcgui.cpp b/xbmc/interfaces/legacy/ModuleXbmcgui.cpp new file mode 100644 index 0000000..104a755 --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcgui.cpp @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "ModuleXbmcgui.h" + +#include "LanguageHook.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "windowing/GraphicContext.h" + +#include <mutex> + +#define NOTIFICATION_INFO "info" +#define NOTIFICATION_WARNING "warning" +#define NOTIFICATION_ERROR "error" + +namespace XBMCAddon +{ + namespace xbmcgui + { + long getCurrentWindowId() + { + DelayedCallGuard dg; + std::unique_lock<CCriticalSection> gl(CServiceBroker::GetWinSystem()->GetGfxContext()); + return CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + } + + long getCurrentWindowDialogId() + { + DelayedCallGuard dg; + std::unique_lock<CCriticalSection> gl(CServiceBroker::GetWinSystem()->GetGfxContext()); + return CServiceBroker::GetGUI()->GetWindowManager().GetTopmostModalDialog(); + } + + long getScreenHeight() + { + XBMC_TRACE; + return CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(); + } + + long getScreenWidth() + { + XBMC_TRACE; + return CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(); + } + + const char* getNOTIFICATION_INFO() { return NOTIFICATION_INFO; } + const char* getNOTIFICATION_WARNING() { return NOTIFICATION_WARNING; } + const char* getNOTIFICATION_ERROR() { return NOTIFICATION_ERROR; } + + } +} diff --git a/xbmc/interfaces/legacy/ModuleXbmcgui.h b/xbmc/interfaces/legacy/ModuleXbmcgui.h new file mode 100644 index 0000000..d2d9558 --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcgui.h @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "guilib/GUIEditControl.h" +#include "swighelper.h" + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace XBMCAddon +{ + namespace xbmcgui + { +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + // + /// \defgroup python_xbmcgui Library - xbmcgui + /// @{ + /// @brief **GUI functions on Kodi.** + /// + /// Offers classes and functions that manipulate the Graphical User + /// Interface through windows, dialogs, and various control widgets. + // + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui + /// @brief \python_func{ xbmcgui.getCurrentWindowId() } + /// Returns the id for the current 'active' window as an integer. + /// + /// @return The currently active window Id + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// wid = xbmcgui.getCurrentWindowId() + /// .. + /// ~~~~~~~~~~~~~ + /// + getCurrentWindowId(); +#else + long getCurrentWindowId(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui + /// @brief \python_func{ xbmcgui.getCurrentWindowDialogId() } + /// Returns the id for the current 'active' dialog as an integer. + /// + /// @return The currently active dialog Id + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// wid = xbmcgui.getCurrentWindowDialogId() + /// .. + /// ~~~~~~~~~~~~~ + /// + getCurrentWindowDialogId(); +#else + long getCurrentWindowDialogId(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui + /// @brief \python_func{ getScreenHeight() } + /// Returns the height of this screen. + /// + /// @return Screen height + /// + /// + ///------------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getScreenHeight(); +#else + long getScreenHeight(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui + /// @brief \python_func{ getScreenWidth() } + /// Returns the width of this screen. + /// + /// @return Screen width + /// + /// + ///------------------------------------------------------------------------- + /// @python_v18 New function added. + /// + getScreenWidth(); +#else + long getScreenWidth(); +#endif + ///@} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + SWIG_CONSTANT2(int, ICON_OVERLAY_NONE, CGUIListItem::ICON_OVERLAY_NONE); + SWIG_CONSTANT2(int, ICON_OVERLAY_RAR, CGUIListItem::ICON_OVERLAY_RAR); + SWIG_CONSTANT2(int, ICON_OVERLAY_ZIP, CGUIListItem::ICON_OVERLAY_ZIP); + SWIG_CONSTANT2(int, ICON_OVERLAY_LOCKED, CGUIListItem::ICON_OVERLAY_LOCKED); + SWIG_CONSTANT2(int, ICON_OVERLAY_UNWATCHED, CGUIListItem::ICON_OVERLAY_UNWATCHED); + SWIG_CONSTANT2(int, ICON_OVERLAY_WATCHED, CGUIListItem::ICON_OVERLAY_WATCHED); + SWIG_CONSTANT2(int, ICON_OVERLAY_HD, CGUIListItem::ICON_OVERLAY_HD); + + SWIG_CONSTANT2(int, INPUT_TYPE_TEXT, CGUIEditControl::INPUT_TYPE_TEXT); + SWIG_CONSTANT2(int, INPUT_TYPE_NUMBER, CGUIEditControl::INPUT_TYPE_NUMBER); + SWIG_CONSTANT2(int, INPUT_TYPE_DATE, CGUIEditControl::INPUT_TYPE_DATE); + SWIG_CONSTANT2(int, INPUT_TYPE_TIME, CGUIEditControl::INPUT_TYPE_TIME); + SWIG_CONSTANT2(int, INPUT_TYPE_IPADDRESS, CGUIEditControl::INPUT_TYPE_IPADDRESS); + SWIG_CONSTANT2(int, INPUT_TYPE_PASSWORD, CGUIEditControl::INPUT_TYPE_PASSWORD); + SWIG_CONSTANT2(int, INPUT_TYPE_PASSWORD_MD5, CGUIEditControl::INPUT_TYPE_PASSWORD_MD5); + SWIG_CONSTANT2(int, INPUT_TYPE_SECONDS, CGUIEditControl::INPUT_TYPE_SECONDS); + SWIG_CONSTANT2(int, INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW, CGUIEditControl::INPUT_TYPE_PASSWORD_NUMBER_VERIFY_NEW); + + SWIG_CONSTANT_FROM_GETTER(const char*, NOTIFICATION_INFO); + SWIG_CONSTANT_FROM_GETTER(const char*, NOTIFICATION_WARNING); + SWIG_CONSTANT_FROM_GETTER(const char*, NOTIFICATION_ERROR); + + SWIG_CONSTANT(int, INPUT_ALPHANUM); + SWIG_CONSTANT(int, INPUT_NUMERIC); + SWIG_CONSTANT(int, INPUT_DATE); + SWIG_CONSTANT(int, INPUT_TIME); + SWIG_CONSTANT(int, INPUT_IPADDRESS); + SWIG_CONSTANT(int, INPUT_PASSWORD); + + SWIG_CONSTANT(int, HORIZONTAL); + SWIG_CONSTANT(int, VERTICAL); + + SWIG_CONSTANT(int, PASSWORD_VERIFY); + SWIG_CONSTANT(int, ALPHANUM_HIDE_INPUT); + + } +} +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/xbmc/interfaces/legacy/ModuleXbmcplugin.cpp b/xbmc/interfaces/legacy/ModuleXbmcplugin.cpp new file mode 100644 index 0000000..0af97b7 --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcplugin.cpp @@ -0,0 +1,125 @@ +/* + * 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 "ModuleXbmcplugin.h" + +#include "FileItem.h" +#include "filesystem/PluginDirectory.h" + +namespace XBMCAddon +{ + + namespace xbmcplugin + { + bool addDirectoryItem(int handle, const String& url, const xbmcgui::ListItem* listItem, + bool isFolder, int totalItems) + { + if (listItem == nullptr) + throw new XBMCAddon::WrongTypeException("None not allowed as argument for listitem"); + AddonClass::Ref<xbmcgui::ListItem> pListItem(listItem); + pListItem->item->SetPath(url); + pListItem->item->m_bIsFolder = isFolder; + + // call the directory class to add our item + return XFILE::CPluginDirectory::AddItem(handle, pListItem->item.get(), totalItems); + } + + bool addDirectoryItems(int handle, + const std::vector<Tuple<String,const XBMCAddon::xbmcgui::ListItem*,bool> >& items, + int totalItems) + { + CFileItemList fitems; + for (const auto& item : items) + { + const String& url = item.first(); + const XBMCAddon::xbmcgui::ListItem* pListItem = item.second(); + bool bIsFolder = item.GetNumValuesSet() > 2 ? item.third() : false; + pListItem->item->SetPath(url); + pListItem->item->m_bIsFolder = bIsFolder; + fitems.Add(pListItem->item); + } + + // call the directory class to add our items + return XFILE::CPluginDirectory::AddItems(handle, &fitems, totalItems); + } + + void endOfDirectory(int handle, bool succeeded, bool updateListing, + bool cacheToDisc) + { + // tell the directory class that we're done + XFILE::CPluginDirectory::EndOfDirectory(handle, succeeded, updateListing, cacheToDisc); + } + + void setResolvedUrl(int handle, bool succeeded, const xbmcgui::ListItem* listItem) + { + if (listItem == nullptr) + throw new XBMCAddon::WrongTypeException("None not allowed as argument for listitem"); + AddonClass::Ref<xbmcgui::ListItem> pListItem(listItem); + XFILE::CPluginDirectory::SetResolvedUrl(handle, succeeded, pListItem->item.get()); + } + + void addSortMethod(int handle, int sortMethod, const String& clabelMask, const String& clabel2Mask) + { + String labelMask; + if (sortMethod == SORT_METHOD_TRACKNUM) + labelMask = (clabelMask.empty() ? "[%N. ]%T" : clabelMask.c_str()); + else if (sortMethod == SORT_METHOD_EPISODE || sortMethod == SORT_METHOD_PRODUCTIONCODE) + labelMask = (clabelMask.empty() ? "%H. %T" : clabelMask.c_str()); + else + labelMask = (clabelMask.empty() ? "%T" : clabelMask.c_str()); + + String label2Mask; + label2Mask = (clabel2Mask.empty() ? "%D" : clabel2Mask.c_str()); + + // call the directory class to add the sort method. + if (sortMethod >= SORT_METHOD_NONE && sortMethod < SORT_METHOD_MAX) + XFILE::CPluginDirectory::AddSortMethod(handle, (SORT_METHOD)sortMethod, labelMask, label2Mask); + } + + String getSetting(int handle, const char* id) + { + return XFILE::CPluginDirectory::GetSetting(handle, id); + } + + void setSetting(int handle, const String& id, const String& value) + { + XFILE::CPluginDirectory::SetSetting(handle, id, value); + } + + void setContent(int handle, const char* content) + { + XFILE::CPluginDirectory::SetContent(handle, content); + } + + void setPluginCategory(int handle, const String& category) + { + XFILE::CPluginDirectory::SetProperty(handle, "plugincategory", category); + } + + void setPluginFanart(int handle, const char* image, + const char* color1, + const char* color2, + const char* color3) + { + if (image) + XFILE::CPluginDirectory::SetProperty(handle, "fanart_image", image); + if (color1) + XFILE::CPluginDirectory::SetProperty(handle, "fanart_color1", color1); + if (color2) + XFILE::CPluginDirectory::SetProperty(handle, "fanart_color2", color2); + if (color3) + XFILE::CPluginDirectory::SetProperty(handle, "fanart_color3", color3); + } + + void setProperty(int handle, const char* key, const String& value) + { + XFILE::CPluginDirectory::SetProperty(handle, key, value); + } + + } +} diff --git a/xbmc/interfaces/legacy/ModuleXbmcplugin.h b/xbmc/interfaces/legacy/ModuleXbmcplugin.h new file mode 100644 index 0000000..1aa458c --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcplugin.h @@ -0,0 +1,489 @@ +/* + * 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 "AddonString.h" +#include "ListItem.h" +#include "Tuple.h" +#include "swighelper.h" + +#include <vector> + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace XBMCAddon +{ + namespace xbmcplugin + { +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + // + /// \defgroup python_xbmcplugin Library - xbmcplugin + /// @{ + /// @brief <b>Plugin functions on Kodi.</b> + /// + /// Offers classes and functions that allow a developer to present + /// information through Kodi's standard menu structure. While plugins don't + /// have the same flexibility as scripts, they boast significantly quicker + /// development time and a more consistent user experience. + // + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.addDirectoryItem(handle, url, listitem [,isFolder, totalItems]) } + /// Callback function to pass directory contents back to Kodi. + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param url string - url of the entry. would be + /// `plugin://` for another virtual directory + /// @param listitem ListItem - item to add. + /// @param isFolder [opt] bool - True=folder / False=not a + /// folder(default). + /// @param totalItems [opt] integer - total number of items + /// that will be passed.(used for progressbar) + /// @return Returns a bool for successful completion. + /// + /// @note You can use the above as keywords for arguments and skip certain + /// optional arguments. Once you use a keyword, all following arguments + /// require the keyword. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// if not xbmcplugin.addDirectoryItem(int(sys.argv[1]), 'F:\\Trailers\\300.mov', listitem, totalItems=50): break + /// .. + /// ~~~~~~~~~~~~~ + /// + addDirectoryItem(...); +#else + bool addDirectoryItem(int handle, const String& url, const XBMCAddon::xbmcgui::ListItem* listitem, + bool isFolder = false, int totalItems = 0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.addDirectoryItems(handle, items[, totalItems]) } + /// Callback function to pass directory contents back to Kodi as a list. + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param items List - list of (url, listitem[, isFolder]) + /// as a tuple to add. + /// @param totalItems [opt] integer - total number of items + /// that will be passed.(used for progressbar) + /// @return Returns a bool for successful completion. + /// + /// @remark Large lists benefit over using the standard addDirectoryItem(). + /// You may call this more than once to add items in chunks. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// if not xbmcplugin.addDirectoryItems(int(sys.argv[1]), [(url, listitem, False,)]: raise + /// .. + /// ~~~~~~~~~~~~~ + /// + addDirectoryItems(...); +#else + bool addDirectoryItems(int handle, + const std::vector<Tuple<String,const XBMCAddon::xbmcgui::ListItem*,bool> >& items, + int totalItems = 0); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.endOfDirectory(handle[, succeeded, updateListing, cacheToDisc]) } + /// Callback function to tell Kodi that the end of the directory listing in + /// a virtualPythonFolder module is reached. + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param succeeded [opt] bool - True=script completed + /// successfully(Default)/False=Script did not. + /// @param updateListing [opt] bool - True=this folder should + /// update the current listing/False=Folder + /// is a subfolder(Default). + /// @param cacheToDisc [opt] bool - True=Folder will cache if + /// extended time(default)/False=this folder + /// will never cache to disc. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.endOfDirectory(int(sys.argv[1]), cacheToDisc=False) + /// .. + /// ~~~~~~~~~~~~~ + /// + endOfDirectory(...); +#else + void endOfDirectory(int handle, bool succeeded = true, bool updateListing = false, + bool cacheToDisc = true); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setResolvedUrl(handle, succeeded, listitem) } + /// Callback function to tell Kodi that the file plugin has been resolved to + /// a url + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param succeeded bool - True=script completed + /// successfully/False=Script did not. + /// @param listitem ListItem - item the file plugin resolved + /// to for playback. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setResolvedUrl(int(sys.argv[1]), True, listitem) + /// .. + /// ~~~~~~~~~~~~~ + /// + setResolvedUrl(...); +#else + void setResolvedUrl(int handle, bool succeeded, const XBMCAddon::xbmcgui::ListItem* listitem); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.addSortMethod(handle, sortMethod [,labelMask, label2Mask]) } + ///------------------------------------------------------------------------- + /// Adds a sorting method for the media list. + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param sortMethod integer - see available sort methods at + /// the bottom (or see \ref List_of_sort_methods "SortUtils"). + /// | Value | Description | + /// |----------------------------------------------|-----------------------| + /// | xbmcplugin.SORT_METHOD_NONE | Do not sort + /// | xbmcplugin.SORT_METHOD_LABEL | Sort by label + /// | xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE | Sort by the label and ignore "The" before + /// | xbmcplugin.SORT_METHOD_DATE | Sort by the date + /// | xbmcplugin.SORT_METHOD_SIZE | Sort by the size + /// | xbmcplugin.SORT_METHOD_FILE | Sort by the file + /// | xbmcplugin.SORT_METHOD_DRIVE_TYPE | Sort by the drive type + /// | xbmcplugin.SORT_METHOD_TRACKNUM | Sort by the track number + /// | xbmcplugin.SORT_METHOD_DURATION | Sort by the duration + /// | xbmcplugin.SORT_METHOD_TITLE | Sort by the title + /// | xbmcplugin.SORT_METHOD_TITLE_IGNORE_THE | Sort by the title and ignore "The" before + /// | xbmcplugin.SORT_METHOD_ARTIST | Sort by the artist + /// | xbmcplugin.SORT_METHOD_ARTIST_IGNORE_THE | Sort by the artist and ignore "The" before + /// | xbmcplugin.SORT_METHOD_ALBUM | Sort by the album + /// | xbmcplugin.SORT_METHOD_ALBUM_IGNORE_THE | Sort by the album and ignore "The" before + /// | xbmcplugin.SORT_METHOD_GENRE | Sort by the genre + /// | xbmcplugin.SORT_SORT_METHOD_VIDEO_YEAR, xbmcplugin.SORT_METHOD_YEAR | Sort by the year + /// | xbmcplugin.SORT_METHOD_VIDEO_RATING | Sort by the video rating + /// | xbmcplugin.SORT_METHOD_PROGRAM_COUNT | Sort by the program count + /// | xbmcplugin.SORT_METHOD_PLAYLIST_ORDER | Sort by the playlist order + /// | xbmcplugin.SORT_METHOD_EPISODE | Sort by the episode + /// | xbmcplugin.SORT_METHOD_VIDEO_TITLE | Sort by the video title + /// | xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE | Sort by the video sort title + /// | xbmcplugin.SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE | Sort by the video sort title and ignore "The" before + /// | xbmcplugin.SORT_METHOD_PRODUCTIONCODE | Sort by the production code + /// | xbmcplugin.SORT_METHOD_SONG_RATING | Sort by the song rating + /// | xbmcplugin.SORT_METHOD_MPAA_RATING | Sort by the mpaa rating + /// | xbmcplugin.SORT_METHOD_VIDEO_RUNTIME | Sort by video runtime + /// | xbmcplugin.SORT_METHOD_STUDIO | Sort by the studio + /// | xbmcplugin.SORT_METHOD_STUDIO_IGNORE_THE | Sort by the studio and ignore "The" before + /// | xbmcplugin.SORT_METHOD_UNSORTED | Use list not sorted + /// | xbmcplugin.SORT_METHOD_BITRATE | Sort by the bitrate + /// | xbmcplugin.SORT_METHOD_LISTENERS | Sort by the listeners + /// | xbmcplugin.SORT_METHOD_COUNTRY | Sort by the country + /// | xbmcplugin.SORT_METHOD_DATEADDED | Sort by the added date + /// | xbmcplugin.SORT_METHOD_FULLPATH | Sort by the full path name + /// | xbmcplugin.SORT_METHOD_LABEL_IGNORE_FOLDERS | Sort by the label names and ignore related folder names + /// | xbmcplugin.SORT_METHOD_LASTPLAYED | Sort by last played date + /// | xbmcplugin.SORT_METHOD_PLAYCOUNT | Sort by the play count + /// | xbmcplugin.SORT_METHOD_CHANNEL | Sort by the channel + /// | xbmcplugin.SORT_METHOD_DATE_TAKEN | Sort by the taken date + /// | xbmcplugin.SORT_METHOD_VIDEO_USER_RATING | Sort by the rating of the user of video + /// | xbmcplugin.SORT_METHOD_SONG_USER_RATING | Sort by the rating of the user of song + /// @param labelMask [opt] string - the label mask to use for + /// the first label. + /// - applies to: + /// | sortMethod | labelMask | + /// |---------------------------------------|-----------------------------| + /// | SORT_METHOD_TRACKNUM | Defaults to `[%%N. ]%%T` | + /// | SORT_METHOD_EPISODE | Defaults to `%%H. %%T` | + /// | SORT_METHOD_PRODUCTIONCODE | Defaults to `%%H. %%T` | + /// | All other sort methods | Defaults to `%%T` | + /// + /// + /// @param label2Mask [opt] string - the label mask to use for + /// the second label. Defaults to `%%D` + /// - applies to: + /// | | | | + /// |-----------------------------------------|-----------------------------|-----------------------------------------| + /// | SORT_METHOD_NONE | SORT_METHOD_UNSORTED | SORT_METHOD_VIDEO_TITLE | + /// | SORT_METHOD_TRACKNUM | SORT_METHOD_FILE | SORT_METHOD_TITLE | + /// | SORT_METHOD_TITLE_IGNORE_THE | SORT_METHOD_LABEL | SORT_METHOD_LABEL_IGNORE_THE | + /// | SORT_METHOD_VIDEO_SORT_TITLE | SORT_METHOD_FULLPATH | SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE | + /// | SORT_METHOD_LABEL_IGNORE_FOLDERS | SORT_METHOD_CHANNEL | | + /// @note to add multiple sort methods just call the method multiple times. + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v13 Added new sort **SORT_METHOD_DATE_TAKEN**, **SORT_METHOD_COUNTRY**, + /// **SORT_METHOD_DATEADDED**, **SORT_METHOD_FULLPATH**, **SORT_METHOD_LABEL_IGNORE_FOLDERS**, + /// **SORT_METHOD_LASTPLAYED**, **SORT_METHOD_PLAYCOUNT**, **SORT_METHOD_CHANNEL**. + /// @python_v17 Added new sort **SORT_METHOD_VIDEO_USER_RATING**. + /// @python_v19 Added new option **labelMask**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.addSortMethod(int(sys.argv[1]), xbmcplugin.SORTMETHOD_DATEADDED) + /// .. + /// ~~~~~~~~~~~~~ + /// + addSortMethod(...); +#else + void addSortMethod(int handle, int sortMethod, const String& labelMask = emptyString, const String& label2Mask = emptyString); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.getSetting(handle, id) } + /// Returns the value of a setting as a string. + /// + /// @param handle integer - handle the plugin was started + /// with. + /// @param id string - id of the setting that the + /// module needs to access. + /// @return Setting value as string + /// + /// @note You can use the above as a keyword. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// apikey = xbmcplugin.getSetting(int(sys.argv[1]), 'apikey') + /// .. + /// ~~~~~~~~~~~~~ + /// + getSetting(...); +#else + String getSetting(int handle, const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setSetting(handle, id, value) } + /// Sets a plugin setting for the current running plugin. + /// + /// @param handle integer - handle the plugin was started with. + /// @param id string - id of the setting that the module needs to access. + /// @param value string or unicode - value of the setting. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setSetting(int(sys.argv[1]), id='username', value='teamxbmc') + /// .. + /// ~~~~~~~~~~~~~ + /// + setSetting(...); +#else + void setSetting(int handle, const String& id, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setContent(handle, content) } + /// Sets the plugins content. + /// + /// @param handle integer - handle the plugin was started with. + /// @param content string - content type (eg. movies) + /// + /// @par Available content strings + /// | | | | | + /// |:--------:|:--------:|:--------:|:-----------:| + /// | files | songs | artists | albums | + /// | movies | tvshows | episodes | musicvideos | + /// | videos | images | games | -- | + /// + /// @remark Use **videos** for all videos which do not apply to the + /// more specific mentioned ones like "movies", "episodes" etc. + /// A good example is youtube. + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v18 Added new **games** content + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setContent(int(sys.argv[1]), 'movies') + /// .. + /// ~~~~~~~~~~~~~ + /// + setContent(...); +#else + void setContent(int handle, const char* content); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setPluginCategory(handle, category) } + /// Sets the plugins name for skins to display. + /// + /// @param handle integer - handle the plugin was started with. + /// @param category string or unicode - plugins sub category. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setPluginCategory(int(sys.argv[1]), 'Comedy') + /// .. + /// ~~~~~~~~~~~~~ + /// + setPluginCategory(...); +#else + void setPluginCategory(int handle, const String& category); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setPluginFanart(handle, image, color1, color2, color3) } + /// Sets the plugins fanart and color for skins to display. + /// + /// @param handle integer - handle the plugin was started with. + /// @param image [opt] string - path to fanart image. + /// @param color1 [opt] hexstring - color1. (e.g. '0xFFFFFFFF') + /// @param color2 [opt] hexstring - color2. (e.g. '0xFFFF3300') + /// @param color3 [opt] hexstring - color3. (e.g. '0xFF000000') + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setPluginFanart(int(sys.argv[1]), 'special://home/addons/plugins/video/Apple movie trailers II/fanart.png', color2='0xFFFF3300') + /// .. + /// ~~~~~~~~~~~~~ + /// + setPluginFanart(...); +#else + void setPluginFanart(int handle, const char* image = NULL, + const char* color1 = NULL, + const char* color2 = NULL, + const char* color3 = NULL); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcplugin + /// @brief \python_func{ xbmcplugin.setProperty(handle, key, value) } + /// Sets a container property for this plugin. + /// + /// @param handle integer - handle the plugin was started with. + /// @param key string - property name. + /// @param value string or unicode - value of property. + /// + /// @note Key is NOT case sensitive. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcplugin.setProperty(int(sys.argv[1]), 'Emulator', 'M.A.M.E.') + /// .. + /// ~~~~~~~~~~~~~ + /// + setProperty(...); + ///@} +#else + void setProperty(int handle, const char* key, const String& value); +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + SWIG_CONSTANT(int, SORT_METHOD_NONE); + SWIG_CONSTANT(int, SORT_METHOD_LABEL); + SWIG_CONSTANT(int, SORT_METHOD_LABEL_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_DATE); + SWIG_CONSTANT(int, SORT_METHOD_SIZE); + SWIG_CONSTANT(int, SORT_METHOD_FILE); + SWIG_CONSTANT(int, SORT_METHOD_DRIVE_TYPE); + SWIG_CONSTANT(int, SORT_METHOD_TRACKNUM); + SWIG_CONSTANT(int, SORT_METHOD_DURATION); + SWIG_CONSTANT(int, SORT_METHOD_TITLE); + SWIG_CONSTANT(int, SORT_METHOD_TITLE_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_ARTIST); + SWIG_CONSTANT(int, SORT_METHOD_ARTIST_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_ALBUM); + SWIG_CONSTANT(int, SORT_METHOD_ALBUM_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_GENRE); + SWIG_CONSTANT2(int, SORT_METHOD_VIDEO_YEAR,SORT_METHOD_YEAR); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_RATING); + SWIG_CONSTANT(int, SORT_METHOD_PROGRAM_COUNT); + SWIG_CONSTANT(int, SORT_METHOD_PLAYLIST_ORDER); + SWIG_CONSTANT(int, SORT_METHOD_EPISODE); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_TITLE); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_SORT_TITLE); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_SORT_TITLE_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_ORIGINAL_TITLE); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_ORIGINAL_TITLE_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_PRODUCTIONCODE); + SWIG_CONSTANT(int, SORT_METHOD_SONG_RATING); + SWIG_CONSTANT(int, SORT_METHOD_MPAA_RATING); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_RUNTIME); + SWIG_CONSTANT(int, SORT_METHOD_STUDIO); + SWIG_CONSTANT(int, SORT_METHOD_STUDIO_IGNORE_THE); + SWIG_CONSTANT(int, SORT_METHOD_UNSORTED); + SWIG_CONSTANT(int, SORT_METHOD_BITRATE); + SWIG_CONSTANT(int, SORT_METHOD_LISTENERS); + SWIG_CONSTANT(int, SORT_METHOD_COUNTRY); + SWIG_CONSTANT(int, SORT_METHOD_DATEADDED); + SWIG_CONSTANT(int, SORT_METHOD_FULLPATH); + SWIG_CONSTANT(int, SORT_METHOD_LABEL_IGNORE_FOLDERS); + SWIG_CONSTANT(int, SORT_METHOD_LASTPLAYED); + SWIG_CONSTANT(int, SORT_METHOD_PLAYCOUNT); + SWIG_CONSTANT(int, SORT_METHOD_CHANNEL); + SWIG_CONSTANT(int, SORT_METHOD_DATE_TAKEN); + SWIG_CONSTANT(int, SORT_METHOD_VIDEO_USER_RATING); + SWIG_CONSTANT(int, SORT_METHOD_SONG_USER_RATING); + } +} +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/xbmc/interfaces/legacy/ModuleXbmcvfs.cpp b/xbmc/interfaces/legacy/ModuleXbmcvfs.cpp new file mode 100644 index 0000000..c6858e3 --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcvfs.cpp @@ -0,0 +1,137 @@ +/* + * 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 "ModuleXbmcvfs.h" + +#include "FileItem.h" +#include "LanguageHook.h" +#include "URL.h" +#include "Util.h" +#include "filesystem/Directory.h" +#include "filesystem/File.h" +#include "filesystem/SpecialProtocol.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" + +namespace XBMCAddon +{ + + namespace xbmcvfs + { + bool copy(const String& strSource, const String& strDestination) + { + DelayedCallGuard dg; + return XFILE::CFile::Copy(strSource, strDestination); + } + + // delete a file + bool deleteFile(const String& strSource) + { + DelayedCallGuard dg; + return XFILE::CFile::Delete(strSource); + } + + // rename a file + bool rename(const String& file, const String& newFile) + { + DelayedCallGuard dg; + return XFILE::CFile::Rename(file,newFile); + } + + // check for a file or folder existence, mimics Pythons os.path.exists() + bool exists(const String& path) + { + DelayedCallGuard dg; + if (URIUtils::HasSlashAtEnd(path, true)) + return XFILE::CDirectory::Exists(path, false); + return XFILE::CFile::Exists(path, false); + } + + // make legal file name + String makeLegalFilename(const String& filename) + { + XBMC_TRACE; + return CUtil::MakeLegalPath(filename); + } + + // translate path + String translatePath(const String& path) + { + XBMC_TRACE; + return CSpecialProtocol::TranslatePath(path); + } + + // validate path + String validatePath(const String& path) + { + XBMC_TRACE; + return CUtil::ValidatePath(path, true); + } + + // make a directory + bool mkdir(const String& path) + { + DelayedCallGuard dg; + return XFILE::CDirectory::Create(path); + } + + // make all directories along the path + bool mkdirs(const String& path) + { + DelayedCallGuard dg; + return CUtil::CreateDirectoryEx(path); + } + + bool rmdir(const String& path, bool force) + { + DelayedCallGuard dg; + + if (force) + return CFileUtils::DeleteItem(path); + else + return XFILE::CDirectory::Remove(path); + } + + Tuple<std::vector<String>, std::vector<String> > listdir(const String& path) + { + DelayedCallGuard dg; + CFileItemList items; + std::string strSource; + strSource = path; + XFILE::CDirectory::GetDirectory(strSource, items, "", XFILE::DIR_FLAG_NO_FILE_DIRS); + + Tuple<std::vector<String>, std::vector<String> > ret; + // initialize the Tuple to two values + ret.second(); + + for (int i=0; i < items.Size(); i++) + { + std::string itemPath = items[i]->GetPath(); + + if (URIUtils::HasSlashAtEnd(itemPath)) // folder + { + URIUtils::RemoveSlashAtEnd(itemPath); + std::string strFileName = URIUtils::GetFileName(itemPath); + if (strFileName.empty()) + { + CURL url(itemPath); + strFileName = url.GetHostName(); + } + ret.first().push_back(strFileName); + } + else // file + { + std::string strFileName = URIUtils::GetFileName(itemPath); + ret.second().push_back(strFileName); + } + } + + return ret; + } + } +} diff --git a/xbmc/interfaces/legacy/ModuleXbmcvfs.h b/xbmc/interfaces/legacy/ModuleXbmcvfs.h new file mode 100644 index 0000000..30e768f --- /dev/null +++ b/xbmc/interfaces/legacy/ModuleXbmcvfs.h @@ -0,0 +1,334 @@ +/* + * 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 "AddonString.h" +#include "Tuple.h" + +#include <vector> + +#ifndef DOXYGEN_SHOULD_SKIP_THIS +namespace XBMCAddon +{ + namespace xbmcvfs + { +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ + + // + /// \defgroup python_xbmcvfs Library - xbmcvfs + /// @{ + /// @brief **Virtual file system functions on Kodi.** + /// + /// Offers classes and functions offers access to the Virtual File Server + /// (VFS) which you can use to manipulate files and folders. + // + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.copy(source, destination) } + /// Copy file to destination, returns true/false. + /// + /// @param source file to copy. + /// @param destination destination file + /// @return True if successed + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.copy(source, destination) + /// .. + /// ~~~~~~~~~~~~~ + /// + copy(...); +#else + bool copy(const String& strSource, const String& strDestination); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.delete(file) } + /// Delete a file + /// + /// @param file File to delete + /// @return True if successed + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// xbmcvfs.delete(file) + /// .. + /// ~~~~~~~~~~~~~ + /// + delete(...); +#else + bool deleteFile(const String& file); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.rename(file, newFileName) } + /// Rename a file + /// + /// @param file File to rename + /// @param newFileName New filename, including the full path + /// @return True if successed + /// + /// @note Moving files between different filesystem (eg. local to nfs://) is not possible on + /// all platforms. You may have to do it manually by using the copy and deleteFile functions. + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.rename(file,newFileName) + /// .. + /// ~~~~~~~~~~~~~ + /// + rename(...); +#else + bool rename(const String& file, const String& newFile); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.exists(path) } + /// Check for a file or folder existence + /// + /// @param path File or folder (folder must end with + /// slash or backslash) + /// @return True if successed + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.exists(path) + /// .. + /// ~~~~~~~~~~~~~ + /// + exists(...); +#else + bool exists(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.makeLegalFilename(filename) } + /// Returns a legal filename or path as a string. + /// + /// @param filename string - filename/path to make legal + /// @return Legal filename or path as a string + /// + /// + /// @note The returned value is platform-specific. This is due to the fact that + /// the chars that need to be replaced to make a path legal depend on the + /// underlying OS filesystem. This is useful, for example, if you want to create + /// a file or folder based on data over which you have no control (e.g. an external API). + /// + /// + ///------------------------------------------------------------------------- + /// @python_v19 New function added (replaces old **xbmc.makeLegalFilename**) + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # windows + /// >> xbmcvfs.makeLegalFilename('C://Trailers/Ice Age: The Meltdown.avi') + /// C:\Trailers\Ice Age_ The Meltdown.avi + /// # non-windows + /// >> xbmcvfs.makeLegalFilename("///\\jk???lj????.mpg") + /// /jk___lj____.mpg + /// .. + /// ~~~~~~~~~~~~~ + /// + makeLegalFilename(...); +#else + String makeLegalFilename(const String& filename); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.translatePath(path) } + /// Returns the translated path. + /// + /// @param path string - Path to format + /// @return Translated path + /// + /// @note Only useful if you are coding for both Linux and Windows. + /// e.g. Converts 'special://home' -> '/home/[username]/.kodi' + /// on Linux. + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v19 New function added (replaces old **xbmc.translatePath**) + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// fpath = xbmcvfs.translatePath('special://home') + /// .. + /// ~~~~~~~~~~~~~ + /// + translatePath(...); +#else + String translatePath(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.validatePath(path) } + /// Returns the validated path. + /// + /// @param path string - Path to format + /// @return Validated path + /// + /// @note The result is platform-specific. Only useful if you are coding + /// for multiple platfforms for fixing slash problems + /// (e.g. Corrects 'Z://something' -> 'Z:\something'). + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v19 New function added (replaces old **xbmc.validatePath**) + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// fpath = xbmcvfs.validatePath(somepath) + /// .. + /// ~~~~~~~~~~~~~ + /// + validatePath(...); +#else + String validatePath(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.mkdir(path) } + /// Create a folder. + /// + /// @param path Folder to create + /// @return True if successed + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.mkdir(path) + /// .. + /// ~~~~~~~~~~~~~ + /// + mkdir(...); +#else + bool mkdir(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.mkdirs(path) } + /// Make all directories along the path + /// + /// Create folder(s) - it will create all folders in the path. + /// + /// @param path Folders to create + /// @return True if successed + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.mkdirs(path) + /// .. + /// ~~~~~~~~~~~~~ + /// + mkdirs(...); +#else + bool mkdirs(const String& path); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.rmdir(path, [force]) } + /// Remove a folder. + /// + /// @param path string - Folder to remove + /// @param force [opt] bool - Force directory removal + /// (default False). This can be useful + /// if the directory is not empty. + /// @return bool - True if successful, False + /// otherwise + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// success = xbmcvfs.rmdir(path) + /// .. + /// ~~~~~~~~~~~~~ + /// + rmdir(...); +#else + bool rmdir(const String& path, bool force = false); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcvfs + /// @brief \python_func{ xbmcvfs.listdir(path) } + /// Lists content of a folder. + /// + /// @param path Folder to get list from + /// @return Directory content list + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dirs, files = xbmcvfs.listdir(path) + /// .. + /// ~~~~~~~~~~~~~ + /// + listdir(...); +#else + Tuple<std::vector<String>, std::vector<String> > listdir(const String& path); +#endif + //@} + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + } +} +#endif /* DOXYGEN_SHOULD_SKIP_THIS */ diff --git a/xbmc/interfaces/legacy/Monitor.cpp b/xbmc/interfaces/legacy/Monitor.cpp new file mode 100644 index 0000000..34d0553 --- /dev/null +++ b/xbmc/interfaces/legacy/Monitor.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 "Monitor.h" + +#include "threads/SystemClock.h" + +#include <algorithm> +#include <math.h> + +using namespace std::chrono_literals; + +namespace XBMCAddon +{ + namespace xbmc + { + Monitor::Monitor(): abortEvent(true) + { + XBMC_TRACE; + if (languageHook) + { + Id = languageHook->GetAddonId(); + invokerId = languageHook->GetInvokerId(); + languageHook->RegisterMonitorCallback(this); + } + } + + void Monitor::AbortNotify() + { + XBMC_TRACE; + abortEvent.Set(); + } + + bool Monitor::waitForAbort(double timeout) + { + XBMC_TRACE; + int timeoutMS = ceil(timeout * 1000); + XbmcThreads::EndTime<> endTime{std::chrono::milliseconds(timeoutMS)}; + + if (timeoutMS <= 0) + endTime.SetInfinite(); + + while (!endTime.IsTimePast()) + { + { + DelayedCallGuard dg(languageHook); + auto timeout = std::min(endTime.GetTimeLeft(), 100ms); + if (abortEvent.Wait(timeout)) + return true; + } + if (languageHook) + languageHook->MakePendingCalls(); + } + return false; + } + + bool Monitor::abortRequested() + { + XBMC_TRACE; + return abortEvent.Signaled(); + } + + Monitor::~Monitor() + { + XBMC_TRACE; + deallocating(); + DelayedCallGuard dg(languageHook); + // we're shutting down so unregister me. + if (languageHook) + { + DelayedCallGuard dc; + languageHook->UnregisterMonitorCallback(this); + } + } + } +} + diff --git a/xbmc/interfaces/legacy/Monitor.h b/xbmc/interfaces/legacy/Monitor.h new file mode 100644 index 0000000..0d3d831 --- /dev/null +++ b/xbmc/interfaces/legacy/Monitor.h @@ -0,0 +1,311 @@ +/* + * 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 "AddonCallback.h" +#include "AddonString.h" + +namespace XBMCAddon +{ + namespace xbmc + { + + /// + /// \ingroup python_xbmc + /// \defgroup python_monitor Monitor + /// @{ + /// @brief **Kodi's monitor class.** + /// + /// \python_class{ xbmc.Monitor() } + /// + /// Creates a new monitor to notify addon about changes. + /// + class Monitor : public AddonCallback + { + String Id; + long invokerId; + CEvent abortEvent; + public: + Monitor(); + +#ifndef SWIG + inline void OnSettingsChanged() { XBMC_TRACE; invokeCallback(new CallbackFunction<Monitor>(this,&Monitor::onSettingsChanged)); } + inline void OnScreensaverActivated() { XBMC_TRACE; invokeCallback(new CallbackFunction<Monitor>(this,&Monitor::onScreensaverActivated)); } + inline void OnScreensaverDeactivated() { XBMC_TRACE; invokeCallback(new CallbackFunction<Monitor>(this,&Monitor::onScreensaverDeactivated)); } + inline void OnDPMSActivated() { XBMC_TRACE; invokeCallback(new CallbackFunction<Monitor>(this,&Monitor::onDPMSActivated)); } + inline void OnDPMSDeactivated() { XBMC_TRACE; invokeCallback(new CallbackFunction<Monitor>(this,&Monitor::onDPMSDeactivated)); } + inline void OnScanStarted(const String &library) + { + XBMC_TRACE; + invokeCallback( + new CallbackFunction<Monitor, const String>(this, &Monitor::onScanStarted, library)); + } + inline void OnScanFinished(const String &library) + { + XBMC_TRACE; + invokeCallback( + new CallbackFunction<Monitor, const String>(this, &Monitor::onScanFinished, library)); + } + inline void OnCleanStarted(const String& library) + { + XBMC_TRACE; + invokeCallback( + new CallbackFunction<Monitor, const String>(this, &Monitor::onCleanStarted, library)); + } + inline void OnCleanFinished(const String& library) + { + XBMC_TRACE; + invokeCallback( + new CallbackFunction<Monitor, const String>(this, &Monitor::onCleanFinished, library)); + } + inline void OnNotification(const String& sender, const String& method, const String& data) + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Monitor, const String, const String, const String>( + this, &Monitor::onNotification, sender, method, data)); + } + + inline const String& GetId() { return Id; } + inline long GetInvokerId() { return invokerId; } + + /** + * Called from XBPython to notify registered monitors that a script is aborting/ending. + */ + void AbortNotify(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onSettingsChanged() } + /// onSettingsChanged method. + /// + /// Will be called when addon settings are changed + /// + onSettingsChanged(); +#else + virtual void onSettingsChanged() { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onScreensaverActivated() } + /// onScreensaverActivated method. + /// + /// Will be called when screensaver kicks in + /// + onScreensaverActivated(); +#else + virtual void onScreensaverActivated() { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onScreensaverDeactivated() } + /// onScreensaverDeactivated method. + /// + /// Will be called when screensaver goes off + /// + onScreensaverDeactivated(); +#else + virtual void onScreensaverDeactivated() { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onDPMSActivated() } + /// onDPMSActivated method. + /// + /// Will be called when energysaving/DPMS gets active + /// + onDPMSActivated(); +#else + virtual void onDPMSActivated() { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onDPMSDeactivated() } + /// onDPMSDeactivated method. + /// + /// Will be called when energysaving/DPMS is turned off + /// + onDPMSDeactivated(); +#else + virtual void onDPMSDeactivated() { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onScanStarted(library) } + /// onScanStarted method. + /// + /// @param library Video / music as string + /// + /// + /// @note Will be called when library clean has ended and return video or + /// music to indicate which library is being scanned + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + onScanStarted(...); +#else + virtual void onScanStarted(const String library) { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onScanFinished(library) } + /// onScanFinished method. + /// + /// @param library Video / music as string + /// + /// + /// @note Will be called when library clean has ended and return video or + /// music to indicate which library has been scanned + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + onScanFinished(...); +#else + virtual void onScanFinished(const String library) { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onCleanStarted(library) } + /// onCleanStarted method. + /// + /// @param library Video / music as string + /// + /// + /// @note Will be called when library clean has ended and return video or + /// music to indicate which library has been cleaned + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + onCleanStarted(...); +#else + virtual void onCleanStarted(const String library) { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onCleanFinished(library) } + /// onCleanFinished method. + /// + /// @param library Video / music as string + /// + /// + /// @note Will be called when library clean has ended and return video or + /// music to indicate which library has been finished + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + onCleanFinished(...); +#else + virtual void onCleanFinished(const String library) { XBMC_TRACE; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_monitor + /// @brief \python_func{ onNotification(sender, method, data) } + /// onNotification method. + /// + /// @param sender Sender of the notification + /// @param method Name of the notification + /// @param data JSON-encoded data of the notification + /// + /// @note Will be called when Kodi receives or sends a notification + /// + /// + ///----------------------------------------------------------------------- + /// @python_v13 New function added. + /// + onNotification(...); +#else + virtual void onNotification(const String sender, const String method, const String data) + { + XBMC_TRACE; + } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_monitor + /// @brief \python_func{ waitForAbort([timeout]) } + /// Wait for Abort + /// \anchor xbmc_Monitor_waitForAbort + /// + /// Block until abort is requested, or until timeout occurs. If an + /// abort requested have already been made, return immediately. + /// + /// @param timeout [opt] float - timeout in seconds. + /// Default: no timeout. + /// + /// @return True when abort have been requested, + /// False if a timeout is given and the + /// operation times out. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// monitor = xbmc.Monitor() + /// # do something + /// monitor.waitForAbort(10) # sleeps for 10 secs or returns early if kodi aborts + /// if monitor.abortRequested(): + /// # abort was requested to Kodi (e.g. shutdown), do your cleanup logic + /// .. + /// ~~~~~~~~~~~~~ + /// + waitForAbort(...); +#else + bool waitForAbort(double timeout = -1); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_monitor + /// @brief \python_func{ abortRequested() } + /// Returns True if abort has been requested. + /// + /// @return True if requested + /// + /// + ///----------------------------------------------------------------------- + /// @python_v14 New function added. + /// + abortRequested(); +#else + bool abortRequested(); +#endif + ~Monitor() override; + }; + /** @} */ + } +}; diff --git a/xbmc/interfaces/legacy/PlayList.cpp b/xbmc/interfaces/legacy/PlayList.cpp new file mode 100644 index 0000000..1117a81 --- /dev/null +++ b/xbmc/interfaces/legacy/PlayList.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PlayList.h" + +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "playlists/PlayListFactory.h" +#include "utils/URIUtils.h" + +namespace XBMCAddon +{ + namespace xbmc + { + //! @todo need a means to check for a valid construction + //! either by throwing an exception or by an "isValid" check + PlayList::PlayList(int playList) : + iPlayList(playList), pPlayList(NULL) + { + // we do not create our own playlist, just using the ones from playlistplayer + if (iPlayList != PLAYLIST::TYPE_MUSIC && iPlayList != PLAYLIST::TYPE_VIDEO) + throw PlayListException("PlayList does not exist"); + + pPlayList = &CServiceBroker::GetPlaylistPlayer().GetPlaylist(playList); + iPlayList = playList; + } + + PlayList::~PlayList() = default; + + void PlayList::add(const String& url, XBMCAddon::xbmcgui::ListItem* listitem, int index) + { + CFileItemList items; + + if (listitem != NULL) + { + // an optional listitem was passed + // set m_strPath to the passed url + listitem->item->SetPath(url); + + items.Add(listitem->item); + } + else + { + CFileItemPtr item(new CFileItem(url, false)); + item->SetLabel(url); + + items.Add(item); + } + + pPlayList->Insert(items, index); + } + + bool PlayList::load(const char* cFileName) + { + CFileItem item(cFileName); + item.SetPath(cFileName); + + if (item.IsPlayList()) + { + // load playlist and copy al items to existing playlist + + // load a playlist like .m3u, .pls + // first get correct factory to load playlist + std::unique_ptr<PLAYLIST::CPlayList> pPlayList(PLAYLIST::CPlayListFactory::Create(item)); + if (nullptr != pPlayList) + { + // load it + if (!pPlayList->Load(item.GetPath())) + //hmmm unable to load playlist? + return false; + + // clear current playlist + CServiceBroker::GetPlaylistPlayer().ClearPlaylist(this->iPlayList); + + // add each item of the playlist to the playlistplayer + for (int i=0; i < pPlayList->size(); ++i) + { + CFileItemPtr playListItem =(*pPlayList)[i]; + if (playListItem->GetLabel().empty()) + playListItem->SetLabel(URIUtils::GetFileName(playListItem->GetPath())); + + this->pPlayList->Add(playListItem); + } + } + } + else + // filename is not a valid playlist + throw PlayListException("Not a valid playlist"); + + return true; + } + + void PlayList::remove(const char* filename) + { + pPlayList->Remove(filename); + } + + void PlayList::clear() + { + pPlayList->Clear(); + } + + int PlayList::size() + { + return pPlayList->size(); + } + + void PlayList::shuffle() + { + pPlayList->Shuffle(); + } + + void PlayList::unshuffle() + { + pPlayList->UnShuffle(); + } + + int PlayList::getposition() + { + return CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); + } + + XBMCAddon::xbmcgui::ListItem* PlayList::operator [](long i) + { + int iPlayListSize = size(); + + long pos = i; + if (pos < 0) pos += iPlayListSize; + + if (pos < 0 || pos >= iPlayListSize) + throw PlayListException("array out of bound"); + + CFileItemPtr ptr((*pPlayList)[pos]); + + return new XBMCAddon::xbmcgui::ListItem(ptr); + } + } +} + diff --git a/xbmc/interfaces/legacy/PlayList.h b/xbmc/interfaces/legacy/PlayList.h new file mode 100644 index 0000000..6162945 --- /dev/null +++ b/xbmc/interfaces/legacy/PlayList.h @@ -0,0 +1,212 @@ +/* + * 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 "AddonClass.h" +#include "Exception.h" +#include "ListItem.h" +#include "playlists/PlayList.h" + +namespace XBMCAddon +{ + namespace xbmc + { + XBMCCOMMONS_STANDARD_EXCEPTION(PlayListException); + + // + /// \defgroup python_PlayList PlayList + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's Play List class.** + /// + /// \python_class{ xbmc.PlayList(playList) } + /// + /// To create and edit a playlist which can be handled by the player. + /// + /// @param playList [integer] To define the stream type + /// | Value | Integer String | Description | + /// |:-----:|:--------------------|:---------------------------------------| + /// | 0 | xbmc.PLAYLIST_MUSIC | Playlist for music files or streams | + /// | 1 | xbmc.PLAYLIST_VIDEO | Playlist for video files or streams | + /// + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// play=xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + /// ... + /// ~~~~~~~~~~~~~ + // + class PlayList : public AddonClass + { + int iPlayList; + PLAYLIST::CPlayList *pPlayList; + + public: + explicit PlayList(int playList); + ~PlayList() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ getPlayListId() } + /// Get the PlayList Identifier + /// + /// @return Id as an integer. + /// + getPlayListId(); +#else + inline int getPlayListId() const { return iPlayList; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ add(url[, listitem, index]) } + /// Adds a new file to the playlist. + /// + /// @param url string or unicode - filename or url to add. + /// @param listitem [opt] listitem - used with setInfo() to set different infolabels. + /// @param index [opt] integer - position to add playlist item. (default=end) + /// + /// @note You can use the above as keywords for arguments and skip certain optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// playlist = xbmc.PlayList(xbmc.PLAYLIST_VIDEO) + /// video = 'F:\\movies\\Ironman.mov' + /// listitem = xbmcgui.ListItem('Ironman', thumbnailImage='F:\\movies\\Ironman.tbn') + /// listitem.setInfo('video', {'Title': 'Ironman', 'Genre': 'Science Fiction'}) + /// playlist.add(url=video, listitem=listitem, index=7) + /// .. + /// ~~~~~~~~~~~~~ + /// + add(...); +#else + void add(const String& url, XBMCAddon::xbmcgui::ListItem* listitem = NULL, int index = -1); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ load(filename) } + /// Load a playlist. + /// + /// Clear current playlist and copy items from the file to this Playlist + /// filename can be like .pls or .m3u ... + /// + /// @param filename File with list to play inside + /// @return False if unable to load playlist + /// + load(...); +#else + bool load(const char* filename); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ remove(filename) } + /// Remove an item with this filename from the playlist. + /// + /// @param filename The file to remove from list. + /// + remove(...); +#else + void remove(const char* filename); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ clear() } + /// Clear all items in the playlist. + /// + clear(); +#else + void clear(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ size() } + /// Returns the total number of PlayListItems in this playlist. + /// + /// @return Amount of playlist entries. + /// + size(); +#else + int size(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ shuffle() } + /// Shuffle the playlist. + /// + shuffle(); +#else + void shuffle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ unshuffle() } + /// Unshuffle the playlist. + /// + unshuffle(); +#else + void unshuffle(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ getposition() } + /// Returns the position of the current song in this playlist. + /// + /// @return Position of the current song + /// + getposition(); +#else + int getposition(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayList + /// @brief \python_func{ [] } + /// Retrieve the item at the given position. + /// + /// @param i Pointer in list + /// @return The selected item on list + /// + /// @note A negative index means + /// from the end rather than from the start. + /// + [](...); +#else + XBMCAddon::xbmcgui::ListItem* operator[](long i); +#endif + }; + /// @} + } +} + diff --git a/xbmc/interfaces/legacy/Player.cpp b/xbmc/interfaces/legacy/Player.cpp new file mode 100644 index 0000000..cf982ba --- /dev/null +++ b/xbmc/interfaces/legacy/Player.cpp @@ -0,0 +1,607 @@ +/* + * 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 "Player.h" + +#include "AddonUtils.h" +#include "GUIInfoManager.h" +#include "GUIUserMessages.h" +#include "ListItem.h" +#include "PlayList.h" +#include "PlayListPlayer.h" +#include "ServiceBroker.h" +#include "application/Application.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "cores/IPlayer.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/MediaSettings.h" + +namespace +{ + +std::shared_ptr<const CApplicationPlayer> getAppPlayer() +{ + const auto& components = CServiceBroker::GetAppComponents(); + auto res = components.GetComponent<CApplicationPlayer>(); + return res; +} + +std::shared_ptr<CApplicationPlayer> getAppPlayerMut() +{ + auto& components = CServiceBroker::GetAppComponents(); + auto res = components.GetComponent<CApplicationPlayer>(); + return res; +} + +} // namespace + +namespace XBMCAddon +{ + namespace xbmc + { + PlayParameter Player::defaultPlayParameter; + + Player::Player() + { + iPlayList = PLAYLIST::TYPE_MUSIC; + + // now that we're done, register hook me into the system + if (languageHook) + { + DelayedCallGuard dc(languageHook); + languageHook->RegisterPlayerCallback(this); + } + } + + Player::~Player() + { + deallocating(); + + // we're shutting down so unregister me. + if (languageHook) + { + DelayedCallGuard dc(languageHook); + languageHook->UnregisterPlayerCallback(this); + } + } + + void Player::play(const Alternative<String, const PlayList* > & item, + const XBMCAddon::xbmcgui::ListItem* listitem, bool windowed, int startpos) + { + XBMC_TRACE; + + if (&item == &defaultPlayParameter) + playCurrent(windowed); + else if (item.which() == XBMCAddon::first) + playStream(item.former(), listitem, windowed); + else // item is a PlayListItem + playPlaylist(item.later(),windowed,startpos); + } + + void Player::playStream(const String& item, const xbmcgui::ListItem* plistitem, bool windowed) + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + if (!item.empty()) + { + // set fullscreen or windowed + CMediaSettings::GetInstance().SetMediaStartWindowed(windowed); + + const AddonClass::Ref<xbmcgui::ListItem> listitem(plistitem); + + if (listitem.isSet()) + { + // set m_strPath to the passed url + listitem->item->SetPath(item.c_str()); + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(new CFileItem(*listitem->item))); + } + else + { + CFileItemList *l = new CFileItemList; //don't delete, + l->Add(std::make_shared<CFileItem>(item, false)); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, -1, -1, + static_cast<void*>(l)); + } + } + else + playCurrent(windowed); + } + + void Player::playCurrent(bool windowed) + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + // set fullscreen or windowed + CMediaSettings::GetInstance().SetMediaStartWindowed(windowed); + + // play current file in playlist + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != iPlayList) + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(iPlayList); + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_PLAYLISTPLAYER_PLAY, CServiceBroker::GetPlaylistPlayer().GetCurrentSong()); + } + + void Player::playPlaylist(const PlayList* playlist, bool windowed, int startpos) + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + if (playlist != NULL) + { + // set fullscreen or windowed + CMediaSettings::GetInstance().SetMediaStartWindowed(windowed); + + // play a python playlist (a playlist from playlistplayer.cpp) + iPlayList = playlist->getPlayListId(); + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(iPlayList); + if (startpos > -1) + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(startpos); + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY, startpos); + } + else + playCurrent(windowed); + } + + void Player::stop() + { + XBMC_TRACE; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_STOP); + } + + void Player::pause() + { + XBMC_TRACE; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_MEDIA_PAUSE); + } + + void Player::playnext() + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_NEXT); + } + + void Player::playprevious() + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PREV); + } + + void Player::playselected(int selected) + { + XBMC_TRACE; + DelayedCallGuard dc(languageHook); + + if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() != iPlayList) + { + CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(iPlayList); + } + CServiceBroker::GetPlaylistPlayer().SetCurrentSong(selected); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY, selected); + //CServiceBroker::GetPlaylistPlayer().Play(selected); + //CLog::Log(LOGINFO, "Current Song After Play: {}", CServiceBroker::GetPlaylistPlayer().GetCurrentSong()); + } + + void Player::OnPlayBackStarted(const CFileItem &file) + { + // We only have fileItem due to us having to + // implement the interface, we can't send it to python + // as we're not able to serialize it. + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this, &Player::onPlayBackStarted)); + } + + void Player::OnAVStarted(const CFileItem &file) + { + // We only have fileItem due to us having to + // implement the interface, we can't send it to python + // as we're not able to serialize it. + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this, &Player::onAVStarted)); + } + + void Player::OnAVChange() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this, &Player::onAVChange)); + } + + void Player::OnPlayBackEnded() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onPlayBackEnded)); + } + + void Player::OnPlayBackStopped() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onPlayBackStopped)); + } + + void Player::OnPlayBackError() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onPlayBackError)); + } + + void Player::OnPlayBackPaused() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onPlayBackPaused)); + } + + void Player::OnPlayBackResumed() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onPlayBackResumed)); + } + + void Player::OnQueueNextItem() + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player>(this,&Player::onQueueNextItem)); + } + + void Player::OnPlayBackSpeedChanged(int speed) + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player,int>(this,&Player::onPlayBackSpeedChanged,speed)); + } + + void Player::OnPlayBackSeek(int64_t time, int64_t seekOffset) + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player,int,int>(this,&Player::onPlayBackSeek,static_cast<int>(time),static_cast<int>(seekOffset))); + } + + void Player::OnPlayBackSeekChapter(int chapter) + { + XBMC_TRACE; + invokeCallback(new CallbackFunction<Player,int>(this,&Player::onPlayBackSeekChapter,chapter)); + } + + void Player::onPlayBackStarted() + { + XBMC_TRACE; + } + + void Player::onAVStarted() + { + XBMC_TRACE; + } + + void Player::onAVChange() + { + XBMC_TRACE; + } + + void Player::onPlayBackEnded() + { + XBMC_TRACE; + } + + void Player::onPlayBackStopped() + { + XBMC_TRACE; + } + + void Player::onPlayBackError() + { + XBMC_TRACE; + } + + void Player::onPlayBackPaused() + { + XBMC_TRACE; + } + + void Player::onPlayBackResumed() + { + XBMC_TRACE; + } + + void Player::onQueueNextItem() + { + XBMC_TRACE; + } + + void Player::onPlayBackSpeedChanged(int speed) + { + XBMC_TRACE; + } + + void Player::onPlayBackSeek(int time, int seekOffset) + { + XBMC_TRACE; + } + + void Player::onPlayBackSeekChapter(int chapter) + { + XBMC_TRACE; + } + + bool Player::isPlaying() + { + XBMC_TRACE; + return getAppPlayer()->IsPlaying(); + } + + bool Player::isPlayingAudio() + { + XBMC_TRACE; + return getAppPlayer()->IsPlayingAudio(); + } + + bool Player::isPlayingVideo() + { + XBMC_TRACE; + return getAppPlayer()->IsPlayingVideo(); + } + + bool Player::isPlayingRDS() + { + XBMC_TRACE; + return getAppPlayer()->IsPlayingRDS(); + } + + bool Player::isPlayingGame() + { + XBMC_TRACE; + return getAppPlayer()->IsPlayingGame(); + } + + bool Player::isExternalPlayer() + { + XBMC_TRACE; + return getAppPlayer()->IsExternalPlaying(); + } + + String Player::getPlayingFile() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any file"); + + return g_application.CurrentFileItem().GetDynPath(); + } + + XBMCAddon::xbmcgui::ListItem* Player::getPlayingItem() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any item"); + + CFileItemPtr itemPtr = std::make_shared<CFileItem>(g_application.CurrentFileItem()); + return new XBMCAddon::xbmcgui::ListItem(itemPtr); + } + + InfoTagVideo* Player::getVideoInfoTag() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlayingVideo()) + throw PlayerException("Kodi is not playing any videofile"); + + const CVideoInfoTag* movie = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentMovieTag(); + if (movie) + return new InfoTagVideo(movie); + + return new InfoTagVideo(true); + } + + InfoTagMusic* Player::getMusicInfoTag() + { + XBMC_TRACE; + if (getAppPlayer()->IsPlayingVideo() || !getAppPlayer()->IsPlayingAudio()) + throw PlayerException("Kodi is not playing any music file"); + + const MUSIC_INFO::CMusicInfoTag* tag = CServiceBroker::GetGUI()->GetInfoManager().GetCurrentSongTag(); + if (tag) + return new InfoTagMusic(tag); + + return new InfoTagMusic(true); + } + + void Player::updateInfoTag(const XBMCAddon::xbmcgui::ListItem* item) + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any file"); + + CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_UPDATE_ITEM, 0, item->item); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } + + InfoTagGame* Player::getGameInfoTag() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlayingGame()) + throw PlayerException("Kodi is not playing any game file"); + + const KODI::GAME::CGameInfoTag* game = + CServiceBroker::GetGUI()->GetInfoManager().GetCurrentGameTag(); + if (game) + return new InfoTagGame(game); + + return new InfoTagGame(); + } + + InfoTagRadioRDS* Player::getRadioRDSInfoTag() + { + XBMC_TRACE; + if (getAppPlayer()->IsPlayingVideo() || !getAppPlayer()->IsPlayingRDS()) + throw PlayerException("Kodi is not playing any music file with RDS"); + + std::shared_ptr<CFileItem> item = g_application.CurrentFileItemPtr(); + if (item && item->HasPVRChannelInfoTag()) + return new InfoTagRadioRDS(item->GetPVRChannelInfoTag()); + + return new InfoTagRadioRDS(); + } + + double Player::getTotalTime() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any media file"); + + return g_application.GetTotalTime(); + } + + double Player::getTime() + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any media file"); + + return g_application.GetTime(); + } + + void Player::seekTime(double pTime) + { + XBMC_TRACE; + if (!getAppPlayer()->IsPlaying()) + throw PlayerException("Kodi is not playing any media file"); + + g_application.SeekTime( pTime ); + } + + void Player::setSubtitles(const char* cLine) + { + XBMC_TRACE; + if (getAppPlayer()->HasPlayer()) + { + getAppPlayerMut()->AddSubtitle(cLine); + } + } + + void Player::showSubtitles(bool bVisible) + { + XBMC_TRACE; + if (getAppPlayer()->HasPlayer()) + { + getAppPlayerMut()->SetSubtitleVisible(bVisible != 0); + } + } + + String Player::getSubtitles() + { + XBMC_TRACE; + if (getAppPlayer()->HasPlayer()) + { + SubtitleStreamInfo info; + getAppPlayerMut()->GetSubtitleStreamInfo(CURRENT_STREAM, info); + + if (info.language.length() > 0) + return info.language; + else + return info.name; + } + + return ""; + } + + std::vector<String> Player::getAvailableSubtitleStreams() + { + if (getAppPlayer()->HasPlayer()) + { + int subtitleCount = getAppPlayer()->GetSubtitleCount(); + std::vector<String> ret(subtitleCount); + for (int iStream=0; iStream < subtitleCount; iStream++) + { + SubtitleStreamInfo info; + getAppPlayer()->GetSubtitleStreamInfo(iStream, info); + + if (info.language.length() > 0) + ret[iStream] = info.language; + else + ret[iStream] = info.name; + } + return ret; + } + + return std::vector<String>(); + } + + void Player::setSubtitleStream(int iStream) + { + if (getAppPlayer()->HasPlayer()) + { + int streamCount = getAppPlayer()->GetSubtitleCount(); + if(iStream < streamCount) + { + getAppPlayerMut()->SetSubtitle(iStream); + getAppPlayerMut()->SetSubtitleVisible(true); + } + } + } + + std::vector<String> Player::getAvailableAudioStreams() + { + if (getAppPlayer()->HasPlayer()) + { + int streamCount = getAppPlayer()->GetAudioStreamCount(); + std::vector<String> ret(streamCount); + for (int iStream=0; iStream < streamCount; iStream++) + { + AudioStreamInfo info; + getAppPlayerMut()->GetAudioStreamInfo(iStream, info); + + if (info.language.length() > 0) + ret[iStream] = info.language; + else + ret[iStream] = info.name; + } + return ret; + } + + return std::vector<String>(); + } + + void Player::setAudioStream(int iStream) + { + if (getAppPlayer()->HasPlayer()) + { + int streamCount = getAppPlayer()->GetAudioStreamCount(); + if (iStream < streamCount) + getAppPlayerMut()->SetAudioStream(iStream); + } + } + + std::vector<String> Player::getAvailableVideoStreams() + { + int streamCount = getAppPlayer()->GetVideoStreamCount(); + std::vector<String> ret(streamCount); + for (int iStream = 0; iStream < streamCount; ++iStream) + { + VideoStreamInfo info; + getAppPlayer()->GetVideoStreamInfo(iStream, info); + + if (info.language.length() > 0) + ret[iStream] = info.language; + else + ret[iStream] = info.name; + } + return ret; + } + + void Player::setVideoStream(int iStream) + { + int streamCount = getAppPlayer()->GetVideoStreamCount(); + if (iStream < streamCount) + getAppPlayerMut()->SetVideoStream(iStream); + } + } +} + diff --git a/xbmc/interfaces/legacy/Player.h b/xbmc/interfaces/legacy/Player.h new file mode 100644 index 0000000..b700597 --- /dev/null +++ b/xbmc/interfaces/legacy/Player.h @@ -0,0 +1,824 @@ +/* + * 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 "AddonCallback.h" +#include "AddonString.h" +#include "Alternative.h" +#include "Exception.h" +#include "InfoTagGame.h" +#include "InfoTagMusic.h" +#include "InfoTagRadioRDS.h" +#include "InfoTagVideo.h" +#include "ListItem.h" +#include "PlayList.h" +#include "cores/IPlayerCallback.h" +#include "swighelper.h" + +#include <vector> + +namespace XBMCAddon +{ + namespace xbmc + { + XBMCCOMMONS_STANDARD_EXCEPTION(PlayerException); + + typedef Alternative<String, const PlayList* > PlayParameter; + + // This class is a merge of what was previously in xbmcmodule/player.h + // and xbmcmodule/PythonPlayer.h without the python references. The + // queuing and handling of asynchronous callbacks is done internal to + // this class. + + // + /// \defgroup python_Player Player + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's player.** + /// + /// \python_class{ xbmc.Player() } + /// + /// To become and create the class to play something. + /// + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// xbmc.Player().play(url, listitem, windowed) + /// ... + /// ~~~~~~~~~~~~~ + // + class Player : public AddonCallback, public IPlayerCallback + { + private: + int iPlayList; + + void playStream(const String& item = emptyString, const XBMCAddon::xbmcgui::ListItem* listitem = NULL, bool windowed = false); + void playPlaylist(const PlayList* playlist = NULL, + bool windowed = false, int startpos=-1); + void playCurrent(bool windowed = false); + + public: +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + static PlayParameter defaultPlayParameter; +#endif + +#ifndef DOXYGEN_SHOULD_SKIP_THIS + // Construct a Player proxying the given generated binding. The + // construction of a Player needs to identify whether or not any + // callbacks will be executed asynchronously or not. + explicit Player(); + ~Player(void) override; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ play([item, listitem, windowed, startpos]) } + /// Play an item. + /// + /// @param item [opt] string - filename, url or playlist + /// @param listitem [opt] listitem - used with setInfo() to set + /// different infolabels. + /// @param windowed [opt] bool - true=play video windowed, + /// false=play users preference.(default) + /// @param startpos [opt] int - starting position when playing + /// a playlist. Default = -1 + /// + /// @note If item is not given then the Player will try to play the + /// current item in the current playlist.\n + /// \n + /// You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword. + /// + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// listitem = xbmcgui.ListItem('Ironman') + /// listitem.setInfo('video', {'Title': 'Ironman', 'Genre': 'Science Fiction'}) + /// xbmc.Player().play(url, listitem, windowed) + /// xbmc.Player().play(playlist, listitem, windowed, startpos) + /// ... + /// ~~~~~~~~~~~~~ + /// + play(...); +#else + void play(const PlayParameter& item = Player::defaultPlayParameter, + const XBMCAddon::xbmcgui::ListItem* listitem = NULL, bool windowed = false, int startpos = -1); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ stop() } + /// Stop playing. + /// + stop(); +#else + void stop(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ pause() } + /// Pause or resume playing if already paused. + /// + pause(); +#else + void pause(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ playnext() } + /// Play next item in playlist. + /// + playnext(); +#else + void playnext(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ playprevious() } + /// Play previous item in playlist. + /// + playprevious(); +#else + void playprevious(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ playselected(selected) } + /// Play a certain item from the current playlist. + /// + /// @param selected Integer - Item to select + /// + playselected(...); +#else + void playselected(int selected); +#endif + + // + /// @defgroup python_PlayerCB Callback functions from Kodi to Add-On + /// \ingroup python_Player + /// @{ + /// @brief **Callback functions.** + /// + /// Functions to handle control callbacks from Kodi to Add-On. + /// + /// ---------------------------------------------------------------------- + /// + /// @link python_Player Go back to normal functions from player@endlink + // + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackStarted() } + /// onPlayBackStarted method. + /// + /// Will be called when Kodi player starts. Video or audio might not be available at this point. + /// + ///------------------------------------------------------------------------ + /// @python_v18 Use onAVStarted() instead if you need to detect if Kodi is actually playing a media file + /// (i.e, if a stream is available) + /// + onPlayBackStarted(); +#else + virtual void onPlayBackStarted(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onAVStarted() } + /// onAVStarted method. + /// + /// Will be called when Kodi has a video or audiostream. + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + onAVStarted(); +#else + virtual void onAVStarted(); +#endif + + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onAVChange() } + /// onAVChange method. + /// + /// Will be called when Kodi has a video, audio or subtitle stream. Also happens when the stream changes. + /// + ///------------------------------------------------------------------------ + /// @python_v18 New function added. + /// + onAVChange(); +#else + virtual void onAVChange(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackEnded() } + /// onPlayBackEnded method. + /// + /// Will be called when Kodi stops playing a file. + /// + onPlayBackEnded(); +#else + virtual void onPlayBackEnded(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackStopped() } + /// onPlayBackStopped method. + /// + /// Will be called when user stops Kodi playing a file. + /// + onPlayBackStopped(); +#else + virtual void onPlayBackStopped(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackError() } + /// onPlayBackError method. + /// + /// Will be called when playback stops due to an error. + /// + onPlayBackError(); +#else + virtual void onPlayBackError(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackPaused() } + /// onPlayBackPaused method. + /// + /// Will be called when user pauses a playing file. + /// + onPlayBackPaused(); +#else + virtual void onPlayBackPaused(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackResumed() } + /// onPlayBackResumed method. + /// + /// Will be called when user resumes a paused file. + /// + onPlayBackResumed(); +#else + virtual void onPlayBackResumed(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onQueueNextItem() } + /// onQueueNextItem method. + /// + /// Will be called when user queues the next item. + /// + onQueueNextItem(); +#else + virtual void onQueueNextItem(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackSpeedChanged(speed) } + /// onPlayBackSpeedChanged method. + /// + /// Will be called when players speed changes (eg. user FF/RW). + /// + /// @param speed [integer] Current speed of player + /// + /// @note Negative speed means player is rewinding, 1 is normal playback + /// speed. + /// + onPlayBackSpeedChanged(int speed); +#else + virtual void onPlayBackSpeedChanged(int speed); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackSeek(time, seekOffset) } + /// onPlayBackSeek method. + /// + /// Will be called when user seeks to a time. + /// + /// @param time [integer] Time to seek to + /// @param seekOffset [integer] ? + /// + onPlayBackSeek(...); +#else + virtual void onPlayBackSeek(int time, int seekOffset); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_PlayerCB + /// @brief \python_func{ onPlayBackSeekChapter(chapter) } + /// onPlayBackSeekChapter method. + /// + /// Will be called when user performs a chapter seek. + /// + /// @param chapter [integer] Chapter to seek to + /// + onPlayBackSeekChapter(...); +#else + virtual void onPlayBackSeekChapter(int chapter); +#endif + /// @} + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isPlaying() } + /// Check Kodi is playing something. + /// + /// @return True if Kodi is playing a file. + /// + isPlaying(); +#else + bool isPlaying(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isPlayingAudio() } + /// Check for playing audio. + /// + /// @return True if Kodi is playing an audio file. + /// + isPlayingAudio(); +#else + bool isPlayingAudio(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isPlayingVideo() } + /// Check for playing video. + /// + /// @return True if Kodi is playing a video. + /// + isPlayingVideo(); +#else + bool isPlayingVideo(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isPlayingRDS() } + /// Check for playing radio data system (RDS). + /// + /// @return True if kodi is playing a radio data + /// system (RDS). + /// + isPlayingRDS(); +#else + bool isPlayingRDS(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isPlayingGame() } + /// Check for playing game. + /// + /// @return True if kodi is playing a game + /// + ///------------------------------------------------------------------------ + /// @python_v20 New function added. + /// + isPlayingGame(); +#else + bool isPlayingGame(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ isExternalPlayer() } + /// Check for external player. + /// + /// @return True if kodi is playing using an + /// external player. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v18 New function added. + /// + isExternalPlayer(); +#else + bool isExternalPlayer(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getPlayingFile() } + /// Returns the current playing file as a string. + /// + /// @note For LiveTV, returns a __pvr://__ url which is not translatable + /// to an OS specific file or external url. + /// + /// @return Playing filename + /// @throws Exception If player is not playing a file. + /// + getPlayingFile(); +#else + String getPlayingFile(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getPlayingItem() } + /// Returns the current playing item. + /// + /// @return Playing item + /// @throws Exception If player is not playing a file. + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + getPlayingItem(); +#else + XBMCAddon::xbmcgui::ListItem* getPlayingItem(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getTime() } + /// Get playing time. + /// + /// Returns the current time of the current playing media as fractional + /// seconds. + /// + /// @return Current time as fractional seconds + /// @throws Exception If player is not playing a file. + /// + getTime(); +#else + double getTime(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ seekTime(seekTime) } + /// Seek time. + /// + /// Seeks the specified amount of time as fractional seconds. + /// The time specified is relative to the beginning of the currently. + /// playing media file. + /// + /// @param seekTime Time to seek as fractional seconds + /// @throws Exception If player is not playing a file. + /// + seekTime(...); +#else + void seekTime(double seekTime); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ setSubtitles(subtitleFile) } + /// Set subtitle file and enable subtitles. + /// + /// @param subtitleFile File to use as source ofsubtitles + /// + setSubtitles(...); +#else + void setSubtitles(const char* subtitleFile); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ showSubtitles(visible) } + /// Enable / disable subtitles. + /// + /// @param visible [boolean] True for visible subtitles. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// xbmc.Player().showSubtitles(True) + /// ... + /// ~~~~~~~~~~~~~ + /// + showSubtitles(...); +#else + void showSubtitles(bool bVisible); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getSubtitles() } + /// Get subtitle stream name. + /// + /// @return Stream name + /// + getSubtitles(); +#else + String getSubtitles(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getAvailableSubtitleStreams() } + /// Get Subtitle stream names. + /// + /// @return List of subtitle streams as name + /// + getAvailableSubtitleStreams(); +#else + std::vector<String> getAvailableSubtitleStreams(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ setSubtitleStream(stream) } + /// Set Subtitle Stream. + /// + /// @param iStream [int] Subtitle stream to select for play + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// xbmc.Player().setSubtitleStream(1) + /// ... + /// ~~~~~~~~~~~~~ + /// + setSubtitleStream(...); +#else + void setSubtitleStream(int iStream); +#endif + + // Player_UpdateInfoTag +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ updateInfoTag(item) } + /// Update info labels for currently playing item. + /// + /// @param item ListItem with new info + /// + /// @throws Exception If player is not playing a file + /// + /// @python_v18 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// item = xbmcgui.ListItem() + /// item.setPath(xbmc.Player().getPlayingFile()) + /// item.setInfo('music', {'title' : 'foo', 'artist' : 'bar'}) + /// xbmc.Player().updateInfoTag(item) + /// ... + /// ~~~~~~~~~~~~~ + /// + updateInfoTag(); +#else + void updateInfoTag(const XBMCAddon::xbmcgui::ListItem* item); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getGameInfoTag() } + /// To get game info tag. + /// + /// Returns the GameInfoTag of the current playing game. + /// + /// @return Game info tag + /// @throws Exception If player is not playing a file or current + /// file is not a game file. + /// + ///------------------------------------------------------------------------ + /// @python_v20 New function added. + /// + getGameInfoTag(); +#else + InfoTagGame* getGameInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getVideoInfoTag() } + /// To get video info tag. + /// + /// Returns the VideoInfoTag of the current playing Movie. + /// + /// @return Video info tag + /// @throws Exception If player is not playing a file or current + /// file is not a movie file. + /// + getVideoInfoTag(); +#else + InfoTagVideo* getVideoInfoTag(); +#endif + + // Player_GetMusicInfoTag +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getMusicInfoTag() } + /// To get music info tag. + /// + /// Returns the MusicInfoTag of the current playing 'Song'. + /// + /// @return Music info tag + /// @throws Exception If player is not playing a file or current + /// file is not a music file. + /// + getMusicInfoTag(); +#else + InfoTagMusic* getMusicInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getRadioRDSInfoTag() } + /// To get Radio RDS info tag + /// + /// Returns the RadioRDSInfoTag of the current playing 'Radio Song if. + /// present'. + /// + /// @return Radio RDS info tag + /// @throws Exception If player is not playing a file or current + /// file is not a rds file. + /// + getRadioRDSInfoTag(); +#else + InfoTagRadioRDS* getRadioRDSInfoTag(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getTotalTime() } + /// To get total playing time. + /// + /// Returns the total time of the current playing media in seconds. + /// This is only accurate to the full second. + /// + /// @return Total time of the current playing media + /// @throws Exception If player is not playing a file. + /// + getTotalTime(); +#else + double getTotalTime(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getAvailableAudioStreams() } + /// Get Audio stream names + /// + /// @return List of audio streams as name + /// + getAvailableAudioStreams(); +#else + std::vector<String> getAvailableAudioStreams(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ setAudioStream(stream) } + /// Set Audio Stream. + /// + /// @param iStream [int] Audio stream to select for play + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// xbmc.Player().setAudioStream(1) + /// ... + /// ~~~~~~~~~~~~~ + /// + setAudioStream(...); +#else + void setAudioStream(int iStream); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ getAvailableVideoStreams() } + /// Get Video stream names + /// + /// @return List of video streams as name + /// + getAvailableVideoStreams(); +#else + std::vector<String> getAvailableVideoStreams(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_Player + /// @brief \python_func{ setVideoStream(stream) } + /// Set Video Stream. + /// + /// @param iStream [int] Video stream to select for play + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// ... + /// xbmc.Player().setVideoStream(1) + /// ... + /// ~~~~~~~~~~~~~ + /// + setVideoStream(...); +#else + void setVideoStream(int iStream); +#endif + +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + void OnPlayBackStarted(const CFileItem& file) override; + void OnAVStarted(const CFileItem& file) override; + void OnAVChange() override; + void OnPlayBackEnded() override; + void OnPlayBackStopped() override; + void OnPlayBackError() override; + void OnPlayBackPaused() override; + void OnPlayBackResumed() override; + void OnQueueNextItem() override; + void OnPlayBackSpeedChanged(int iSpeed) override; + void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) override; + void OnPlayBackSeekChapter(int iChapter) override; +#endif + + protected: + }; + } +} + diff --git a/xbmc/interfaces/legacy/RenderCapture.h b/xbmc/interfaces/legacy/RenderCapture.h new file mode 100644 index 0000000..cf1b931 --- /dev/null +++ b/xbmc/interfaces/legacy/RenderCapture.h @@ -0,0 +1,206 @@ +/* + * 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 "AddonClass.h" +#include "Exception.h" +#include "ServiceBroker.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" +#include "commons/Buffer.h" + +#include <climits> + +namespace XBMCAddon +{ + namespace xbmc + { + XBMCCOMMONS_STANDARD_EXCEPTION(RenderCaptureException); + + // + /// \defgroup python_xbmc_RenderCapture RenderCapture + /// \ingroup python_xbmc + /// @{ + /// @brief **Kodi's render capture.** + /// + /// \python_class{ RenderCapture() } + /// + /// + ///-------------------------------------------------------------------------- + /// + // + class RenderCapture : public AddonClass + { + unsigned int m_captureId; + unsigned int m_width; + unsigned int m_height; + uint8_t *m_buffer; + + public: + inline RenderCapture() + { + m_captureId = UINT_MAX; + m_buffer = nullptr; + m_width = 0; + m_height = 0; + } + inline ~RenderCapture() override + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + appPlayer->RenderCaptureRelease(m_captureId); + delete [] m_buffer; + } + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ getWidth() } + /// Get width + /// + /// To get width of captured image as set during RenderCapture.capture(). + /// Returns 0 prior to calling capture. + /// + /// @return Width or 0 prior to calling capture + /// + getWidth(); +#else + inline int getWidth() { return m_width; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ getHeight() } + /// Get height + /// + /// To get height of captured image as set during RenderCapture.capture(). + /// Returns 0 prior to calling capture. + /// + /// @return height or 0 prior to calling capture + getHeight(); +#else + inline int getHeight() { return m_height; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ getAspectRatio() } + /// Get aspect ratio of currently displayed video. + /// + /// @return Aspect ratio + /// @warning This may be called prior to calling RenderCapture.capture(). + /// + getAspectRatio(); +#else + inline float getAspectRatio() + { + const auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + return appPlayer->GetRenderAspectRatio(); + } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ getImageFormat() } + /// Get image format + /// + /// @return Format of captured image: 'BGRA' + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 Image will now always be returned in BGRA + /// + getImageFormat() +#else + inline const char* getImageFormat() +#endif + { + return "BGRA"; + } + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ getImage([msecs]) } + /// Returns captured image as a bytearray. + /// + /// @param msecs [opt] Milliseconds to wait. Waits + /// 1000ms if not specified + /// @return Captured image as a bytearray + /// + /// @note The size of the image is m_width * m_height * 4 + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 Added the option to specify wait time in msec. + /// + getImage(...) +#else + inline XbmcCommons::Buffer getImage(unsigned int msecs = 0) +#endif + { + if (!GetPixels(msecs)) + return XbmcCommons::Buffer(0); + + size_t size = m_width * m_height * 4; + return XbmcCommons::Buffer(m_buffer, size); + } + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmc_RenderCapture + /// @brief \python_func{ capture(width, height) } + /// Issue capture request. + /// + /// @param width Width capture image should be rendered to + /// @param height Height capture image should should be rendered to + /// + /// + ///----------------------------------------------------------------------- + /// @python_v17 Removed the option to pass **flags** + /// + capture(...) +#else + inline void capture(int width, int height) +#endif + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + + if (m_buffer) + { + appPlayer->RenderCaptureRelease(m_captureId); + delete [] m_buffer; + } + m_captureId = appPlayer->RenderCaptureAlloc(); + m_width = width; + m_height = height; + m_buffer = new uint8_t[m_width*m_height*4]; + appPlayer->RenderCapture(m_captureId, m_width, m_height, CAPTUREFLAG_CONTINUOUS); + } + +// hide these from swig +#ifndef SWIG + inline bool GetPixels(unsigned int msec) + { + auto& components = CServiceBroker::GetAppComponents(); + const auto appPlayer = components.GetComponent<CApplicationPlayer>(); + return appPlayer->RenderCaptureGetPixels(m_captureId, msec, m_buffer, + m_width * m_height * 4); + } +#endif + + }; + //@} + } +} diff --git a/xbmc/interfaces/legacy/Settings.cpp b/xbmc/interfaces/legacy/Settings.cpp new file mode 100644 index 0000000..0ba77c3 --- /dev/null +++ b/xbmc/interfaces/legacy/Settings.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "Settings.h" + +#include "settings/SettingsBase.h" +#include "settings/lib/Setting.h" + +#include <algorithm> +#include <functional> +#include <utility> + +namespace XBMCAddon +{ +namespace xbmcaddon +{ + +template<class TSetting> +bool GetSettingValue(const std::shared_ptr<CSettingsBase>& settings, + const std::string& key, + typename TSetting::Value& value) +{ + if (key.empty() || !settings->IsLoaded()) + return false; + + auto setting = settings->GetSetting(key); + if (setting == nullptr || setting->GetType() != TSetting::Type()) + return false; + + value = std::static_pointer_cast<TSetting>(setting)->GetValue(); + return true; +} + +template<class TSetting> +bool GetSettingValueList(const std::shared_ptr<CSettingsBase>& settings, + const std::string& key, + std::function<typename TSetting::Value(CVariant)> transform, + std::vector<typename TSetting::Value>& values) +{ + if (key.empty() || !settings->IsLoaded()) + return false; + + auto setting = settings->GetSetting(key); + if (setting == nullptr || setting->GetType() != SettingType::List || + std::static_pointer_cast<CSettingList>(setting)->GetElementType() != TSetting::Type()) + return false; + + const auto variantValues = settings->GetList(key); + std::transform(variantValues.begin(), variantValues.end(), std::back_inserter(values), transform); + return true; +} + +template<class TSetting> +bool SetSettingValue(const std::shared_ptr<CSettingsBase>& settings, + const std::string& key, + typename TSetting::Value value) +{ + if (key.empty() || !settings->IsLoaded()) + return false; + + // try to get the setting + auto setting = settings->GetSetting(key); + if (setting == nullptr || setting->GetType() != TSetting::Type()) + return false; + + return std::static_pointer_cast<TSetting>(setting)->SetValue(value); +} + +template<class TSetting> +bool SetSettingValueList(const std::shared_ptr<CSettingsBase>& settings, + const std::string& key, + const std::vector<typename TSetting::Value>& values) +{ + if (key.empty() || !settings->IsLoaded()) + return false; + + // try to get the setting + auto setting = settings->GetSetting(key); + if (setting == nullptr || setting->GetType() != SettingType::List || + std::static_pointer_cast<CSettingList>(setting)->GetElementType() != TSetting::Type()) + return false; + + std::vector<CVariant> variantValues; + std::transform(values.begin(), values.end(), std::back_inserter(variantValues), + [](typename TSetting::Value value) { return CVariant(value); }); + + return settings->SetList(key, variantValues); +} + +Settings::Settings(std::shared_ptr<CSettingsBase> settings) : settings(std::move(settings)) +{ +} + +bool Settings::getBool(const char* id) +{ + bool value = false; + if (!GetSettingValue<CSettingBool>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"boolean\" for \"%s\"", id); + + return value; +} + +int Settings::getInt(const char* id) +{ + int value = 0; + if (!GetSettingValue<CSettingInt>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"integer\" for \"%s\"", id); + + return value; +} + +double Settings::getNumber(const char* id) +{ + double value = 0.0; + if (!GetSettingValue<CSettingNumber>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"number\" for \"%s\"", id); + + return value; +} + +String Settings::getString(const char* id) +{ + std::string value; + if (!GetSettingValue<CSettingString>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"string\" for \"%s\"", id); + + return value; +} + +std::vector<bool> Settings::getBoolList(const char* id) +{ + const auto transform = [](const CVariant& value) { return value.asBoolean(); }; + std::vector<bool> values; + if (!GetSettingValueList<CSettingBool>(settings, id, transform, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[boolean]\" for \"%s\"", id); + + return values; +} + +std::vector<int> Settings::getIntList(const char* id) +{ + const auto transform = [](const CVariant& value) { return value.asInteger32(); }; + std::vector<int> values; + if (!GetSettingValueList<CSettingInt>(settings, id, transform, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[integer]\" for \"%s\"", id); + + return values; +} + +std::vector<double> Settings::getNumberList(const char* id) +{ + const auto transform = [](const CVariant& value) { return value.asDouble(); }; + std::vector<double> values; + if (!GetSettingValueList<CSettingNumber>(settings, id, transform, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[number]\" for \"%s\"", id); + + return values; +} + +std::vector<String> Settings::getStringList(const char* id) +{ + const auto transform = [](const CVariant& value) { return value.asString(); }; + std::vector<std::string> values; + if (!GetSettingValueList<CSettingString>(settings, id, transform, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[string]\" for \"%s\"", id); + + return values; +} + +void Settings::setBool(const char* id, bool value) +{ + if (!SetSettingValue<CSettingBool>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"boolean\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setInt(const char* id, int value) +{ + if (!SetSettingValue<CSettingInt>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"integer\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setNumber(const char* id, double value) +{ + if (!SetSettingValue<CSettingNumber>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"number\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setString(const char* id, const String& value) +{ + if (!SetSettingValue<CSettingString>(settings, id, value)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"string\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setBoolList(const char* id, const std::vector<bool>& values) +{ + if (!SetSettingValueList<CSettingBool>(settings, id, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[boolean]\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setIntList(const char* id, const std::vector<int>& values) +{ + if (!SetSettingValueList<CSettingInt>(settings, id, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[integer]\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setNumberList(const char* id, const std::vector<double>& values) +{ + if (!SetSettingValueList<CSettingNumber>(settings, id, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[number]\" for \"%s\"", id); + settings->Save(); +} + +void Settings::setStringList(const char* id, const std::vector<String>& values) +{ + if (!SetSettingValueList<CSettingString>(settings, id, values)) + throw XBMCAddon::WrongTypeException("Invalid setting type \"list[string]\" for \"%s\"", id); + settings->Save(); +} + +} // namespace xbmcaddon +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/Settings.h b/xbmc/interfaces/legacy/Settings.h new file mode 100644 index 0000000..8b8bb0e --- /dev/null +++ b/xbmc/interfaces/legacy/Settings.h @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2017-2021 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "commons/Exception.h" +#include "interfaces/legacy/AddonClass.h" +#include "interfaces/legacy/AddonString.h" +#include "interfaces/legacy/Exception.h" +#include "interfaces/legacy/Tuple.h" +#include "settings/lib/SettingDefinitions.h" + +#include <memory> +#include <string> +#include <vector> + +class CSettingsBase; + +namespace XBMCAddon +{ +namespace xbmcaddon +{ + +XBMCCOMMONS_STANDARD_EXCEPTION(SettingCallbacksNotSupportedException); + +// +/// \defgroup python_settings Settings +/// \ingroup python_xbmcaddon +/// @{ +/// @brief **Add-on settings** +/// +/// \python_class{ Settings() } +/// +/// This wrapper provides access to the settings specific to an add-on. +/// It supports reading and writing specific setting values. +/// +///----------------------------------------------------------------------- +/// @python_v20 New class added. +/// +/// **Example:** +/// ~~~~~~~~~~~~~{.py} +/// ... +/// settings = xbmcaddon.Addon('id').getSettings() +/// ... +/// ~~~~~~~~~~~~~ +// +class Settings : public AddonClass +{ +public: +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + std::shared_ptr<CSettingsBase> settings; +#endif + +#ifndef SWIG + Settings(std::shared_ptr<CSettingsBase> settings); +#endif + + virtual ~Settings() = default; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getBool(id) } + /// Returns the value of a setting as a boolean. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return bool - Setting as a boolean + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// enabled = settings.getBool('enabled') + /// .. + /// ~~~~~~~~~~~~~ + /// + getBool(...); +#else + bool getBool(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getInt(id) } + /// Returns the value of a setting as an integer. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return integer - Setting as an integer + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// max = settings.getInt('max') + /// .. + /// ~~~~~~~~~~~~~ + /// + getInt(...); +#else + int getInt(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getNumber(id) } + /// Returns the value of a setting as a floating point number. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return float - Setting as a floating point number + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// max = settings.getNumber('max') + /// .. + /// ~~~~~~~~~~~~~ + /// + getNumber(...); +#else + double getNumber(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getString(id) } + /// Returns the value of a setting as a unicode string. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return string - Setting as a unicode string + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// apikey = settings.getString('apikey') + /// .. + /// ~~~~~~~~~~~~~ + /// + getString(...); +#else + String getString(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getBoolList(id) } + /// Returns the value of a setting as a list of booleans. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return list - Setting as a list of booleans + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// enabled = settings.getBoolList('enabled') + /// .. + /// ~~~~~~~~~~~~~ + /// + getBoolList(...); +#else + std::vector<bool> getBoolList(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getIntList(id) } + /// Returns the value of a setting as a list of integers. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return list - Setting as a list of integers + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// ids = settings.getIntList('ids') + /// .. + /// ~~~~~~~~~~~~~ + /// + getIntList(...); +#else + std::vector<int> getIntList(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getNumberList(id) } + /// Returns the value of a setting as a list of floating point numbers. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return list - Setting as a list of floating point numbers + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// max = settings.getNumberList('max') + /// .. + /// ~~~~~~~~~~~~~ + /// + getNumberList(...); +#else + std::vector<double> getNumberList(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ getStringList(id) } + /// Returns the value of a setting as a list of unicode strings. + /// + /// @param id string - id of the setting that the module needs to access. + /// @return list - Setting as a list of unicode strings + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// views = settings.getStringList('views') + /// .. + /// ~~~~~~~~~~~~~ + /// + getStringList(...); +#else + std::vector<String> getStringList(const char* id); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setBool(id, value) } + /// Sets the value of a setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value bool - value of the setting. + /// @return bool - True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setBool(id='enabled', value=True) + /// .. + /// ~~~~~~~~~~~~~ + /// + setBool(...); +#else + void setBool(const char* id, bool value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setInt(id, value) } + /// Sets the value of a setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value integer - value of the setting. + /// @return bool - True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setInt(id='max', value=5) + /// .. + /// ~~~~~~~~~~~~~ + /// + setInt(...); +#else + void setInt(const char* id, int value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setNumber(id, value) } + /// Sets the value of a setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value float - value of the setting. + /// @return bool - True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setNumber(id='max', value=5.5) + /// .. + /// ~~~~~~~~~~~~~ + /// + setNumber(...); +#else + void setNumber(const char* id, double value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setString(id, value) } + /// Sets the value of a setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param value string or unicode - value of the setting. + /// @return bool - True if the value of the setting was set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setString(id='username', value='teamkodi') + /// .. + /// ~~~~~~~~~~~~~ + /// + setString(...); +#else + void setString(const char* id, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setBoolList(id, values) } + /// Sets the boolean values of a list setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param values list of boolean - values of the setting. + /// @return bool - True if the values of the setting were set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setBoolList(id='enabled', values=[ True, False ]) + /// .. + /// ~~~~~~~~~~~~~ + /// + setBoolList(...); +#else + void setBoolList(const char* id, const std::vector<bool>& values); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setIntList(id, value) } + /// Sets the integer values of a list setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param values list of int - values of the setting. + /// @return bool - True if the values of the setting were set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setIntList(id='max', values=[ 5, 23 ]) + /// .. + /// ~~~~~~~~~~~~~ + /// + setIntList(...); +#else + void setIntList(const char* id, const std::vector<int>& values); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setNumberList(id, value) } + /// Sets the floating point values of a list setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param values list of float - values of the setting. + /// @return bool - True if the values of the setting were set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setNumberList(id='max', values=[ 5.5, 5.8 ]) + /// .. + /// ~~~~~~~~~~~~~ + /// + setNumberList(...); +#else + void setNumberList(const char* id, const std::vector<double>& values); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_settings + /// @brief \python_func{ setStringList(id, value) } + /// Sets the string values of a list setting. + /// + /// @param id string - id of the setting that the module needs to access. + /// @param values list of string or unicode - values of the setting. + /// @return bool - True if the values of the setting were set, false otherwise + /// + /// + /// @note You can use the above as keywords for arguments. + /// + /// + ///----------------------------------------------------------------------- + /// @python_v20 New function added. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// settings.setStringList(id='username', values=[ 'team', 'kodi' ]) + /// .. + /// ~~~~~~~~~~~~~ + /// + setStringList(...); +#else + void setStringList(const char* id, const std::vector<String>& values); +#endif +}; +//@} + +} // namespace xbmcaddon +} // namespace XBMCAddon diff --git a/xbmc/interfaces/legacy/Stat.h b/xbmc/interfaces/legacy/Stat.h new file mode 100644 index 0000000..90a3843 --- /dev/null +++ b/xbmc/interfaces/legacy/Stat.h @@ -0,0 +1,195 @@ +/* + * 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 "AddonClass.h" +#include "LanguageHook.h" +#include "filesystem/File.h" + +namespace XBMCAddon +{ + namespace xbmcvfs + { + // + /// \defgroup python_stat Stat + /// \ingroup python_xbmcvfs + /// @{ + /// @brief **Get file or file system status.** + /// + /// \python_class{ xbmcvfs.Stat(path) } + /// + /// These class return information about a file. Execute (search) permission + /// is required on all of the directories in path that lead to the file. + /// + /// @param path [string] file or folder + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v12 New function added + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// st = xbmcvfs.Stat(path) + /// modified = st.st_mtime() + /// .. + /// ~~~~~~~~~~~~~ + // + class Stat : public AddonClass + { + struct __stat64 st; + + public: + Stat(const String& path) + { + DelayedCallGuard dg; + XFILE::CFile::Stat(path, &st); + } + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_mode() } + /// To get file protection. + /// + /// @return st_mode + /// + st_mode(); +#else + inline long long st_mode() { return st.st_mode; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_ino() } + /// To get inode number. + /// + /// @return st_ino + /// + st_ino(); +#else + inline long long st_ino() { return st.st_ino; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_dev() } + /// To get ID of device containing file. + /// + /// The st_dev field describes the device on which this file resides. + /// + /// @return st_dev + /// + st_dev(); +#else + inline long long st_dev() { return st.st_dev; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_nlink() } + /// To get number of hard links. + /// + /// @return st_nlink + /// + st_nlink(); +#else + inline long long st_nlink() { return st.st_nlink; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_uid() } + /// To get user ID of owner. + /// + /// @return st_uid + /// + st_uid(); +#else + inline long long st_uid() { return st.st_uid; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_gid() } + /// To get group ID of owner. + /// + /// @return st_gid + /// + st_gid(); +#else + inline long long st_gid() { return st.st_gid; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_size() } + /// To get total size, in bytes. + /// + /// The st_size field gives the size of the file (if it is a regular file + /// or a symbolic link) in bytes. The size of a symbolic link (only on + /// Linux and Mac OS X) is the length of the pathname it contains, without + /// a terminating null byte. + /// + /// @return st_size + /// + st_size(); +#else + inline long long st_size() { return st.st_size; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_atime() } + /// To get time of last access. + /// + /// @return st_atime + /// + st_atime(); +#else + inline long long atime() { return st.st_atime; }; //names st_atime/st_mtime/st_ctime are used by sys/stat.h +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_mtime() } + /// To get time of last modification. + /// + /// @return st_mtime + /// + st_mtime(); +#else + inline long long mtime() { return st.st_mtime; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_stat + /// @brief \python_func{ st_ctime() } + /// To get time of last status change. + /// + /// @return st_ctime + /// + st_ctime(); +#else + inline long long ctime() { return st.st_ctime; } +#endif + }; + /// @} + } +} + diff --git a/xbmc/interfaces/legacy/String.cpp b/xbmc/interfaces/legacy/String.cpp new file mode 100644 index 0000000..cb172a2 --- /dev/null +++ b/xbmc/interfaces/legacy/String.cpp @@ -0,0 +1,14 @@ +/* + * 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 "AddonString.h" + +namespace XBMCAddon +{ + String emptyString; +} diff --git a/xbmc/interfaces/legacy/Tuple.h b/xbmc/interfaces/legacy/Tuple.h new file mode 100644 index 0000000..116f307 --- /dev/null +++ b/xbmc/interfaces/legacy/Tuple.h @@ -0,0 +1,98 @@ +/* + * 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 + +/** + * This file contains a few templates to define various length + * Tuples. + */ +namespace XBMCAddon +{ + struct tuple_null_type { }; + + class TupleBase + { + protected: + int numValuesSet; + explicit inline TupleBase(int pnumValuesSet) : numValuesSet(pnumValuesSet) {} + inline void nvs(int newSize) { if(numValuesSet < newSize) numValuesSet = newSize; } + public: + inline int GetNumValuesSet() const { return numValuesSet; } + }; + + // stub type template to be partial specialized + template<typename T1 = tuple_null_type, typename T2 = tuple_null_type, + typename T3 = tuple_null_type, typename T4 = tuple_null_type, + typename Extraneous = tuple_null_type> class Tuple {}; + + // Tuple that holds a single value + template<typename T1> class Tuple<T1, tuple_null_type, tuple_null_type, tuple_null_type, tuple_null_type> : public TupleBase + { + private: + T1 v1; + public: + explicit inline Tuple(T1 p1) : TupleBase(1), v1(p1) {} + inline Tuple() : TupleBase(0) {} + inline Tuple(const Tuple<T1>& o) : TupleBase(o), v1(o.v1) {} + Tuple<T1>& operator=(const Tuple<T1>& other) = default; + + inline T1& first() { TupleBase::nvs(1); return v1; } + inline const T1& first() const { return v1; } + }; + + // Tuple that holds two values + template<typename T1, typename T2> class Tuple<T1, T2, tuple_null_type, tuple_null_type, tuple_null_type> : public Tuple<T1> + { + protected: + T2 v2; + + public: + inline Tuple(T1 p1, T2 p2) : Tuple<T1>(p1), v2(p2) { TupleBase::nvs(2); } + explicit inline Tuple(T1 p1) : Tuple<T1>(p1) {} + inline Tuple() = default; + Tuple<T1, T2>& operator=(const Tuple<T1, T2>& other) = default; + inline Tuple(const Tuple<T1,T2>& o) : Tuple<T1>(o), v2(o.v2) {} + + inline T2& second() { TupleBase::nvs(2); return v2; } + inline const T2& second() const { return v2; } + }; + + // Tuple that holds three values + template<typename T1, typename T2, typename T3> class Tuple<T1, T2, T3, tuple_null_type, tuple_null_type> : public Tuple<T1,T2> + { + private: + T3 v3; + public: + inline Tuple(T1 p1, T2 p2, T3 p3) : Tuple<T1,T2>(p1,p2), v3(p3) { TupleBase::nvs(3); } + inline Tuple(T1 p1, T2 p2) : Tuple<T1,T2>(p1,p2) {} + explicit inline Tuple(T1 p1) : Tuple<T1,T2>(p1) {} + inline Tuple() = default; + inline Tuple(const Tuple<T1,T2,T3>& o) : Tuple<T1,T2>(o), v3(o.v3) {} + + inline T3& third() { TupleBase::nvs(3); return v3; } + inline const T3& third() const { return v3; } + }; + + // Tuple that holds four values + template<typename T1, typename T2, typename T3, typename T4> class Tuple<T1, T2, T3, T4, tuple_null_type> : public Tuple<T1,T2,T3> + { + private: + T4 v4; + public: + inline Tuple(T1 p1, T2 p2, T3 p3, T4 p4) : Tuple<T1,T2,T3>(p1,p2,p3), v4(p4) { TupleBase::nvs(4); } + inline Tuple(T1 p1, T2 p2, T3 p3) : Tuple<T1,T2,T3>(p1,p2,p3) {} + inline Tuple(T1 p1, T2 p2) : Tuple<T1,T2,T3>(p1,p2) {} + explicit inline Tuple(T1 p1) : Tuple<T1,T2,T3>(p1) {} + inline Tuple() = default; + inline Tuple(const Tuple<T1,T2,T3,T4>& o) : Tuple<T1,T2,T3>(o), v4(o.v4) {} + + inline T4& fourth() { TupleBase::nvs(4); return v4; } + inline const T4& fourth() const { return v4; } + }; +} diff --git a/xbmc/interfaces/legacy/Window.cpp b/xbmc/interfaces/legacy/Window.cpp new file mode 100644 index 0000000..100803e --- /dev/null +++ b/xbmc/interfaces/legacy/Window.cpp @@ -0,0 +1,767 @@ +/* + * 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 "Window.h" + +#include "ServiceBroker.h" +#include "WindowException.h" +#include "WindowInterceptor.h" +#include "application/Application.h" +#include "guilib/GUIButtonControl.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIEditControl.h" +#include "guilib/GUIRadioButtonControl.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" +#include "utils/StringUtils.h" +#include "utils/Variant.h" + +#define ACTIVE_WINDOW CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() + +namespace XBMCAddon +{ + namespace xbmcgui + { + thread_local ref* InterceptorBase::upcallTls; + + /** + * Used in add/remove control. It only locks if it's given a + * non-NULL CCriticalSection. It's given a NULL CCriticalSection + * when a function higher in the call stack already has a + */ + class MaybeLock + { + CCriticalSection* lock; + public: + inline explicit MaybeLock(CCriticalSection* p_lock) : lock(p_lock) { if (lock) lock->lock(); } + inline ~MaybeLock() { if (lock) lock->unlock(); } + }; + + class SingleLockWithDelayGuard + { + DelayedCallGuard dcg; + CCriticalSection& lock; + public: + inline SingleLockWithDelayGuard(CCriticalSection& ccrit, LanguageHook* lh) : dcg(lh), lock(ccrit) { lock.lock(); } + inline ~SingleLockWithDelayGuard() { lock.unlock(); } + }; + + /** + * Explicit template instantiation + */ + template class Interceptor<CGUIWindow>; + + /** + * This interceptor is a simple, non-callbackable (is that a word?) + * Interceptor to satisfy the Window requirements for upcalling + * for the purposes of instantiating a Window instance from + * an already existing window. + */ + class ProxyExistingWindowInterceptor : public InterceptorBase + { + CGUIWindow* cguiwindow; + + public: + inline ProxyExistingWindowInterceptor(CGUIWindow* window) : + cguiwindow(window) { XBMC_TRACE; } + + CGUIWindow* get() override; + }; + + CGUIWindow* ProxyExistingWindowInterceptor::get() { XBMC_TRACE; return cguiwindow; } + + Window::Window(bool discrim): + window(NULL), + m_actionEvent(true), + canPulse(true), existingWindow(false) + { + XBMC_TRACE; + } + + /** + * This just creates a default window. + */ + Window::Window(int existingWindowId) : + window(NULL), + m_actionEvent(true) + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + + if (existingWindowId == -1) + { + // in this case just do the other constructor. + canPulse = true; + existingWindow = false; + + setWindow(new Interceptor<CGUIWindow>("CGUIWindow",this,getNextAvailableWindowId())); + } + else + { + // user specified window id, use this one if it exists + // It is not possible to capture key presses or button presses + CGUIWindow* pWindow = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(existingWindowId); + if (!pWindow) + throw WindowException("Window id does not exist"); + + setWindow(new ProxyExistingWindowInterceptor(pWindow)); + } + } + + Window::~Window() + { + XBMC_TRACE; + + deallocating(); + } + + void Window::deallocating() + { + AddonCallback::deallocating(); + + dispose(); + } + + void Window::dispose() + { + XBMC_TRACE; + + //! @todo rework locking + // Python GIL and CServiceBroker::GetWinSystem()->GetGfxContext() are deadlock happy + // dispose is called from GUIWindowManager and in this case DelayGuard must not be used. + if (!CServiceBroker::GetAppMessenger()->IsProcessThread()) + { + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(), languageHook); + } + + if (!isDisposed) + { + isDisposed = true; + + // no callbacks are possible any longer + // - this will be handled by the parent constructor + + // first change to an existing window + if (!existingWindow) + { + if (ACTIVE_WINDOW == iWindowId && !g_application.m_bStop) + { + if(CServiceBroker::GetGUI()->GetWindowManager().GetWindow(iOldWindowId)) + { + CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(iOldWindowId); + } + // old window does not exist anymore, switch to home + else CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME); + } + + } + else + { + //! @bug + //! This is an existing window, so no resources are free'd. Note that + //! THIS WILL FAIL for any controls newly created by python - they will + //! remain after the script ends. Ideally this would be remedied by + //! a flag in Control that specifies that it was python created - any python + //! created controls could then be removed + free'd from the window. + //! how this works with controlgroups though could be a bit tricky. + } + + // and free our list of controls + std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin(); + while (it != vecControls.end()) + { + AddonClass::Ref<Control> pControl = *it; + // initialize control to zero + pControl->pGUIControl = NULL; + pControl->iControlId = 0; + pControl->iParentId = 0; + ++it; + } + + if (!existingWindow) + { + if (window) + { + if (CServiceBroker::GetGUI()->GetWindowManager().IsWindowVisible(ref(window)->GetID())) + { + destroyAfterDeInit = true; + close(); + } + else + CServiceBroker::GetGUI()->GetWindowManager().Delete(ref(window)->GetID()); + } + } + + vecControls.clear(); + } + } + + void Window::setWindow(InterceptorBase* _window) + { + XBMC_TRACE; + window = _window; + iWindowId = _window->get()->GetID(); + + if (!existingWindow) + CServiceBroker::GetGUI()->GetWindowManager().Add(window->get()); + } + + int Window::getNextAvailableWindowId() + { + XBMC_TRACE; + // window id's 13000 - 13100 are reserved for python + // get first window id that is not in use + int id = WINDOW_PYTHON_START; + // if window 13099 is in use it means python can't create more windows + if (CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_PYTHON_END)) + throw WindowException("maximum number of windows reached"); + + while(id < WINDOW_PYTHON_END && CServiceBroker::GetGUI()->GetWindowManager().GetWindow(id) != NULL) id++; + return id; + } + + void Window::popActiveWindowId() + { + XBMC_TRACE; + if (iOldWindowId != iWindowId && + iWindowId != ACTIVE_WINDOW) + iOldWindowId = ACTIVE_WINDOW; + } + + // Internal helper method + /* Searches for a control in Window->vecControls + * If we can't find any but the window has the controlId (in case of a not python window) + * we create a new control with basic functionality + */ + Control* Window::GetControlById(int iControlId, CCriticalSection* gc) + { + XBMC_TRACE; + + // find in window vector first!!! + // this saves us from creating a complete new control + std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin(); + while (it != vecControls.end()) + { + AddonClass::Ref<Control> control = (*it); + if (control->iControlId == iControlId) + { + return control.get(); + } else ++it; + } + + // lock xbmc GUI before accessing data from it + MaybeLock lock(gc); + + // check if control exists + CGUIControl* pGUIControl = ref(window)->GetControl(iControlId); + if (!pGUIControl) + { + // control does not exist. + throw WindowException("Non-Existent Control %d",iControlId); + } + + // allocate a new control with a new reference + CLabelInfo li; + + Control* pControl = NULL; + + //! @todo Yuck! Should probably be done with a Factory pattern + switch(pGUIControl->GetControlType()) + { + case CGUIControl::GUICONTROL_BUTTON: + pControl = new ControlButton(); + + li = ((CGUIButtonControl *)pGUIControl)->GetLabelInfo(); + + // note: conversion from infocolors -> plain colors here + ((ControlButton*)pControl)->disabledColor = li.disabledColor; + ((ControlButton*)pControl)->focusedColor = li.focusedColor; + ((ControlButton*)pControl)->textColor = li.textColor; + ((ControlButton*)pControl)->shadowColor = li.shadowColor; + if (li.font) ((ControlButton*)pControl)->strFont = li.font->GetFontName(); + ((ControlButton*)pControl)->align = li.align; + break; + case CGUIControl::GUICONTROL_LABEL: + pControl = new ControlLabel(); + break; + case CGUIControl::GUICONTROL_SPIN: + pControl = new ControlSpin(); + break; + case CGUIControl::GUICONTROL_FADELABEL: + pControl = new ControlFadeLabel(); + break; + case CGUIControl::GUICONTROL_TEXTBOX: + pControl = new ControlTextBox(); + break; + case CGUIControl::GUICONTROL_IMAGE: + case CGUIControl::GUICONTROL_BORDEREDIMAGE: + pControl = new ControlImage(); + break; + case CGUIControl::GUICONTROL_PROGRESS: + pControl = new ControlProgress(); + break; + case CGUIControl::GUICONTROL_SLIDER: + pControl = new ControlSlider(); + break; + case CGUIControl::GUICONTAINER_LIST: + case CGUIControl::GUICONTAINER_WRAPLIST: + case CGUIControl::GUICONTAINER_FIXEDLIST: + case CGUIControl::GUICONTAINER_PANEL: + pControl = new ControlList(); + // create a python spin control + ((ControlList*)pControl)->pControlSpin = new ControlSpin(); + break; + case CGUIControl::GUICONTROL_GROUP: + pControl = new ControlGroup(); + break; + case CGUIControl::GUICONTROL_RADIO: + pControl = new ControlRadioButton(); + + li = ((CGUIRadioButtonControl *)pGUIControl)->GetLabelInfo(); + + // note: conversion from infocolors -> plain colors here + ((ControlRadioButton*)pControl)->disabledColor = li.disabledColor; + ((ControlRadioButton*)pControl)->focusedColor = li.focusedColor; + ((ControlRadioButton*)pControl)->textColor = li.textColor; + ((ControlRadioButton*)pControl)->shadowColor = li.shadowColor; + if (li.font) ((ControlRadioButton*)pControl)->strFont = li.font->GetFontName(); + ((ControlRadioButton*)pControl)->align = li.align; + break; + case CGUIControl::GUICONTROL_EDIT: + pControl = new ControlEdit(); + + li = ((CGUIEditControl *)pGUIControl)->GetLabelInfo(); + + // note: conversion from infocolors -> plain colors here + ((ControlEdit*)pControl)->disabledColor = li.disabledColor; + ((ControlEdit*)pControl)->textColor = li.textColor; + if (li.font) ((ControlEdit*)pControl)->strFont = li.font->GetFontName(); + ((ControlButton*)pControl)->align = li.align; + break; + default: + break; + } + + if (!pControl) + // throw an exception + throw WindowException("Unknown control type for python"); + + // we have a valid control here, fill in all the 'Control' data + pControl->pGUIControl = pGUIControl; + pControl->iControlId = pGUIControl->GetID(); + pControl->iParentId = iWindowId; + pControl->dwHeight = (int)pGUIControl->GetHeight(); + pControl->dwWidth = (int)pGUIControl->GetWidth(); + pControl->dwPosX = (int)pGUIControl->GetXPosition(); + pControl->dwPosY = (int)pGUIControl->GetYPosition(); + pControl->iControlUp = pGUIControl->GetAction(ACTION_MOVE_UP).GetNavigation(); + pControl->iControlDown = pGUIControl->GetAction(ACTION_MOVE_DOWN).GetNavigation(); + pControl->iControlLeft = pGUIControl->GetAction(ACTION_MOVE_LEFT).GetNavigation(); + pControl->iControlRight = pGUIControl->GetAction(ACTION_MOVE_RIGHT).GetNavigation(); + + // It got this far so means the control isn't actually in the vector of controls + // so lets add it to save doing all that next time + vecControls.emplace_back(pControl); + + // return the control with increased reference (+1) + return pControl; + } + + void Window::PulseActionEvent() + { + XBMC_TRACE; + if (canPulse) + m_actionEvent.Set(); + } + + bool Window::WaitForActionEvent(unsigned int milliseconds) + { + XBMC_TRACE; + // DO NOT MAKE THIS A DELAYED CALL!!!! + bool ret = languageHook == NULL ? m_actionEvent.Wait(std::chrono::milliseconds(milliseconds)) + : languageHook->WaitForEvent(m_actionEvent, milliseconds); + if (ret) + m_actionEvent.Reset(); + return ret; + } + + bool Window::OnAction(const CAction &action) + { + XBMC_TRACE; + // do the base class window first, and the call to python after this + bool ret = ref(window)->OnAction(action); + + // workaround - for scripts which try to access the active control (focused) when there is none. + // for example - the case when the mouse enters the screen. + CGUIControl *pControl = ref(window)->GetFocusedControl(); + if (action.IsMouse() && !pControl) + return ret; + + AddonClass::Ref<Action> inf(new Action(action)); + invokeCallback(new CallbackFunction<Window,AddonClass::Ref<Action> >(this,&Window::onAction,inf.get())); + PulseActionEvent(); + + return ret; + } + + bool Window::OnBack(int actionID) + { + // we are always a Python window ... keep that in mind when reviewing the old code + return true; + } + + void Window::OnDeinitWindow(int nextWindowID /*= 0*/) + { + // NOTE!: This handle child classes correctly. XML windows will call + // the OnDeinitWindow from CGUIMediaWindow while non-XML classes will + // call the OnDeinitWindow on CGUIWindow + ref(window)->OnDeinitWindow(nextWindowID); + if (destroyAfterDeInit) + CServiceBroker::GetGUI()->GetWindowManager().Delete(window->get()->GetID()); + } + + void Window::onAction(Action* action) + { + XBMC_TRACE; + // default onAction behavior + if(action->id == ACTION_PREVIOUS_MENU || action->id == ACTION_NAV_BACK) + close(); + } + + bool Window::OnMessage(CGUIMessage& message) + { + XBMC_TRACE; + switch (message.GetMessage()) + { + case GUI_MSG_CLICKED: + { + int iControl=message.GetSenderId(); + AddonClass::Ref<Control> inf; + // find python control object with same iControl + std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin(); + while (it != vecControls.end()) + { + AddonClass::Ref<Control> pControl = (*it); + if (pControl->iControlId == iControl) + { + inf = pControl.get(); + break; + } + ++it; + } + + // did we find our control? + if (inf.isNotNull()) + { + // currently we only accept messages from a button or controllist with a select action + if (inf->canAcceptMessages(message.GetParam1())) + { + invokeCallback(new CallbackFunction<Window,AddonClass::Ref<Control> >(this,&Window::onControl,inf.get())); + PulseActionEvent(); + + // return true here as we are handling the event + return true; + } + } + // if we get here, we didn't add the action + } + break; + } + + return ref(window)->OnMessage(message); + } + + void Window::onControl(Control* action) { XBMC_TRACE; /* do nothing by default */ } + void Window::onClick(int controlId) { XBMC_TRACE; /* do nothing by default */ } + void Window::onDoubleClick(int controlId) { XBMC_TRACE; /* do nothing by default */ } + void Window::onFocus(int controlId) { XBMC_TRACE; /* do nothing by default */ } + void Window::onInit() { XBMC_TRACE; /* do nothing by default */ } + + void Window::show() + { + XBMC_TRACE; + DelayedCallGuard dcguard(languageHook); + popActiveWindowId(); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTIVATE_WINDOW, iWindowId, 0); + } + + void Window::setFocus(Control* pControl) + { + XBMC_TRACE; + if(pControl == NULL) + throw WindowException("Object should be of type Control"); + + CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS,pControl->iParentId, pControl->iControlId); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, pControl->iParentId); + } + + void Window::setFocusId(int iControlId) + { + XBMC_TRACE; + CGUIMessage msg = CGUIMessage(GUI_MSG_SETFOCUS,iWindowId,iControlId); + CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, iWindowId); + } + + Control* Window::getFocus() + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + + int iControlId = ref(window)->GetFocusedControlID(); + if(iControlId == -1) + throw WindowException("No control in this window has focus"); + // Sine I'm already holding the lock theres no reason to give it to GetFocusedControlID + return GetControlById(iControlId,NULL); + } + + long Window::getFocusId() + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + int iControlId = ref(window)->GetFocusedControlID(); + if(iControlId == -1) + throw WindowException("No control in this window has focus"); + return (long)iControlId; + } + + void Window::removeControl(Control* pControl) + { + XBMC_TRACE; + DelayedCallGuard dg(languageHook); + doRemoveControl(pControl,&CServiceBroker::GetWinSystem()->GetGfxContext(),true); + } + + void Window::doRemoveControl(Control* pControl, CCriticalSection* gcontext, bool wait) + { + XBMC_TRACE; + // type checking, object should be of type Control + if(pControl == NULL) + throw WindowException("Object should be of type Control"); + + { + MaybeLock mlock(gcontext); + if(!ref(window)->GetControl(pControl->iControlId)) + throw WindowException("Control does not exist in window"); + } + + // delete control from vecControls in window object + std::vector<AddonClass::Ref<Control> >::iterator it = vecControls.begin(); + while (it != vecControls.end()) + { + AddonClass::Ref<Control> control = (*it); + if (control->iControlId == pControl->iControlId) + { + it = vecControls.erase(it); + } else ++it; + } + + CGUIMessage msg(GUI_MSG_REMOVE_CONTROL, 0, 0); + msg.SetPointer(pControl->pGUIControl); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, iWindowId, wait); + + // initialize control to zero + pControl->pGUIControl = NULL; + pControl->iControlId = 0; + pControl->iParentId = 0; + } + + void Window::removeControls(std::vector<Control*> pControls) + { + XBMC_TRACE; + DelayedCallGuard dg(languageHook); + int count = 1; int size = pControls.size(); + for (std::vector<Control*>::iterator iter = pControls.begin(); iter != pControls.end(); count++, ++iter) + doRemoveControl(*iter,NULL, count == size); + } + + long Window::getHeight() + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(), languageHook); + RESOLUTION_INFO resInfo = ref(window)->GetCoordsRes(); + return resInfo.iHeight; + } + + long Window::getWidth() + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(), languageHook); + RESOLUTION_INFO resInfo = ref(window)->GetCoordsRes(); + return resInfo.iWidth; + } + + void Window::setProperty(const char* key, const String& value) + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + std::string lowerKey = key; + StringUtils::ToLower(lowerKey); + + ref(window)->SetProperty(lowerKey, value); + } + + String Window::getProperty(const char* key) + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + std::string lowerKey = key; + StringUtils::ToLower(lowerKey); + std::string value = ref(window)->GetProperty(lowerKey).asString(); + return value; + } + + void Window::clearProperty(const char* key) + { + XBMC_TRACE; + if (!key) return; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + + std::string lowerKey = key; + StringUtils::ToLower(lowerKey); + ref(window)->SetProperty(lowerKey, ""); + } + + void Window::clearProperties() + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + ref(window)->ClearProperties(); + } + + void Window::close() + { + XBMC_TRACE; + bModal = false; + + if (!existingWindow) + PulseActionEvent(); + + { + DelayedCallGuard dcguard(languageHook); + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_PREVIOUS_WINDOW, iOldWindowId, 0); + } + + iOldWindowId = 0; + } + + void Window::doModal() + { + XBMC_TRACE; + if (!existingWindow) + { + bModal = true; + + if(iWindowId != ACTIVE_WINDOW) + show(); + + while (bModal && !g_application.m_bStop) + { +//! @todo garbear added this code to the python window.cpp class and +//! commented in XBPyThread.cpp. I'm not sure how to handle this +//! in this native implementation. +// // Check if XBPyThread::stop() raised a SystemExit exception +// if (PyThreadState_Get()->async_exc == PyExc_SystemExit) +// { +// CLog::Log(LOGDEBUG, "PYTHON: doModal() encountered a SystemExit exception, closing window and returning"); +// Window_Close(self, NULL); +// break; +// } + languageHook->MakePendingCalls(); // MakePendingCalls + + bool stillWaiting; + do + { + { + DelayedCallGuard dcguard(languageHook); + stillWaiting = WaitForActionEvent(100) ? false : true; + } + languageHook->MakePendingCalls(); + } while (stillWaiting); + } + } + } + + void Window::addControl(Control* pControl) + { + XBMC_TRACE; + DelayedCallGuard dg(languageHook); + doAddControl(pControl,&CServiceBroker::GetWinSystem()->GetGfxContext(),true); + } + + void Window::doAddControl(Control* pControl, CCriticalSection* gcontext, bool wait) + { + XBMC_TRACE; + if(pControl == NULL) + throw WindowException("NULL Control passed to WindowBase::addControl"); + + if(pControl->iControlId != 0) + throw WindowException("Control is already used"); + + // lock kodi GUI before accessing data from it + pControl->iParentId = iWindowId; + + { + MaybeLock mlock(gcontext); + // assign control id, if id is already in use, try next id + do pControl->iControlId = ++iCurrentControlId; + while (ref(window)->GetControl(pControl->iControlId)); + } + + pControl->Create(); + + // set default navigation for control + pControl->iControlUp = pControl->iControlId; + pControl->iControlDown = pControl->iControlId; + pControl->iControlLeft = pControl->iControlId; + pControl->iControlRight = pControl->iControlId; + + pControl->pGUIControl->SetAction(ACTION_MOVE_UP, CGUIAction(pControl->iControlUp)); + pControl->pGUIControl->SetAction(ACTION_MOVE_DOWN, CGUIAction(pControl->iControlDown)); + pControl->pGUIControl->SetAction(ACTION_MOVE_LEFT, CGUIAction(pControl->iControlLeft)); + pControl->pGUIControl->SetAction(ACTION_MOVE_RIGHT, CGUIAction(pControl->iControlRight)); + + // add control to list and allocate resources for the control + vecControls.emplace_back(pControl); + pControl->pGUIControl->AllocResources(); + + // This calls the CGUIWindow parent class to do the final add + CGUIMessage msg(GUI_MSG_ADD_CONTROL, 0, 0); + msg.SetPointer(pControl->pGUIControl); + CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, iWindowId, wait); + } + + void Window::addControls(std::vector<Control*> pControls) + { + XBMC_TRACE; + SingleLockWithDelayGuard gslock(CServiceBroker::GetWinSystem()->GetGfxContext(),languageHook); + int count = 1; int size = pControls.size(); + for (std::vector<Control*>::iterator iter = pControls.begin(); iter != pControls.end(); count++, ++iter) + doAddControl(*iter,NULL, count == size); + } + + Control* Window::getControl(int iControlId) + { + XBMC_TRACE; + DelayedCallGuard dg(languageHook); + return GetControlById(iControlId,&CServiceBroker::GetWinSystem()->GetGfxContext()); + } + + void Action::setFromCAction(const CAction& action) + { + XBMC_TRACE; + id = action.GetID(); + buttonCode = action.GetButtonCode(); + fAmount1 = action.GetAmount(0); + fAmount2 = action.GetAmount(1); + fRepeat = action.GetRepeat(); + strAction = action.GetName(); + } + + } +} diff --git a/xbmc/interfaces/legacy/Window.h b/xbmc/interfaces/legacy/Window.h new file mode 100644 index 0000000..b30be7b --- /dev/null +++ b/xbmc/interfaces/legacy/Window.h @@ -0,0 +1,878 @@ +/* + * 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 "AddonCallback.h" +#include "AddonString.h" +#include "Control.h" +#include "swighelper.h" + +#include <limits.h> +#include <mutex> +#include <vector> + +namespace XBMCAddon +{ + namespace xbmcgui + { + // Forward declare the interceptor as the AddonWindowInterceptor.h + // file needs to include the Window class because of the template + class InterceptorBase; + + // + /// \defgroup python_xbmcgui_action Action + /// \ingroup python_xbmcgui + ///@{ + /// @brief **Action class.** + /// + /// \python_class{ xbmcgui.Action(): } + /// + /// This class serves in addition to identify carried out + /// \ref kodi_key_action_ids of Kodi and to be able to carry out thereby own + /// necessary steps. + /// + /// The data of this class are always transmitted by callback + /// Window::onAction at a window. + /// + class Action : public AddonClass + { + public: + Action() = default; + +#ifndef SWIG + explicit Action(const CAction& caction) { setFromCAction(caction); } + + void setFromCAction(const CAction& caction); + + long id = -1; + float fAmount1 = 0.0f; + float fAmount2 = 0.0f; + float fRepeat = 0.0f; + unsigned long buttonCode = 0; + std::string strAction; + + // Not sure if this is correct but it's here for now. + AddonClass::Ref<Control> control; // previously pObject +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_action + /// @brief \python_func{ getId() } + /// To get \ref kodi_key_action_ids + /// + /// This function returns the identification code used by the explained + /// order, it is necessary to determine the type of command from + /// \ref kodi_key_action_ids. + /// + /// @return The action's current id as a long or 0 if + /// no action is mapped in the xml's. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// def onAction(self, action): + /// if action.getId() == ACTION_PREVIOUS_MENU: + /// print('action received: previous') + /// .. + /// ~~~~~~~~~~~~~ + /// + getId(); +#else + long getId() { XBMC_TRACE; return id; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_action + /// @brief \python_func{ getButtonCode() } + /// Returns the button code for this action. + /// + /// @return [integer] button code + /// + getButtonCode(); +#else + long getButtonCode() { XBMC_TRACE; return buttonCode; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_action + /// @brief \python_func{ getAmount1() } + /// Returns the first amount of force applied to the thumbstick. + /// + /// @return [float] first amount + /// + getAmount1(); +#else + float getAmount1() { XBMC_TRACE; return fAmount1; } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_action + /// @brief \python_func{ getAmount2() } + /// Returns the second amount of force applied to the thumbstick. + /// + /// @return [float] second amount + /// + getAmount2(); +#else + float getAmount2() { XBMC_TRACE; return fAmount2; } +#endif + }; + ///@} + + //========================================================================== + // This is the main class for the xbmcgui.Window functionality. It is tied + // into the main Kodi windowing system via the Interceptor + //========================================================================== + // + /// \defgroup python_xbmcgui_window Window + /// \ingroup python_xbmcgui + /// @{ + /// @brief __GUI window class for Add-Ons.__ + /// + /// This class allows over their functions to create and edit windows that + /// can be accessed from an Add-On. + /// + /// Likewise, all functions from here as well in the other window classes + /// \ref python_xbmcgui_window_dialog "WindowDialog", + /// \ref python_xbmcgui_window_xml "WindowXML" and + /// \ref python_xbmcgui_window_dialog_xml "WindowXMLDialog" + /// with inserted and available. + /// + /// + ///-------------------------------------------------------------------------- + /// Constructor for window + /// ---------------------------- + /// + /// \python_class{ xbmcgui.Window([existingWindowId]): } + /// + /// Creates a new from Add-On usable window class. This is to create + /// window for related controls by system calls. + /// + /// @param existingWindowId [opt] Specify an id to use an existing + /// window. + /// @throws ValueError if supplied window Id does not exist. + /// @throws Exception if more then 200 windows are created. + /// + /// Deleting this window will activate the old window that was active + /// and resets (not delete) all controls that are associated with this + /// window. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.Window() + /// width = win.getWidth() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// + // + class Window : public AddonCallback + { + friend class WindowDialogMixin; + bool isDisposed = false; + + void doAddControl(Control* pControl, CCriticalSection* gcontext, bool wait); + void doRemoveControl(Control* pControl, CCriticalSection* gcontext, bool wait); + + protected: +#ifndef SWIG + InterceptorBase* window; + int iWindowId = -1; + + std::vector<AddonClass::Ref<Control> > vecControls; + int iOldWindowId = 0; + int iCurrentControlId = 3000; + bool bModal = false; + CEvent m_actionEvent; + + bool canPulse = false; + + // I REALLY hate this ... but it's the simplest fix right now. + bool existingWindow = true; + bool destroyAfterDeInit = false; + + /** + * This only takes a boolean to allow subclasses to explicitly use it. A + * default constructor can be used as a concrete class and we need to tell + * the difference. + * subclasses should use this constructor and not the other. + */ + explicit Window(bool discrim); + + void deallocating() override; + + /** + * This helper retrieves the next available id. It is assumed that the + * global lock is already being held. + */ + static int getNextAvailableWindowId(); + + /** + * Child classes MUST call this in their constructors. It should be an + * instance of Interceptor<P extends CGUIWindow>. Control of memory + * management for this class is then given to the Window. + */ + void setWindow(InterceptorBase* _window); + + /** + * This is a helper method since popping the previous window id is a common + * function. + */ + void popActiveWindowId(); + + /** + * This is a helper method since getting a control by it's id is a common + * function. + */ + Control* GetControlById(int iControlId, CCriticalSection* gc); + + SWIGHIDDENVIRTUAL void PulseActionEvent(); + SWIGHIDDENVIRTUAL bool WaitForActionEvent(unsigned int milliseconds); +#endif + + public: + explicit Window(int existingWindowId = -1); + + ~Window() override; + +#ifndef SWIG + SWIGHIDDENVIRTUAL bool OnMessage(CGUIMessage& message); + SWIGHIDDENVIRTUAL bool OnAction(const CAction &action); + SWIGHIDDENVIRTUAL bool OnBack(int actionId); + SWIGHIDDENVIRTUAL void OnDeinitWindow(int nextWindowID); + + SWIGHIDDENVIRTUAL bool IsDialogRunning() const + { + XBMC_TRACE; + return false; + } + SWIGHIDDENVIRTUAL bool IsDialog() const + { + XBMC_TRACE; + return false; + } + SWIGHIDDENVIRTUAL bool IsModalDialog() const + { + XBMC_TRACE; + return false; + } + SWIGHIDDENVIRTUAL bool IsMediaWindow() const + { + XBMC_TRACE; + return false; + } + SWIGHIDDENVIRTUAL void dispose(); + + /** + * This is called from the InterceptorBase destructor to prevent further + * use of the interceptor from the window. + */ + inline void interceptorClear() + { + std::unique_lock<CCriticalSection> lock(*this); + window = NULL; + } +#endif + + // + /// @defgroup python_xbmcgui_window_cb Callback functions from Kodi to add-on + /// \ingroup python_xbmcgui_window + /// @{ + /// @brief __GUI window callback functions.__ + /// + /// Functions to handle control callbacks from Kodi. + /// + /// Likewise, all functions from here as well in the all window classes + /// (\ref python_xbmcgui_window "Window", + /// \ref python_xbmcgui_window_dialog "WindowDialog", + /// \ref python_xbmcgui_window_xml "WindowXML" and + /// \ref python_xbmcgui_window_dialog_xml "WindowXMLDialog") with inserted + /// and available. + /// + /// ------------------------------------------------------------------------ + /// + /// @link python_xbmcgui_window Go back to normal functions from window@endlink + // + + // callback takes a parameter +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onAction(self, Action action) } + /// **onAction method.** + /// + /// This method will receive all actions that the main program will send + /// to this window. + /// + /// @param self Own base class pointer + /// @param action The action id to perform, see + /// \ref python_xbmcgui_action to get use + /// of them + /// + /// @note + /// - By default, only the `PREVIOUS_MENU` and `NAV_BACK actions` are + /// handled. + /// - Overwrite this method to let your script handle all actions. + /// - Don't forget to capture `ACTION_PREVIOUS_MENU` or `ACTION_NAV_BACK`, + /// else the user can't close this window. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onAction(self, action): + /// if action.getId() == ACTION_PREVIOUS_MENU: + /// print('action received: previous') + /// self.close() + /// if action.getId() == ACTION_SHOW_INFO: + /// print('action received: show info') + /// if action.getId() == ACTION_STOP: + /// print('action received: stop') + /// if action.getId() == ACTION_PAUSE: + /// print('action received: pause') + /// .. + /// ~~~~~~~~~~~~~ + /// + onAction(...); +#else + virtual void onAction(Action* action); +#endif + + // on control is not actually on Window in the api but is called into Python anyway. +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onControl(self, Control) } + /// **onControl method.** + /// + /// This method will receive all click events on owned and selected + /// controls when the control itself doesn't handle the message. + /// + /// @param self Own base class pointer + /// @param control The \ref python_xbmcgui_control "Control" class + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onControl(self, control): + /// print("Window.onControl(control=[%s])"%control) + /// .. + /// ~~~~~~~~~~~~~ + /// + void onControl(...); +#else + virtual void onControl(Control* control); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onClick(self, int controlId) } + /// **onClick method.** + /// + /// This method will receive all click events that the main program will + /// send to this window. + /// + /// @param self Own base class pointer + /// @param controlId The one time clicked GUI control + /// identifier + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onClick(self,controlId): + /// if controlId == 10: + /// print("The control with Id 10 is clicked") + /// .. + /// ~~~~~~~~~~~~~ + /// + onClick(...); +#else + virtual void onClick(int controlId); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onDoubleClick(self, int controlId) } + /// __onDoubleClick method.__ + /// + /// This method will receive all double click events that the main program + /// will send to this window. + /// + /// @param self Own base class pointer + /// @param controlId The double clicked GUI control + /// identifier + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onDoubleClick(self,controlId): + /// if controlId == 10: + /// print("The control with Id 10 is double clicked") + /// .. + /// ~~~~~~~~~~~~~ + /// + onDoubleClick(...); +#else + virtual void onDoubleClick(int controlId); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onFocus(self, int controlId) } + /// __onFocus method.__ + /// + /// This method will receive all focus events that the main program will + /// send to this window. + /// + /// @param self Own base class pointer + /// @param controlId The focused GUI control identifier + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onDoubleClick(self,controlId): + /// if controlId == 10: + /// print("The control with Id 10 is focused") + /// .. + /// ~~~~~~~~~~~~~ + /// + onFocus(...); +#else + virtual void onFocus(int controlId); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_cb + /// @brief \python_func{ onInit(self) } + /// __onInit method.__ + /// + /// This method will be called to initialize the window + /// + /// @param self Own base class pointer + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// # Define own function where becomes called from Kodi + /// def onInit(self): + /// print("Window.onInit method called from Kodi") + /// .. + /// ~~~~~~~~~~~~~ + /// + onInit(...); +#else + virtual void onInit(); +#endif + //@} + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ show() } + /// Show this window. + /// + /// Shows this window by activating it, calling close() after it wil + /// activate the current window again. + /// + /// @note If your script ends this window will be closed to. To show it + /// forever, make a loop at the end of your script or use doModal() + /// instead. + /// + show(); +#else + SWIGHIDDENVIRTUAL void show(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ setFocus(Control) } + /// Give the supplied control focus. + /// + /// @param Control \ref python_xbmcgui_control "Control" class to focus + /// @throws TypeError If supplied argument is not a \ref python_xbmcgui_control "Control" + /// type + /// @throws SystemError On Internal error + /// @throws RuntimeError If control is not added to a window + /// + setFocus(...); +#else + SWIGHIDDENVIRTUAL void setFocus(Control* pControl); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ setFocusId(ControlId) } + /// Gives the control with the supplied focus. + /// + /// @param ControlId [integer] On skin defined id of control + /// @throws SystemError On Internal error + /// @throws RuntimeError If control is not added to a window + /// + setFocusId(...); +#else + SWIGHIDDENVIRTUAL void setFocusId(int iControlId); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getFocus(Control) } + /// Returns the control which is focused. + /// + /// @return Focused control class + /// @throws SystemError On Internal error + /// @throws RuntimeError If no control has focus + /// + getFocus(); +#else + SWIGHIDDENVIRTUAL Control* getFocus(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getFocusId(int) } + /// Returns the id of the control which is focused. + /// + /// @return Focused control id + /// @throws SystemError On Internal error + /// @throws RuntimeError If no control has focus + /// + getFocusId(); +#else + SWIGHIDDENVIRTUAL long getFocusId(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ removeControl(Control) } + /// Removes the control from this window. + /// + /// @param Control \ref python_xbmcgui_control "Control" class to remove + /// @throws TypeError If supplied argument is not a \ref python_xbmcgui_control + /// type + /// @throws RuntimeError If control is not added to this window + /// + /// This will not delete the control. It is only removed from the window. + /// + removeControl(...); +#else + SWIGHIDDENVIRTUAL void removeControl(Control* pControl); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ removeControls(List) } + /// Removes a list of controls from this window. + /// + /// @param List List with controls to remove + /// @throws TypeError If supplied argument is not a \ref python_xbmcgui_control + /// type + /// @throws RuntimeError If control is not added to this window + /// + /// This will not delete the controls. They are only removed from the + /// window. + /// + removeControls(...); +#else + SWIGHIDDENVIRTUAL void removeControls(std::vector<Control*> pControls); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getHeight() } + /// Returns the height of this Window instance. + /// + /// @return Window height in pixels + /// + ///----------------------------------------------------------------------- + /// @python_v18 Function changed + /// + getHeight(); +#else + SWIGHIDDENVIRTUAL long getHeight(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getWidth() } + /// Returns the width of this Window instance. + /// + /// @return Window width in pixels + /// + ///----------------------------------------------------------------------- + /// @python_v18 Function changed + /// + getWidth(); +#else + SWIGHIDDENVIRTUAL long getWidth(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ setProperty(key, value) } + /// Sets a window property, similar to an infolabel. + /// + /// @param key string - property name. + /// @param value string or unicode - value of property. + /// + /// @note Key is NOT case sensitive. Setting value to an empty string is + /// equivalent to clearProperty(key).\n + /// You can use the above as keywords for arguments and skip + /// certain optional arguments.\n + /// Once you use a keyword, all following arguments require the + /// keyword. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + /// win.setProperty('Category', 'Newest') + /// .. + /// ~~~~~~~~~~~~~ + /// + setProperty(...); +#else + SWIGHIDDENVIRTUAL void setProperty(const char* key, const String& value); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getProperty(key) } + /// Returns a window property as a string, similar to an infolabel. + /// + /// @param key string - property name. + /// + /// @note Key is NOT case sensitive.\n + /// You can use the above as keywords for arguments and skip + /// certain optional arguments. + /// Once you use a keyword, all following arguments require the + /// keyword. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + /// category = win.getProperty('Category') + /// .. + /// ~~~~~~~~~~~~~ + /// + getProperty(...); +#else + SWIGHIDDENVIRTUAL String getProperty(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ clearProperty(key) } + /// Clears the specific window property. + /// + /// @param key string - property name. + /// + /// @note Key is NOT case sensitive. Equivalent to setProperty(key,'') + /// You can use the above as keywords for arguments and skip certain + /// optional arguments. + /// Once you use a keyword, all following arguments require the + /// keyword. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + /// win.clearProperty('Category') + /// .. + /// ~~~~~~~~~~~~~ + /// + clearProperty(...); +#else + SWIGHIDDENVIRTUAL void clearProperty(const char* key); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ clearProperties() } + /// Clears all window properties. + /// + /// + ///----------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.Window(xbmcgui.getCurrentWindowId()) + /// win.clearProperties() + /// .. + /// ~~~~~~~~~~~~~ + /// + clearProperties(); +#else + SWIGHIDDENVIRTUAL void clearProperties(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ close() } + /// Closes this window. + /// + /// Closes this window by activating the old window. + /// + /// @note The window is not deleted with this method. + /// + close(); +#else + SWIGHIDDENVIRTUAL void close(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ doModal() } + /// Display this window until close() is called. + /// + doModal(); +#else + + SWIGHIDDENVIRTUAL void doModal(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ addControl(Control) } + /// Add a \ref python_xbmcgui_control "Control" to this window. + /// + /// @param Control \ref python_xbmcgui_control "Control" to add + /// @throws TypeError If supplied argument is not a \ref python_xbmcgui_control + /// type + /// @throws ReferenceError If control is already used in another + /// window + /// @throws RuntimeError Should not happen :-) + /// + /// The next controls can be added to a window atm + /// | Control-class | Description | + /// |---------------------|------------------------------------------------------------| + /// | \ref python_xbmcgui_control_label "ControlLabel" | Label control to show text + /// | \ref python_xbmcgui_control_fadelabel "ControlFadeLabel" | The fadelabel has multiple labels which it cycles through + /// | \ref python_xbmcgui_control_textbox "ControlTextBox" | To show bigger text field + /// | \ref python_xbmcgui_control_button "ControlButton" | Brings a button to do some actions + /// | \ref python_xbmcgui_control_edit "ControlEdit" | The edit control allows a user to input text in Kodi + /// | \ref python_xbmcgui_control_fadelabel "ControlFadeLabel" | The fade label control is used for displaying multiple pieces of text in the same space in Kodi + /// | \ref python_xbmcgui_control_list "ControlList" | Add a list for something like files + /// | \ref python_xbmcgui_control_group "ControlGroup" | Is for a group which brings the others together + /// | \ref python_xbmcgui_control_image "ControlImage" | Controls a image on skin + /// | \ref python_xbmcgui_control_radiobutton "ControlRadioButton" | For a radio button which handle boolean values + /// | \ref python_xbmcgui_control_progress "ControlProgress" | Progress bar for a performed work or something else + /// | \ref python_xbmcgui_control_slider "ControlSlider" | The slider control is used for things where a sliding bar best represents the operation at hand + /// | \ref python_xbmcgui_control_spin "ControlSpin" | The spin control is used for when a list of options can be chosen + /// | \ref python_xbmcgui_control_textbox "ControlTextBox" | The text box is used for showing a large multipage piece of text in Kodi + /// + addControl(...); +#else + SWIGHIDDENVIRTUAL void addControl(Control* pControl); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ addControls(List) } + /// Add a list of Controls to this window. + /// + /// @param List List with controls to add + /// @throws TypeError If supplied argument is not of List + /// type, or a control is not of \ref python_xbmcgui_control + /// type + /// @throws ReferenceError If control is already used in another + /// window + /// @throws RuntimeError Should not happen :-) + /// + addControls(...); +#else + SWIGHIDDENVIRTUAL void addControls(std::vector<Control*> pControls); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window + /// @brief \python_func{ getControl(controlId) } + /// Gets the control from this window. + /// + /// @param controlId \ref python_xbmcgui_control id to get + /// @throws Exception If \ref python_xbmcgui_control doesn't exist + /// + /// @remark controlId doesn't have to be a python control, it can be a + /// control id from a Kodi window too (you can find id's in the xml files. + /// + /// @note Not python controls are not completely usable yet + /// You can only use the \ref python_xbmcgui_control functions + /// + getControl(...); +#else + SWIGHIDDENVIRTUAL Control* getControl(int iControlId); +#endif + }; + ///@} + } +} diff --git a/xbmc/interfaces/legacy/WindowDialog.cpp b/xbmc/interfaces/legacy/WindowDialog.cpp new file mode 100644 index 0000000..13ea4fd --- /dev/null +++ b/xbmc/interfaces/legacy/WindowDialog.cpp @@ -0,0 +1,76 @@ +/* + * 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 "WindowDialog.h" + +#include "ServiceBroker.h" +#include "WindowInterceptor.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindow.h" +#include "guilib/GUIWindowManager.h" + +#include <mutex> + +namespace XBMCAddon +{ + namespace xbmcgui + { + WindowDialog::WindowDialog() : + Window(true), WindowDialogMixin(this) + { + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + InterceptorBase* interceptor = new Interceptor<CGUIWindow>("CGUIWindow", this, getNextAvailableWindowId()); + // set the render order to the dialog's default because this dialog is mapped to CGUIWindow instead of CGUIDialog + interceptor->SetRenderOrder(RENDER_ORDER_DIALOG); + setWindow(interceptor); + } + + WindowDialog::~WindowDialog() { deallocating(); } + + bool WindowDialog::OnMessage(CGUIMessage& message) + { +#ifdef ENABLE_XBMC_TRACE_API + XBMC_TRACE; + CLog::Log(LOGDEBUG, "{}NEWADDON WindowDialog::OnMessage Message {}", _tg.getSpaces(), + message.GetMessage()); +#endif + + switch(message.GetMessage()) + { + case GUI_MSG_WINDOW_INIT: + { + ref(window)->OnMessage(message); + return true; + } + break; + + case GUI_MSG_CLICKED: + { + return Window::OnMessage(message); + } + break; + } + + // we do not message CGUIPythonWindow here.. + return ref(window)->OnMessage(message); + } + + bool WindowDialog::OnAction(const CAction &action) + { + XBMC_TRACE; + return WindowDialogMixin::OnAction(action) ? true : Window::OnAction(action); + } + + void WindowDialog::OnDeinitWindow(int nextWindowID) + { + CServiceBroker::GetGUI()->GetWindowManager().RemoveDialog(iWindowId); + Window::OnDeinitWindow(nextWindowID); + } + + } +} diff --git a/xbmc/interfaces/legacy/WindowDialog.h b/xbmc/interfaces/legacy/WindowDialog.h new file mode 100644 index 0000000..0d44c86 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowDialog.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Window.h" +#include "WindowDialogMixin.h" + +namespace XBMCAddon +{ + namespace xbmcgui + { + // + /// \defgroup python_xbmcgui_window_dialog Subclass - WindowDialog + /// \ingroup python_xbmcgui_window + /// @{ + /// @brief __GUI window dialog class for Add-Ons.__ + /// + /// \python_class{ xbmcgui.WindowDialog(int windowId): } + /// + /// Creates a new window from Add-On usable dialog class. This is to create + /// window for related controls by system calls. + /// + /// @param windowId [opt] Specify an id to use an existing + /// window. + /// @throws ValueError if supplied window Id does not exist. + /// @throws Exception if more then 200 windows are created. + /// + /// Deleting this window will activate the old window that was active + /// and resets (not delete) all controls that are associated with this + /// window. + /// + /// + ///-------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.WindowDialog() + /// width = dialog.getWidth() + /// .. + /// ~~~~~~~~~~~~~ + /// + /// + // + class WindowDialog : public Window, private WindowDialogMixin + { + public: + WindowDialog(); + ~WindowDialog() override; + +#ifndef SWIG + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + void OnDeinitWindow(int nextWindowID) override; + + bool IsDialogRunning() const override { return WindowDialogMixin::IsDialogRunning(); } + bool IsModalDialog() const override + { + XBMC_TRACE; + return true; + }; + bool IsDialog() const override + { + XBMC_TRACE; + return true; + }; + + inline void show() override + { + XBMC_TRACE; + WindowDialogMixin::show(); + } + inline void close() override + { + XBMC_TRACE; + WindowDialogMixin::close(); + } +#endif + }; + ///@} + } +} diff --git a/xbmc/interfaces/legacy/WindowDialogMixin.cpp b/xbmc/interfaces/legacy/WindowDialogMixin.cpp new file mode 100644 index 0000000..32d6fa9 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowDialogMixin.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 "WindowDialogMixin.h" + +#include "ServiceBroker.h" +#include "WindowInterceptor.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" + +namespace XBMCAddon +{ + namespace xbmcgui + { + void WindowDialogMixin::show() + { + XBMC_TRACE; + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_PYTHON_DIALOG, HACK_CUSTOM_ACTION_OPENING, + 0, static_cast<void*>(w->window->get())); + } + + void WindowDialogMixin::close() + { + XBMC_TRACE; + w->bModal = false; + w->PulseActionEvent(); + + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_PYTHON_DIALOG, HACK_CUSTOM_ACTION_CLOSING, + 0, static_cast<void*>(w->window->get())); + + w->iOldWindowId = 0; + } + + bool WindowDialogMixin::IsDialogRunning() const { XBMC_TRACE; return w->window->isActive(); } + + bool WindowDialogMixin::OnAction(const CAction &action) + { + XBMC_TRACE; + switch (action.GetID()) + { + case HACK_CUSTOM_ACTION_OPENING: + { + // This is from the CGUIPythonWindowXMLDialog::Show_Internal + CServiceBroker::GetGUI()->GetWindowManager().RegisterDialog(w->window->get()); + // active this dialog... + CGUIMessage msg(GUI_MSG_WINDOW_INIT,0,0); + w->OnMessage(msg); + w->window->setActive(true); + //! @todo Figure out how to clean up the CAction + return true; + } + break; + + case HACK_CUSTOM_ACTION_CLOSING: + { + // This is from the CGUIPythonWindowXMLDialog::Show_Internal + w->window->get()->Close(); + return true; + } + break; + } + + return false; + } + } +} + + + diff --git a/xbmc/interfaces/legacy/WindowDialogMixin.h b/xbmc/interfaces/legacy/WindowDialogMixin.h new file mode 100644 index 0000000..5057e81 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowDialogMixin.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Window.h" + +// These messages are a side effect of the way dialogs work through the +// main ApplicationMessenger. At some point it would be nice to remove +// the messenger and have direct (or even drive) communications. +#define HACK_CUSTOM_ACTION_CLOSING -3 +#define HACK_CUSTOM_ACTION_OPENING -4 + +namespace XBMCAddon +{ + namespace xbmcgui + { + class WindowDialogMixin + { + private: + Window* w; + + protected: + inline explicit WindowDialogMixin(Window* window) : w(window) {} + + public: + virtual ~WindowDialogMixin() = default; + + SWIGHIDDENVIRTUAL void show(); + SWIGHIDDENVIRTUAL void close(); + +#ifndef SWIG + SWIGHIDDENVIRTUAL bool IsDialogRunning() const; + SWIGHIDDENVIRTUAL bool OnAction(const CAction &action); +#endif + }; + } +} diff --git a/xbmc/interfaces/legacy/WindowException.h b/xbmc/interfaces/legacy/WindowException.h new file mode 100644 index 0000000..ae35b57 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowException.h @@ -0,0 +1,21 @@ +/* + * 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 "Exception.h" + +namespace XBMCAddon +{ + namespace xbmcgui + { + XBMCCOMMONS_STANDARD_EXCEPTION(WindowException); + } +} + + diff --git a/xbmc/interfaces/legacy/WindowInterceptor.h b/xbmc/interfaces/legacy/WindowInterceptor.h new file mode 100644 index 0000000..4890150 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowInterceptor.h @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "Window.h" +#include "guilib/GUIWindow.h" + +namespace XBMCAddon +{ + namespace xbmcgui + { + +#ifndef SWIG + class Window; + class ref; + + /** + * These two classes are closely associated with the Interceptor template below. + * For more detailed explanation, see that class. + */ + class InterceptorBase + { + protected: + AddonClass::Ref<Window> window; + // This instance is in Window.cpp + static thread_local ref* upcallTls; + + InterceptorBase() : window(NULL) { upcallTls = NULL; } + + /** + * Calling up ONCE resets the upcall to to false. The reason is that when + * a call is recursive we cannot assume the ref has cleared the flag. + * so ... + * + * ref(window)->UpCall() + * + * during the context of 'UpCall' it's possible that another call will + * be made back on the window from the xbmc core side (this happens in + * sometimes in OnMessage). In that case, if upcall is still 'true', than + * the call will wrongly proceed back to the xbmc core side rather than + * to the Addon API side. + */ + static bool up() { bool ret = ((upcallTls) != NULL); upcallTls = NULL; return ret; } + public: + + virtual ~InterceptorBase() { if (window.isNotNull()) { window->interceptorClear(); } } + + virtual CGUIWindow* get() = 0; + + virtual void SetRenderOrder(int renderOrder) { } + + virtual void setActive(bool active) { } + virtual bool isActive() { return false; } + + friend class ref; + }; + + /** + * Guard class. But instead of managing memory or thread resources, + * any call made using the operator-> results in an 'upcall.' That is, + * it expects the call about to be made to have come from the XBMCAddon + * xbmcgui Window API and not from either the scripting language or the + * XBMC core Windowing system. + * + * This class is meant to hold references to instances of Interceptor<P>. + * see that template definition below. + */ + class ref + { + InterceptorBase* w; + public: + inline explicit ref(InterceptorBase* b) : w(b) { w->upcallTls = this; } + inline ~ref() { w->upcallTls = NULL; } + inline CGUIWindow* operator->() { return w->get(); } + inline CGUIWindow* get() { return w->get(); } + }; + + /** + * The intention of this class is to intercept calls from + * multiple points in the CGUIWindow class hierarchy and pass + * those calls over to the XBMCAddon API Window hierarchy. It + * is a class template that uses the type template parameter + * to determine the parent class. + * + * Since this class also maintains two way communication between + * the XBMCAddon API Window hierarchy and the core XBMC CGUIWindow + * hierarchy, it is used as a general bridge. For calls going + * TO the window hierarchy (as in callbacks) it operates in a + * straightforward manner. In the reverse direction (from the + * XBMCAddon hierarchy) it uses some hackery in a "smart pointer" + * overloaded -> operator. + */ + +#define checkedb(methcall) ( window.isNotNull() ? window-> methcall : false ) +#define checkedv(methcall) { if (window.isNotNull()) window-> methcall ; } + + template <class P /* extends CGUIWindow*/> class Interceptor : + public P, public InterceptorBase + { + std::string classname; + protected: + CGUIWindow* get() override { return this; } + + public: + Interceptor(const char* specializedName, + Window* _window, int windowid) : P(windowid, ""), + classname("Interceptor<" + std::string(specializedName) + ">") + { +#ifdef ENABLE_XBMC_TRACE_API + XBMCAddonUtils::TraceGuard tg; + CLog::Log(LOGDEBUG, "{}NEWADDON constructing {} 0x{:x}", tg.getSpaces(), classname, + (long)(((void*)this))); +#endif + window.reset(_window); + P::SetLoadType(CGUIWindow::LOAD_ON_GUI_INIT); + } + + Interceptor(const char* specializedName, + Window* _window, int windowid, + const char* xmlfile) : P(windowid, xmlfile), + classname("Interceptor<" + std::string(specializedName) + ">") + { +#ifdef ENABLE_XBMC_TRACE_API + XBMCAddonUtils::TraceGuard tg; + CLog::Log(LOGDEBUG, "{}NEWADDON constructing {} 0x{:x}", tg.getSpaces(), classname, + (long)(((void*)this))); +#endif + window.reset(_window); + P::SetLoadType(CGUIWindow::LOAD_ON_GUI_INIT); + } + +#ifdef ENABLE_XBMC_TRACE_API + ~Interceptor() override + { + XBMCAddonUtils::TraceGuard tg; + CLog::Log(LOGDEBUG, "{}NEWADDON LIFECYCLE destroying {} 0x{:x}", tg.getSpaces(), classname, + (long)(((void*)this))); + } +#else + ~Interceptor() override = default; +#endif + + bool OnMessage(CGUIMessage& message) override + { XBMC_TRACE; return up() ? P::OnMessage(message) : checkedb(OnMessage(message)); } + bool OnAction(const CAction &action) override + { XBMC_TRACE; return up() ? P::OnAction(action) : checkedb(OnAction(action)); } + + // NOTE!!: This ALWAYS skips up to the CGUIWindow instance. + bool OnBack(int actionId) override + { XBMC_TRACE; return up() ? CGUIWindow::OnBack(actionId) : checkedb(OnBack(actionId)); } + + void OnDeinitWindow(int nextWindowID) override + { XBMC_TRACE; if(up()) P::OnDeinitWindow(nextWindowID); else checkedv(OnDeinitWindow(nextWindowID)); } + + bool IsModalDialog() const override + { + XBMC_TRACE; + return checkedb(IsModalDialog()); + } + + bool IsDialogRunning() const override + { + XBMC_TRACE; + return checkedb(IsDialogRunning()); + } + bool IsDialog() const override + { + XBMC_TRACE; + return checkedb(IsDialog()); + } + bool IsMediaWindow() const override + { + XBMC_TRACE; + return checkedb(IsMediaWindow()); + } + + void SetRenderOrder(int renderOrder) override { XBMC_TRACE; P::m_renderOrder = renderOrder; } + + void setActive(bool active) override { XBMC_TRACE; P::m_active = active; } + bool isActive() override { XBMC_TRACE; return P::m_active; } + }; + + template <class P /* extends CGUIWindow*/> class InterceptorDialog : + public Interceptor<P> + { + public: + InterceptorDialog(const char* specializedName, + Window* _window, int windowid) : + Interceptor<P>(specializedName, _window, windowid) + { } + + InterceptorDialog(const char* specializedName, + Window* _window, int windowid, + const char* xmlfile) : + Interceptor<P>(specializedName, _window, windowid,xmlfile) + { } + }; + +#undef checkedb +#undef checkedv + +#endif + } +} diff --git a/xbmc/interfaces/legacy/WindowXML.cpp b/xbmc/interfaces/legacy/WindowXML.cpp new file mode 100644 index 0000000..51a4fbf --- /dev/null +++ b/xbmc/interfaces/legacy/WindowXML.cpp @@ -0,0 +1,527 @@ +/* + * 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 "WindowXML.h" + +#include "ServiceBroker.h" +#include "WindowException.h" +#include "WindowInterceptor.h" +#include "addons/Addon.h" +#include "addons/Skin.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/TextureManager.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" + +#include <mutex> + +// These #defs are for WindowXML +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 + +#define A(x) interceptor->x + +namespace XBMCAddon +{ + namespace xbmcgui + { + template class Interceptor<CGUIMediaWindow>; + + /** + * This class extends the Interceptor<CGUIMediaWindow> in order to + * add behavior for a few more virtual functions that were unnecessary + * in the Window or WindowDialog. + */ +#define checkedb(methcall) ( window.isNotNull() ? xwin-> methcall : false ) +#define checkedv(methcall) { if (window.isNotNull()) xwin-> methcall ; } + + + //! @todo This should be done with template specialization + class WindowXMLInterceptor : public InterceptorDialog<CGUIMediaWindow> + { + WindowXML* xwin; + public: + WindowXMLInterceptor(WindowXML* _window, int windowid,const char* xmlfile) : + InterceptorDialog<CGUIMediaWindow>("CGUIMediaWindow",_window,windowid,xmlfile), xwin(_window) + { } + + void AllocResources(bool forceLoad = false) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::AllocResources(forceLoad); else checkedv(AllocResources(forceLoad)); } + void FreeResources(bool forceUnLoad = false) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::FreeResources(forceUnLoad); else checkedv(FreeResources(forceUnLoad)); } + bool OnClick(int iItem, const std::string &player = "") override { XBMC_TRACE; return up() ? CGUIMediaWindow::OnClick(iItem, player) : checkedb(OnClick(iItem)); } + + void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) override + { XBMC_TRACE; if(up()) CGUIMediaWindow::Process(currentTime,dirtyregions); else checkedv(Process(currentTime,dirtyregions)); } + + // this is a hack to SKIP the CGUIMediaWindow + bool OnAction(const CAction &action) override + { XBMC_TRACE; return up() ? CGUIWindow::OnAction(action) : checkedb(OnAction(action)); } + + protected: + // CGUIWindow + bool LoadXML(const std::string &strPath, const std::string &strPathLower) override + { XBMC_TRACE; return up() ? CGUIMediaWindow::LoadXML(strPath,strPathLower) : xwin->LoadXML(strPath,strPathLower); } + + // CGUIMediaWindow + void GetContextButtons(int itemNumber, CContextButtons &buttons) override + { XBMC_TRACE; if (up()) CGUIMediaWindow::GetContextButtons(itemNumber,buttons); else xwin->GetContextButtons(itemNumber,buttons); } + bool Update(const std::string &strPath, bool) override + { XBMC_TRACE; return up() ? CGUIMediaWindow::Update(strPath) : xwin->Update(strPath); } + void SetupShares() override { XBMC_TRACE; if(up()) CGUIMediaWindow::SetupShares(); else checkedv(SetupShares()); } + + friend class WindowXML; + friend class WindowXMLDialog; + + }; + + WindowXML::~WindowXML() { XBMC_TRACE; deallocating(); } + + WindowXML::WindowXML(const String& xmlFilename, + const String& scriptPath, + const String& defaultSkin, + const String& defaultRes, + bool isMedia) : + Window(true) + { + XBMC_TRACE; + RESOLUTION_INFO res; + std::string strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res); + m_isMedia = isMedia; + + if (!CFileUtils::Exists(strSkinPath)) + { + std::string str("none"); + ADDON::AddonInfoPtr addonInfo = + std::make_shared<ADDON::CAddonInfo>(str, ADDON::AddonType::SKIN); + ADDON::CSkinInfo::TranslateResolution(defaultRes, res); + + // Check for the matching folder for the skin in the fallback skins folder + std::string fallbackPath = URIUtils::AddFileToFolder(scriptPath, "resources", "skins"); + std::string basePath = URIUtils::AddFileToFolder(fallbackPath, g_SkinInfo->ID()); + + strSkinPath = g_SkinInfo->GetSkinPath(xmlFilename, &res, basePath); + + // Check for the matching folder for the skin in the fallback skins folder (if it exists) + if (CFileUtils::Exists(basePath)) + { + addonInfo->SetPath(basePath); + std::shared_ptr<ADDON::CSkinInfo> skinInfo = std::make_shared<ADDON::CSkinInfo>(addonInfo, res); + skinInfo->Start(); + strSkinPath = skinInfo->GetSkinPath(xmlFilename, &res); + } + + if (!CFileUtils::Exists(strSkinPath)) + { + // Finally fallback to the DefaultSkin as it didn't exist in either the XBMC Skin folder or the fallback skin folder + addonInfo->SetPath(URIUtils::AddFileToFolder(fallbackPath, defaultSkin)); + std::shared_ptr<ADDON::CSkinInfo> skinInfo = std::make_shared<ADDON::CSkinInfo>(addonInfo, res); + + skinInfo->Start(); + strSkinPath = skinInfo->GetSkinPath(xmlFilename, &res); + if (!CFileUtils::Exists(strSkinPath)) + throw WindowException("XML File for Window is missing"); + } + } + + m_scriptPath = scriptPath; +// sXMLFileName = strSkinPath; + + interceptor = new WindowXMLInterceptor(this, lockingGetNextAvailableWindowId(),strSkinPath.c_str()); + setWindow(interceptor); + interceptor->SetCoordsRes(res); + } + + int WindowXML::lockingGetNextAvailableWindowId() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(CServiceBroker::GetWinSystem()->GetGfxContext()); + return getNextAvailableWindowId(); + } + + void WindowXML::addItem(const Alternative<String, const ListItem*>& item, int position) + { + XBMC_TRACE; + // item could be deleted if the reference count is 0. + // so I MAY need to check prior to using a Ref just in + // case this object is managed by Python. I'm not sure + // though. + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + + // Tells the window to add the item to FileItem vector + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + + //---------------------------------------------------- + // Former AddItem call + //AddItem(ritem->item, pos); + { + CFileItemPtr& fileItem = ritem->item; + if (position == INT_MAX || position > A(m_vecItems)->Size()) + { + A(m_vecItems)->Add(fileItem); + } + else if (position < -1 && !(position*-1 < A(m_vecItems)->Size())) + { + A(m_vecItems)->AddFront(fileItem,0); + } + else + { + A(m_vecItems)->AddFront(fileItem,position); + } + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + //---------------------------------------------------- + } + } + + void WindowXML::addItems(const std::vector<Alternative<String, const XBMCAddon::xbmcgui::ListItem* > > & items) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + for (auto item : items) + { + AddonClass::Ref<ListItem> ritem = item.which() == XBMCAddon::first ? ListItem::fromString(item.former()) : AddonClass::Ref<ListItem>(item.later()); + CFileItemPtr& fileItem = ritem->item; + A(m_vecItems)->Add(fileItem); + } + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + + void WindowXML::removeItem(int position) + { + XBMC_TRACE; + // Tells the window to remove the item at the specified position from the FileItem vector + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_vecItems)->Remove(position); + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + int WindowXML::getCurrentListPosition() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + int listPos = A(m_viewControl).GetSelectedItem(); + return listPos; + } + + void WindowXML::setCurrentListPosition(int position) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_viewControl).SetSelectedItem(position); + } + + ListItem* WindowXML::getListItem(int position) + { + XBMCAddonUtils::GuiLock lock(languageHook, false); + //CFileItemPtr fi = pwx->GetListItem(listPos); + CFileItemPtr fi; + { + if (position < 0 || position >= A(m_vecItems)->Size()) + return new ListItem(); + fi = A(m_vecItems)->Get(position); + } + + if (fi == NULL) + { + throw WindowException("Index out of range (%i)",position); + } + + ListItem* sListItem = new ListItem(); + sListItem->item = fi; + + // let's hope someone reference counts this. + return sListItem; + } + + int WindowXML::getListSize() + { + XBMC_TRACE; + return A(m_vecItems)->Size(); + } + + void WindowXML::clearList() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(ClearFileItems()); + + A(m_viewControl).SetItems(*(A(m_vecItems))); + } + + void WindowXML::setContainerProperty(const String& key, const String& value) + { + XBMC_TRACE; + A(m_vecItems)->SetProperty(key, value); + } + + void WindowXML::setContent(const String& value) + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + A(m_vecItems)->SetContent(value); + } + + int WindowXML::getCurrentContainerId() + { + XBMC_TRACE; + XBMCAddonUtils::GuiLock lock(languageHook, false); + return A(m_viewControl.GetCurrentControl()); + } + + bool WindowXML::OnAction(const CAction &action) + { + XBMC_TRACE; + // do the base class window first, and the call to python after this + bool ret = ref(window)->OnAction(action); // we don't currently want the mediawindow actions here + // look at the WindowXMLInterceptor onAction, it skips + // the CGUIMediaWindow::OnAction and calls directly to + // CGUIWindow::OnAction + AddonClass::Ref<Action> inf(new Action(action)); + invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get())); + PulseActionEvent(); + return ret; + } + + bool WindowXML::OnMessage(CGUIMessage& message) + { +#ifdef ENABLE_XBMC_TRACE_API + XBMC_TRACE; + CLog::Log(LOGDEBUG, "{}Message id:{}", _tg.getSpaces(), (int)message.GetMessage()); +#endif + + //! @todo We shouldn't be dropping down to CGUIWindow in any of this ideally. + //! We have to make up our minds about what python should be doing and + //! what this side of things should be doing + switch (message.GetMessage()) + { + case GUI_MSG_WINDOW_DEINIT: + { + return ref(window)->OnMessage(message); + } + break; + + case GUI_MSG_WINDOW_INIT: + { + ref(window)->OnMessage(message); + invokeCallback(new CallbackFunction<WindowXML>(this,&WindowXML::onInit)); + PulseActionEvent(); + return true; + } + break; + + case GUI_MSG_FOCUSED: + { + if (A(m_viewControl).HasControl(message.GetControlId()) && + A(m_viewControl).GetCurrentControl() != message.GetControlId()) + { + A(m_viewControl).SetFocused(); + return true; + } + // check if our focused control is one of our category buttons + int iControl=message.GetControlId(); + + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onFocus,iControl)); + PulseActionEvent(); + } + break; + + case GUI_MSG_NOTIFY_ALL: + // most messages from GUI_MSG_NOTIFY_ALL break container content, whitelist working ones. + if (message.GetParam1() == GUI_MSG_PAGE_CHANGE || message.GetParam1() == GUI_MSG_WINDOW_RESIZE) + return A(CGUIMediaWindow::OnMessage(message)); + return true; + + case GUI_MSG_CLICKED: + { + int iControl=message.GetSenderId(); + // Handle Sort/View internally. Scripters shouldn't use ID 2, 3 or 4. + if (iControl == CONTROL_BTNSORTASC) // sort asc + { + CLog::Log(LOGINFO, "WindowXML: Internal asc/dsc button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortOrder(); + UpdateFileList();*/ + return true; + } + else if (iControl == CONTROL_BTNSORTBY) // sort by + { + CLog::Log(LOGINFO, "WindowXML: Internal sort button not implemented"); + /*if (m_guiState.get()) + m_guiState->SetNextSortMethod(); + UpdateFileList();*/ + return true; + } + + if(iControl && iControl != interceptor->GetID()) // pCallbackWindow && != this->GetID()) + { + CGUIControl* controlClicked = interceptor->GetControl(iControl); + + // The old python way used to check list AND SELECITEM method + // or if its a button, radiobutton. + // Its done this way for now to allow other controls without a + // python version like togglebutton to still raise a onAction event + if (controlClicked) // Will get problems if we the id is not on the window + // and we try to do GetControlType on it. So check to make sure it exists + { + if ((controlClicked->IsContainer() && (message.GetParam1() == ACTION_SELECT_ITEM || message.GetParam1() == ACTION_MOUSE_LEFT_CLICK)) || !controlClicked->IsContainer()) + { + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onClick,iControl)); + PulseActionEvent(); + return true; + } + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_DOUBLE_CLICK) + { + invokeCallback(new CallbackFunction<WindowXML,int>(this,&WindowXML::onDoubleClick,iControl)); + PulseActionEvent(); + return true; + } + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_MOUSE_RIGHT_CLICK) + { + AddonClass::Ref<Action> inf(new Action(CAction(ACTION_CONTEXT_MENU))); + invokeCallback(new CallbackFunction<WindowXML,AddonClass::Ref<Action> >(this,&WindowXML::onAction,inf.get())); + PulseActionEvent(); + return true; + } + // the core context menu can lead to all sort of issues right now when used with WindowXMLs, so lets intercept the corresponding message + else if (controlClicked->IsContainer() && message.GetParam1() == ACTION_CONTEXT_MENU) + return true; + } + } + } + break; + } + + return A(CGUIMediaWindow::OnMessage(message)); + } + + void WindowXML::AllocResources(bool forceLoad /*= false */) + { + XBMC_TRACE; + std::string tmpDir = URIUtils::GetDirectory(ref(window)->GetProperty("xmlfile").asString()); + std::string fallbackMediaPath; + URIUtils::GetParentPath(tmpDir, fallbackMediaPath); + URIUtils::RemoveSlashAtEnd(fallbackMediaPath); + m_mediaDir = fallbackMediaPath; + + //CLog::Log(LOGDEBUG, "CGUIPythonWindowXML::AllocResources called: {}", fallbackMediaPath); + CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir); + ref(window)->AllocResources(forceLoad); + CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir); + } + + void WindowXML::FreeResources(bool forceUnLoad /*= false */) + { + XBMC_TRACE; + + ref(window)->FreeResources(forceUnLoad); + } + + void WindowXML::Process(unsigned int currentTime, CDirtyRegionList ®ions) + { + XBMC_TRACE; + CServiceBroker::GetGUI()->GetTextureManager().AddTexturePath(m_mediaDir); + ref(window)->Process(currentTime, regions); + CServiceBroker::GetGUI()->GetTextureManager().RemoveTexturePath(m_mediaDir); + } + + bool WindowXML::OnClick(int iItem) + { + XBMC_TRACE; + // Hook Over calling CGUIMediaWindow::OnClick(iItem) results in it trying to PLAY the file item + // which if its not media is BAD and 99 out of 100 times undesirable. + return false; + } + + bool WindowXML::OnDoubleClick(int iItem) + { + XBMC_TRACE; + return false; + } + + void WindowXML::GetContextButtons(int itemNumber, CContextButtons &buttons) + { + XBMC_TRACE; + // maybe on day we can make an easy way to do this context menu + // with out this method overriding the MediaWindow version, it will display 'Add to Favorites' + } + + bool WindowXML::LoadXML(const String &strPath, const String &strLowerPath) + { + XBMC_TRACE; + return A(CGUIWindow::LoadXML(strPath, strLowerPath)); + } + + void WindowXML::SetupShares() + { + XBMC_TRACE; + } + + bool WindowXML::Update(const String &strPath) + { + XBMC_TRACE; + return true; + } + + WindowXMLDialog::WindowXMLDialog(const String& xmlFilename, const String& scriptPath, + const String& defaultSkin, + const String& defaultRes) : + WindowXML(xmlFilename, scriptPath, defaultSkin, defaultRes), + WindowDialogMixin(this) + { XBMC_TRACE; } + + WindowXMLDialog::~WindowXMLDialog() { XBMC_TRACE; deallocating(); } + + bool WindowXMLDialog::OnMessage(CGUIMessage &message) + { + XBMC_TRACE; + if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) + return A(CGUIWindow::OnMessage(message)); + + return WindowXML::OnMessage(message); + } + + bool WindowXMLDialog::OnAction(const CAction &action) + { + XBMC_TRACE; + return WindowDialogMixin::OnAction(action) ? true : WindowXML::OnAction(action); + } + + void WindowXMLDialog::OnDeinitWindow(int nextWindowID) + { + XBMC_TRACE; + CServiceBroker::GetGUI()->GetWindowManager().RemoveDialog(interceptor->GetID()); + WindowXML::OnDeinitWindow(nextWindowID); + } + + bool WindowXMLDialog::LoadXML(const String &strPath, const String &strLowerPath) + { + XBMC_TRACE; + if (WindowXML::LoadXML(strPath, strLowerPath)) + { + // Set the render order to the dialog's default in case it's not specified in the skin xml + // because this dialog is mapped to CGUIMediaWindow instead of CGUIDialog. + // This must be done here, because the render order will be reset before loading the skin xml. + if (ref(window)->GetRenderOrder() == RENDER_ORDER_WINDOW) + window->SetRenderOrder(RENDER_ORDER_DIALOG); + return true; + } + return false; + } + + } + +} diff --git a/xbmc/interfaces/legacy/WindowXML.h b/xbmc/interfaces/legacy/WindowXML.h new file mode 100644 index 0000000..349b0a0 --- /dev/null +++ b/xbmc/interfaces/legacy/WindowXML.h @@ -0,0 +1,559 @@ +/* + * 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 "Window.h" +#include "WindowDialogMixin.h" +#include "swighelper.h" +#include "windows/GUIMediaWindow.h" + +#include <limits.h> +#include <vector> + +namespace XBMCAddon +{ + namespace xbmcgui + { + class ListItem; + class WindowXMLInterceptor; + + // + /// \defgroup python_xbmcgui_window_xml Subclass - WindowXML + /// \ingroup python_xbmcgui_window + /// @{ + /// @brief __GUI xml window class.__ + /// + /// \python_class{ xbmcgui.WindowXML(xmlFilename, scriptPath[, defaultSkin, defaultRes]) } + /// + /// Creates a new xml file based window class. + /// + /// \note This class include also all calls from <b><c>\ref python_xbmcgui_window</c></b>. + /// + /// @param xmlFilename string - the name of the xml file to + /// look for. + /// @param scriptPath string - path to script. used to + /// fallback to if the xml doesn't exist in + /// the current skin. (eg xbmcaddon.Addon().getAddonInfo('path')) + /// @param defaultSkin [opt] string - name of the folder in the + /// skins path to look in for the xml. + /// (default='Default') + /// @param defaultRes [opt] string - default skins resolution. + /// (1080i, 720p, ntsc16x9, ntsc, pal16x9 or pal. default='720p') + /// @param isMedia [opt] bool - if False, create a regular window. + /// if True, create a mediawindow. + /// (default=False) + /// @throws Exception if more then 200 windows are created. + /// + /// \remark Skin folder structure is e.g. **resources/skins/Default/720p** + /// + /// Deleting this window will activate the old window that was active + /// and resets (not delete) all controls that are associated with this + /// window. + /// + ///-------------------------------------------------------------------------- + /// @python_v18 New param added **isMedia**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// win = xbmcgui.WindowXML('script-Lyrics-main.xml', xbmcaddon.Addon().getAddonInfo('path'), 'default', '1080i', False) + /// win.doModal() + /// del win + /// .. + /// ~~~~~~~~~~~~~ + /// + /// + ///-------------------------------------------------------------------------- + /// + /// On functions defined input variable <b><tt>controlId</tt> (GUI control identifier)</b> + /// is the on window.xml defined value behind type added with <tt><b>id="..."</b></tt> and + /// used to identify for changes there and on callbacks. + /// + /// ~~~~~~~~~~~~~{.xml} + /// <control type="label" id="31"> + /// <description>Title Label</description> + /// ... + /// </control> + /// <control type="progress" id="32"> + /// <description>progress control</description> + /// ... + /// </control> + /// ~~~~~~~~~~~~~ + /// + // + class WindowXML : public Window + { + std::string sFallBackPath; + + protected: +#ifndef SWIG + /** + * This helper retrieves the next available id. It is doesn't + * assume that the global lock is already being held. + */ + static int lockingGetNextAvailableWindowId(); + + WindowXMLInterceptor* interceptor; +#endif + + public: + WindowXML(const String& xmlFilename, const String& scriptPath, + const String& defaultSkin = "Default", + const String& defaultRes = "720p", + bool isMedia = false); + ~WindowXML() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ addItem(item[, position]) } + /// Add a new item to this Window List. + /// + /// @param item string, unicode or ListItem - item to add. + /// @param position [opt] integer - position of item to add. (NO Int = Adds to bottom,0 adds to top, 1 adds to one below from top,-1 adds to one above from bottom etc etc ) + /// - If integer positions are greater than list size, negative positions will add to top of list, positive positions will add to bottom of list + /// + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.addItem('Reboot Kodi', 0) + /// .. + /// ~~~~~~~~~~~~~ + /// + addItem(...); +#else + SWIGHIDDENVIRTUAL void addItem(const Alternative<String, const ListItem*>& item, int position = INT_MAX); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ addItems(items) } + /// Add a list of items to to the window list. + /// + /// + /// @param items List - list of strings, unicode objects or ListItems to add. + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.addItems(['Reboot Kodi', 'Restart Kodi']) + /// .. + /// ~~~~~~~~~~~~~ + /// + addItems(...); +#else + SWIGHIDDENVIRTUAL void addItems(const std::vector<Alternative<String, const XBMCAddon::xbmcgui::ListItem* > > & items); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ removeItem(position) } + /// Removes a specified item based on position, from the Window List. + /// + /// @param position integer - position of item to remove. + /// + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.removeItem(5) + /// .. + /// ~~~~~~~~~~~~~ + /// + removeItem(...); +#else + SWIGHIDDENVIRTUAL void removeItem(int position); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ getCurrentListPosition() } + /// Gets the current position in the Window List. + /// + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// pos = self.getCurrentListPosition() + /// .. + /// ~~~~~~~~~~~~~ + /// + getCurrentListPosition(); +#else + SWIGHIDDENVIRTUAL int getCurrentListPosition(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ setCurrentListPosition(position) } + /// Set the current position in the Window List. + /// + /// @param position integer - position of item to set. + /// + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.setCurrentListPosition(5) + /// .. + /// ~~~~~~~~~~~~~ + /// + setCurrentListPosition(...); +#else + SWIGHIDDENVIRTUAL void setCurrentListPosition(int position); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ getListItem(position) } + /// Returns a given ListItem in this Window List. + /// + /// @param position integer - position of item to return. + /// + /// + /// + /// ---------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// listitem = self.getListItem(6) + /// .. + /// ~~~~~~~~~~~~~ + /// + getListItem(...); +#else + SWIGHIDDENVIRTUAL ListItem* getListItem(int position); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ getListSize() } + /// Returns the number of items in this Window List. + /// + /// + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// listSize = self.getListSize() + /// .. + /// ~~~~~~~~~~~~~ + /// + getListSize(); +#else + SWIGHIDDENVIRTUAL int getListSize(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ clearList() } + /// Clear the Window List. + /// + /// ------------------------------------------------------------------------ + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.clearList() + /// .. + /// ~~~~~~~~~~~~~ + /// + clearList(); +#else + SWIGHIDDENVIRTUAL void clearList(); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ setContainerProperty(key, value) } + /// Sets a container property, similar to an infolabel. + /// + /// @param key string - property name. + /// @param value string or unicode - value of property. + /// + /// @note Key is NOT case sensitive.\n + /// You can use the above as keywords for arguments and skip certain + /// optional arguments.\n + /// Once you use a keyword, all following arguments require the keyword. + /// + /// + /// ------------------------------------------------------------------------ + /// @python_v17 Changed function from **setProperty** to **setContainerProperty**. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.setContainerProperty('Category', 'Newest') + /// .. + /// ~~~~~~~~~~~~~ + /// + setContainerProperty(...); +#else + SWIGHIDDENVIRTUAL void setContainerProperty(const String &strProperty, const String &strValue); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ setContent(value) } + /// Sets the content type of the container. + /// + /// @param value string or unicode - content value. + /// + /// __Available content types__ + /// | Name | Media | + /// |:-----------:|:-----------------------------------------| + /// | actors | Videos + /// | addons | Addons, Music, Pictures, Programs, Videos + /// | albums | Music, Videos + /// | artists | Music, Videos + /// | countries | Music, Videos + /// | directors | Videos + /// | files | Music, Videos + /// | games | Games + /// | genres | Music, Videos + /// | images | Pictures + /// | mixed | Music, Videos + /// | movies | Videos + /// | Musicvideos | Music, Videos + /// | playlists | Music, Videos + /// | seasons | Videos + /// | sets | Videos + /// | songs | Music + /// | studios | Music, Videos + /// | tags | Music, Videos + /// | tvshows | Videos + /// | videos | Videos + /// | years | Music, Videos + /// + /// ------------------------------------------------------------------------ + /// @python_v18 Added new function. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// self.setContent('movies') + /// .. + /// ~~~~~~~~~~~~~ + /// + setContent(...); +#else + SWIGHIDDENVIRTUAL void setContent(const String &strValue); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcgui_window_xml + /// @brief \python_func{ getCurrentContainerId() } + /// Get the id of the currently visible container. + /// + /// ------------------------------------------------------------------------ + /// @python_v17 Added new function. + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// container_id = self.getCurrentContainerId() + /// .. + /// ~~~~~~~~~~~~~ + /// + getCurrentContainerId(...); +#else + SWIGHIDDENVIRTUAL int getCurrentContainerId(); +#endif + +#ifndef SWIG + // CGUIWindow + bool OnMessage(CGUIMessage& message) override; + bool OnAction(const CAction& action) override; + SWIGHIDDENVIRTUAL void AllocResources(bool forceLoad = false); + SWIGHIDDENVIRTUAL void FreeResources(bool forceUnLoad = false); + SWIGHIDDENVIRTUAL bool OnClick(int iItem); + SWIGHIDDENVIRTUAL bool OnDoubleClick(int iItem); + SWIGHIDDENVIRTUAL void Process(unsigned int currentTime, CDirtyRegionList &dirtyregions); + + bool IsMediaWindow() const override + { + XBMC_TRACE; + return m_isMedia; + }; + + // This method is identical to the Window::OnDeinitWindow method + // except it passes the message on to their respective parents. + // Since the respective parent differences are handled by the + // interceptor there's no reason to define this one here. +// SWIGHIDDENVIRTUAL void OnDeinitWindow(int nextWindowID); + + + protected: + // CGUIWindow + SWIGHIDDENVIRTUAL bool LoadXML(const String &strPath, const String &strPathLower); + + // CGUIMediaWindow + SWIGHIDDENVIRTUAL void GetContextButtons(int itemNumber, CContextButtons &buttons); + SWIGHIDDENVIRTUAL bool Update(const String &strPath); + + void SetupShares(); + String m_scriptPath; + String m_mediaDir; + bool m_isMedia; + + friend class WindowXMLInterceptor; +#endif + }; + ///@} + + // Ideally what we want here is a Dialog/Media Window. The problem is that these + // are two orthogonal discriminations of CGUIWindow and there wasn't a previous + // accounting for this possibility through the use of making CGUIWindow a + // virtual base class of the pertinent subclasses. So now we're left with + // no good solution. + // + // <strike>So here we're going to have the 'main' hierarchy (the one visible to SWIG) + // go the way intended - through WindowXML, but we're going to borrow dialog + // functionality from CGUIDialog by using it as a Mixin.</strike> + // + // jmarshall says that this class has no reason to inherit from CGUIMediaWindow. + // At some point this entire hierarchy needs to be reworked. The XML handling + // routines should be put in a mixin. + // + /// \defgroup python_xbmcgui_window_dialog_xml Subclass - WindowDialogXML + /// \ingroup python_xbmcgui_window_xml + /// @{ + /// @brief __GUI xml window dialog__ + /// + /// \python_class{ xbmcgui.WindowXMLDialog(xmlFilename, scriptPath[, defaultSkin, defaultRes]) } + /// + /// Creates a new xml file based window dialog class. + /// + /// @param xmlFilename string - the name of the xml file to + /// look for. + /// @param scriptPath string - path to script. used to + /// fallback to if the xml doesn't exist in + /// the current skin. (eg \ref python_xbmcaddon_Addon "xbmcaddon.Addon().getAddonInfo('path'))" + /// @param defaultSkin [opt] string - name of the folder in the + /// skins path to look in for the xml. + /// (default='Default') + /// @param defaultRes [opt] string - default skins resolution. + /// (1080i, 720p, ntsc16x9, ntsc, pal16x9 or pal. default='720p') + /// @throws Exception if more then 200 windows are created. + /// + /// @note Skin folder structure is e.g. **resources/skins/Default/720p** + /// + /// + ///------------------------------------------------------------------------- + /// + /// **Example:** + /// ~~~~~~~~~~~~~{.py} + /// .. + /// dialog = xbmcgui.WindowXMLDialog('script-Lyrics-main.xml', xbmcaddon.Addon().getAddonInfo('path'), 'default', '1080i') + /// dialog.doModal() + /// del dialog + /// .. + /// ~~~~~~~~~~~~~ + /// + /// + ///------------------------------------------------------------------------- + /// + /// On functions defined input variable <b><tt>controlId</tt> (GUI control identifier)</b> + /// is the on window.xml defined value behind type added with <tt><b>id="..."</b></tt> and + /// used to identify for changes there and on callbacks. + /// + /// ~~~~~~~~~~~~~{.xml} + /// <control type="label" id="31"> + /// <description>Title Label</description> + /// ... + /// </control> + /// <control type="progress" id="32"> + /// <description>progress control</description> + /// ... + /// </control> + /// ~~~~~~~~~~~~~ + /// + // + class WindowXMLDialog : public WindowXML, private WindowDialogMixin + { + public: + WindowXMLDialog(const String& xmlFilename, const String& scriptPath, + const String& defaultSkin = "Default", + const String& defaultRes = "720p"); + + ~WindowXMLDialog() override; + +#ifndef SWIG + bool OnMessage(CGUIMessage& message) override; + bool IsDialogRunning() const override + { + XBMC_TRACE; + return WindowDialogMixin::IsDialogRunning(); + } + bool IsDialog() const override + { + XBMC_TRACE; + return true; + }; + bool IsModalDialog() const override + { + XBMC_TRACE; + return true; + }; + bool IsMediaWindow() const override + { + XBMC_TRACE; + return false; + }; + bool OnAction(const CAction& action) override; + void OnDeinitWindow(int nextWindowID) override; + + bool LoadXML(const String& strPath, const String& strPathLower) override; + + inline void show() override + { + XBMC_TRACE; + WindowDialogMixin::show(); + } + inline void close() override + { + XBMC_TRACE; + WindowDialogMixin::close(); + } + + friend class DialogJumper; +#endif + }; + ///@} + } +} diff --git a/xbmc/interfaces/legacy/aojsonrpc.h b/xbmc/interfaces/legacy/aojsonrpc.h new file mode 100644 index 0000000..7f1d875 --- /dev/null +++ b/xbmc/interfaces/legacy/aojsonrpc.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/json-rpc/ITransportLayer.h" +#include "interfaces/json-rpc/JSONRPC.h" + +class CVariant; + +class CAddOnTransport : public JSONRPC::ITransportLayer +{ +public: + bool PrepareDownload(const char *path, CVariant &details, std::string &protocol) override { return false; } + bool Download(const char *path, CVariant& result) override { return false; } + int GetCapabilities() override { return JSONRPC::Response; } + + class CAddOnClient : public JSONRPC::IClient + { + public: + int GetPermissionFlags() override { return JSONRPC::OPERATION_PERMISSION_ALL; } + int GetAnnouncementFlags() override { return 0; } + bool SetAnnouncementFlags(int flags) override { return true; } + }; +}; diff --git a/xbmc/interfaces/legacy/swighelper.h b/xbmc/interfaces/legacy/swighelper.h new file mode 100644 index 0000000..15a822c --- /dev/null +++ b/xbmc/interfaces/legacy/swighelper.h @@ -0,0 +1,100 @@ +/* + * 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 + +/** + * SWIGHIDDENVIRTUAL allows the keyword 'virtual' to be there when the main + * Addon api is compiled, but be hidden from the SWIG code generator. + * + * This is to provide finer grain control over which methods are callbackable + * (is that a word? ...) + * into the scripting language, and which ones are not. True polymorphic + * behavior across the scripting language boundary will ONLY occur where + * the keyword 'virtual' is used. In other words, you can use the macro + * SWIGHIDDENVIRTUAL to 'hide' the polymorphic behavior from the scripting + * language using the macro instead. + * + * Note: You should not hide virtual destructors from the scripting language. + */ +#ifdef SWIG +#define SWIGHIDDENVIRTUAL +#else +#define SWIGHIDDENVIRTUAL virtual +#endif + +/** + * SWIG_CONSTANT_FROM_GETTER will define a constant in the scripting + * language from a getter in the Addon api and also provide the + * Addon api declaration. E.g. If you use: + * + * SWIG_CONSTANT_FROM_GETTER(int, MY_CONSTANT); + * + * ... in an Addon api header file then you need to define a function: + * + * int getMy_CONSTANT(); + * + * ... in a .cpp file. That call will be made to determine the value + * of the constant in the scripting language. + */ +#ifdef SWIG +#define SWIG_CONSTANT_FROM_GETTER(type,varname) %constant type varname = get##varname () +#else +#define SWIG_CONSTANT_FROM_GETTER(type,varname) type get##varname () +#endif + +/** + * SWIG_CONSTANT defines a constant in SWIG from an already existing + * definition in the Addon api. E.g. a #define from the core window + * system like SORT_METHOD_PROGRAM_COUNT included in the api via + * a #include can be exposed to the scripting language using + * SWIG_CONSTANT(int,SORT_METHOD_PROGRAM_COUNT). + * + * This macro can be used when the constant name and the C++ reference + * look the same. When they look different see SWIG_CONSTANT2 + * + * Note, this declaration is invisible to the API C++ code and can + * only be seen by the SWIG processor. + */ +#ifdef SWIG +#define SWIG_CONSTANT(type,var) %constant type var = var +#else +#define SWIG_CONSTANT(type,var) +#endif + +/** + * SWIG_CONSTANT2 defines a constant in SWIG from an already existing + * definition in the Addon api. E.g. a #define from the core window + * system like SORT_METHOD_VIDEO_YEAR included in the api via + * a #include can be exposed to the scripting language using + * SWIG_CONSTANT2(int,SORT_METHOD_VIDEO_YEAR,SORT_METHOD_YEAR). + * + * This macro can be used when the constant name and the C++ reference + * don't look the same. When they look the same see SWIG_CONSTANT + * + * Note, this declaration is invisible to the API C++ code and can + * only be seen by the SWIG processor. + */ +#ifdef SWIG +#define SWIG_CONSTANT2(type,var,val) %constant type var = val +#else +#define SWIG_CONSTANT2(type,var,val) +#endif + +/** +* SWIG_IMMUTABLE defines a member as immutable i.e. read-only. +* +* Note, this declaration is invisible to the API C++ code and can +* only be seen by the SWIG processor. +*/ +#ifdef SWIG +#define SWIG_IMMUTABLE(var) %feature("immutable"); var; %feature("immutable", "") +#else +#define SWIG_IMMUTABLE(var) var +#endif + diff --git a/xbmc/interfaces/legacy/wsgi/CMakeLists.txt b/xbmc/interfaces/legacy/wsgi/CMakeLists.txt new file mode 100644 index 0000000..cc29eb4 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/CMakeLists.txt @@ -0,0 +1,13 @@ +if(MICROHTTPD_FOUND) + set(SOURCES WsgiErrorStream.cpp + WsgiInputStream.cpp + WsgiResponseBody.cpp + WsgiResponse.cpp) + + set(HEADERS WsgiErrorStream.h + WsgiInputStream.h + WsgiResponse.h + WsgiResponseBody.h) + + core_add_library(legacy_interface_wsgi) +endif() diff --git a/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.cpp b/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.cpp new file mode 100644 index 0000000..b8096fe --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WsgiErrorStream.h" + +#include "network/httprequesthandler/python/HTTPPythonRequest.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + WsgiErrorStream::WsgiErrorStream() + : m_request(NULL) + { } + + WsgiErrorStream::~WsgiErrorStream() + { + m_request = NULL; + } + + void WsgiErrorStream::write(const String& str) + { + if (str.empty()) + return; + + String msg = str; + // remove a trailing \n + if (msg.at(msg.size() - 1) == '\n') + msg.erase(msg.size() - 1); + + if (m_request != NULL) + CLog::Log(LOGERROR, "WSGI [{}]: {}", m_request->url, msg); + else + CLog::Log(LOGERROR, "WSGI: {}", msg); + } + + void WsgiErrorStream::writelines(const std::vector<String>& seq) + { + if (seq.empty()) + return; + + String msg = StringUtils::Join(seq, ""); + write(msg); + } + +#ifndef SWIG + void WsgiErrorStream::SetRequest(HTTPPythonRequest* request) + { + if (m_request != NULL) + return; + + m_request = request; + } +#endif + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.h b/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.h new file mode 100644 index 0000000..e9e7694 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiErrorStream.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/AddonClass.h" + +#include <vector> + +struct HTTPPythonRequest; + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + + /// \defgroup python_xbmcwsgi_WsgiErrorStream WsgiErrorStream + /// \ingroup python_xbmcwsgi + /// @{ + /// @brief **Represents the wsgi.errors stream to write error messages.** + /// + /// \python_class{ WsgiErrorStream() } + /// + /// This implementation writes the error messages to the application's log + /// file. + /// + ///------------------------------------------------------------------------- + /// + class WsgiErrorStream : public AddonClass + { + public: + WsgiErrorStream(); + ~WsgiErrorStream() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcwsgi_WsgiErrorStream + /// \python_func{ flush() } + /// Since nothing is buffered this is a no-op. + /// + /// + flush(); +#else + inline void flush() { } +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcwsgi_WsgiErrorStream + /// \python_func{ write(str) } + /// Writes the given error message to the application's log file. + /// + /// @param str A string to save in log file + /// + /// @note A trailing `\n` is removed. + /// + write(...); +#else + void write(const String& str); +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// + /// \ingroup python_xbmcwsgi_WsgiErrorStream + /// \python_func{ writelines(seq) } + /// Joins the given list of error messages (without any separator) into + /// a single error message which is written to the application's log file. + /// + /// @param seq A list of strings which will be logged. + /// + writelines(...); +#else + void writelines(const std::vector<String>& seq); +#endif + +#ifndef SWIG + /** + * Sets the given request. + */ + void SetRequest(HTTPPythonRequest* request); + + HTTPPythonRequest* m_request; +#endif + }; + /// @} + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiInputStream.cpp b/xbmc/interfaces/legacy/wsgi/WsgiInputStream.cpp new file mode 100644 index 0000000..5146007 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiInputStream.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WsgiInputStream.h" + +#include "network/httprequesthandler/python/HTTPPythonRequest.h" +#include "utils/StringUtils.h" + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + WsgiInputStreamIterator::WsgiInputStreamIterator() + : m_data(), + m_line() + { } + +#ifndef SWIG + WsgiInputStreamIterator::WsgiInputStreamIterator(const String& data, bool end /* = false */) + : m_data(data), + m_remaining(end ? 0 : data.size()), + m_line() + { } +#endif + + WsgiInputStreamIterator::~WsgiInputStreamIterator() = default; + + String WsgiInputStreamIterator::read(unsigned long size /* = 0 */) const + { + // make sure we don't try to read more data than we have + if (size <= 0 || size > m_remaining) + size = m_remaining; + + // remember the current read offset + size_t offset = static_cast<size_t>(m_offset); + + // adjust the read offset and the remaining data length + m_offset += size; + m_remaining -= size; + + // return the data being requested + return m_data.substr(offset, size); + } + + String WsgiInputStreamIterator::readline(unsigned long size /* = 0 */) const + { + // make sure we don't try to read more data than we have + if (size <= 0 || size > m_remaining) + size = m_remaining; + + size_t offset = static_cast<size_t>(m_offset); + size_t pos = m_data.find('\n', offset); + + // make sure pos has a valid value and includes the \n character + if (pos == std::string::npos) + pos = m_data.size(); + else + pos += 1; + + if (pos - offset < size) + size = pos - offset; + + // read the next line + String line = read(size); + + // remove any trailing \r\n + StringUtils::TrimRight(line, "\r\n"); + + return line; + } + + std::vector<String> WsgiInputStreamIterator::readlines(unsigned long sizehint /* = 0 */) const + { + std::vector<String> lines; + + // make sure we don't try to read more data than we have + if (sizehint <= 0 || sizehint > m_remaining) + sizehint = m_remaining; + + do + { + // read a full line + String line = readline(); + + // adjust the sizehint by the number of bytes just read + sizehint -= line.length(); + + // add it to the list of read lines + lines.push_back(line); + } while (sizehint > 0); + + return lines; + } + +#ifndef SWIG + WsgiInputStreamIterator& WsgiInputStreamIterator::operator++() + { + m_line.clear(); + + if (!end()) + { + // read the next line + m_line = readline(); + } + + return *this; + } + + bool WsgiInputStreamIterator::operator==(const WsgiInputStreamIterator& rhs) + { + return m_data == rhs.m_data && + m_offset == rhs.m_offset && + m_remaining == rhs.m_remaining; + } + + bool WsgiInputStreamIterator::operator!=(const WsgiInputStreamIterator& rhs) + { + return !(*this == rhs); + } + + String& WsgiInputStreamIterator::operator*() + { + return m_line; + } +#endif + + WsgiInputStream::WsgiInputStream() + : m_request(NULL) + { } + + WsgiInputStream::~WsgiInputStream() + { + m_request = NULL; + } + +#ifndef SWIG + WsgiInputStreamIterator* WsgiInputStream::begin() + { + return new WsgiInputStreamIterator(m_data, false); + } + + WsgiInputStreamIterator* WsgiInputStream::end() + { + return new WsgiInputStreamIterator(m_data, true); + } + + void WsgiInputStream::SetRequest(HTTPPythonRequest* request) + { + if (m_request != NULL) + return; + + m_request = request; + + // set the remaining bytes to be read + m_data = m_request->requestContent; + m_offset = 0; + m_remaining = m_data.size(); + } +#endif + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiInputStream.h b/xbmc/interfaces/legacy/wsgi/WsgiInputStream.h new file mode 100644 index 0000000..d7bf73f --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiInputStream.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/AddonClass.h" + +#include <vector> + +struct HTTPPythonRequest; + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + + // Iterator for the wsgi.input stream. + class WsgiInputStreamIterator : public AddonClass + { + public: + WsgiInputStreamIterator(); + ~WsgiInputStreamIterator() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcwsgi_WsgiInputStream + /// \python_func{ read([size]) } + /// + /// Read a maximum of `<size>` bytes from the wsgi.input stream. + /// + /// @param size [opt] bytes to read + /// @return Returns the readed string + /// + read(...); +#else + String read(unsigned long size = 0) const; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcwsgi_WsgiInputStream + /// \python_func{ readline([size]) } + /// + /// Read a full line up to a maximum of `<size>` bytes from the wsgi.input + /// stream. + /// + /// @param size [opt] bytes to read + /// @return Returns the readed string line + /// + read(...); +#else + String readline(unsigned long size = 0) const; +#endif + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcwsgi_WsgiInputStream + /// \python_func{ readlines([sizehint]) } + /// + /// Read multiple full lines up to at least `<sizehint>` bytes from the + /// wsgi.input stream and return them as a list. + /// + /// @param sizehint [opt] bytes to read + /// @return Returns a list readed string lines + /// + read(...); +#else + std::vector<String> readlines(unsigned long sizehint = 0) const; +#endif + +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + WsgiInputStreamIterator(const String& data, bool end = false); + + WsgiInputStreamIterator& operator++(); + bool operator==(const WsgiInputStreamIterator& rhs); + bool operator!=(const WsgiInputStreamIterator& rhs); + String& operator*(); + inline bool end() const { return m_remaining <= 0; } + + protected: + String m_data; + mutable unsigned long m_offset = 0; + mutable unsigned long m_remaining = 0; + + private: + String m_line; +#endif + }; + + /// \defgroup python_xbmcwsgi_WsgiInputStream WsgiInputStream + /// \ingroup python_xbmcwsgi + /// @{ + /// @brief **Represents the wsgi.input stream to access data from a HTTP request.** + /// + /// \python_class{ WsgiInputStream() } + /// + ///------------------------------------------------------------------------- + /// + class WsgiInputStream : public WsgiInputStreamIterator + { + public: + WsgiInputStream(); + ~WsgiInputStream() override; + +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + WsgiInputStreamIterator* begin(); + WsgiInputStreamIterator* end(); + + /** + * Sets the given request. + */ + void SetRequest(HTTPPythonRequest* request); + + HTTPPythonRequest* m_request; +#endif + }; + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiResponse.cpp b/xbmc/interfaces/legacy/wsgi/WsgiResponse.cpp new file mode 100644 index 0000000..175e83d --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiResponse.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WsgiResponse.h" + +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <inttypes.h> +#include <utility> + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + WsgiResponse::WsgiResponse() + : m_responseHeaders(), + m_body() + { } + + WsgiResponse::~WsgiResponse() = default; + + WsgiResponseBody* WsgiResponse::operator()(const String& status, const std::vector<WsgiHttpHeader>& response_headers, void* exc_info /* = NULL */) + { + if (m_called) + { + CLog::Log(LOGWARNING, "WsgiResponse: callable has already been called"); + return NULL; + } + + m_called = true; + + // parse the status + if (!status.empty()) + { + std::vector<String> statusParts = StringUtils::Split(status, ' ', 2); + if (statusParts.size() == 2 && StringUtils::IsNaturalNumber(statusParts.front())) + { + int64_t parsedStatus = strtol(statusParts.front().c_str(), NULL, 0); + if (parsedStatus >= MHD_HTTP_OK && parsedStatus <= MHD_HTTP_NOT_EXTENDED) + m_status = static_cast<int>(parsedStatus); + else + CLog::Log(LOGWARNING, "WsgiResponse: invalid status number {} in \"{}\" provided", + parsedStatus, status); + } + else + CLog::Log(LOGWARNING, "WsgiResponse: invalid status \"{}\" provided", status); + } + else + CLog::Log(LOGWARNING, "WsgiResponse: empty status provided"); + + // copy the response headers + for (const auto& headerIt : response_headers) + m_responseHeaders.insert({headerIt.first(), headerIt.second()}); + + return &m_body; + } + +#ifndef SWIG + void WsgiResponse::Append(const std::string& data) + { + if (!data.empty()) + m_body.m_data.append(data); + } + + bool WsgiResponse::Finalize(HTTPPythonRequest* request) const + { + if (request == NULL || !m_called) + return false; + + // copy the response status + request->responseStatus = m_status; + + // copy the response headers + if (m_status >= MHD_HTTP_OK && m_status < MHD_HTTP_BAD_REQUEST) + request->responseHeaders.insert(m_responseHeaders.begin(), m_responseHeaders.end()); + else + request->responseHeadersError.insert(m_responseHeaders.begin(), m_responseHeaders.end()); + + // copy the body + request->responseData = m_body.m_data; + + return true; + } +#endif + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiResponse.h b/xbmc/interfaces/legacy/wsgi/WsgiResponse.h new file mode 100644 index 0000000..412e520 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiResponse.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/AddonClass.h" +#include "interfaces/legacy/Tuple.h" +#include "interfaces/legacy/wsgi/WsgiResponseBody.h" +#include "network/httprequesthandler/python/HTTPPythonRequest.h" + +#include <vector> + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + typedef Tuple<String, String> WsgiHttpHeader; + + /// \defgroup python_xbmcwsgi_WsgiResponse WsgiResponse + /// \ingroup python_xbmcwsgi + /// @{ + /// @brief **Represents the start_response callable passed to a WSGI handler.** + /// + /// \python_class{ WsgiResponse() } + /// + ///------------------------------------------------------------------------- + /// + class WsgiResponse : public AddonClass + { + public: + WsgiResponse(); + ~WsgiResponse() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcwsgi_WsgiInputStreamIterator + /// \python_func{ operator(status, response_headers[, exc_info]) } + /// + /// Callable implementation to initialize the response with the given + /// HTTP status and the HTTP response headers. + /// + /// @param status an HTTP status string like 200 OK or 404 + /// Not Found. + /// @param response_headers a list of (header_name, header_value) + /// tuples. It must be a Python list. Each + /// header_name must be a valid HTTP header + /// field-name (as + /// @param exc_info [optional] python sys.exc_info() tuple. + /// This argument should be supplied by the + /// application only if start_response is + /// being called by an error + /// @return The write() method \ref python_xbmcwsgi_WsgiResponseBody "WsgiResponseBody" + /// + operator(...); +#else + WsgiResponseBody* operator()(const String& status, const std::vector<WsgiHttpHeader>& response_headers, void* exc_info = NULL); +#endif + +#ifndef SWIG + void Append(const std::string& data); + + bool Finalize(HTTPPythonRequest* request) const; + + private: + bool m_called = false; + int m_status = MHD_HTTP_INTERNAL_SERVER_ERROR; + std::multimap<std::string, std::string> m_responseHeaders; + + WsgiResponseBody m_body; +#endif + }; + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.cpp b/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.cpp new file mode 100644 index 0000000..2e84319 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "WsgiResponseBody.h" + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + WsgiResponseBody::WsgiResponseBody() + : m_data() + { } + + WsgiResponseBody::~WsgiResponseBody() = default; + + void WsgiResponseBody::operator()(const String& data) + { + if (data.empty()) + return; + + m_data.append(data); + } + } +} diff --git a/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.h b/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.h new file mode 100644 index 0000000..4f18583 --- /dev/null +++ b/xbmc/interfaces/legacy/wsgi/WsgiResponseBody.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/AddonClass.h" + +namespace XBMCAddon +{ + namespace xbmcwsgi + { + /// \defgroup python_xbmcwsgi_WsgiResponseBody WsgiResponseBody + /// \ingroup python_xbmcwsgi + /// @{ + /// @brief **Represents the write callable returned by the start_response callable passed to a WSGI handler.** + /// + /// \python_class{ WsgiResponseBody() } + /// + ///------------------------------------------------------------------------- + /// + class WsgiResponseBody : public AddonClass + { + public: + WsgiResponseBody(); + ~WsgiResponseBody() override; + +#ifdef DOXYGEN_SHOULD_USE_THIS + /// \ingroup python_xbmcwsgi_WsgiInputStreamIterator + /// \python_func{ operator(status, response_headers[, exc_info]) } + /// + /// Callable implementation to write data to the response. + /// + /// @param data string data to write + /// + operator()(...); +#else + void operator()(const String& data); +#endif + +#if !defined SWIG && !defined DOXYGEN_SHOULD_SKIP_THIS + String m_data; +#endif + }; + } +} diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp new file mode 100644 index 0000000..b6158a8 --- /dev/null +++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +// python.h should always be included first before any other includes +#include "AddonPythonInvoker.h" + +#include <utility> + +#include <Python.h> +#include <osdefs.h> + +#define MODULE "xbmc" + +#define RUNSCRIPT_PREAMBLE \ + "" \ + "import " MODULE "\n" \ + "class xbmcout:\n" \ + " def __init__(self, loglevel=" MODULE ".LOGDEBUG):\n" \ + " self.ll=loglevel\n" \ + " def write(self, data):\n" \ + " " MODULE ".log(data,self.ll)\n" \ + " def close(self):\n" \ + " " MODULE ".log('.')\n" \ + " def flush(self):\n" \ + " " MODULE ".log('.')\n" \ + "import sys\n" \ + "sys.stdout = xbmcout()\n" \ + "sys.stderr = xbmcout(" MODULE ".LOGERROR)\n" \ + "" + +#define RUNSCRIPT_SETUPTOOLS_HACK \ + "" \ + "import types,sys\n" \ + "pkg_resources_code = \\\n" \ + "\"\"\"\n" \ + "def resource_filename(__name__,__path__):\n" \ + " return __path__\n" \ + "\"\"\"\n" \ + "pkg_resources = types.ModuleType('pkg_resources')\n" \ + "exec(pkg_resources_code, pkg_resources.__dict__)\n" \ + "sys.modules['pkg_resources'] = pkg_resources\n" \ + "" + +#define RUNSCRIPT_SETUP_ENVIROMENT_VARIABLES \ + "" \ + "from os import environ\n" \ + "environ['SSL_CERT_FILE'] = 'system/certs/cacert.pem'\n" \ + "" + +#define RUNSCRIPT_POSTSCRIPT \ + "print('-->Python Interpreter Initialized<--')\n" \ + "" + +#if defined(TARGET_ANDROID) + +#define RUNSCRIPT_COMPLIANT \ + RUNSCRIPT_PREAMBLE RUNSCRIPT_SETUPTOOLS_HACK RUNSCRIPT_POSTSCRIPT + +#elif defined(TARGET_WINDOWS_STORE) + +#define RUNSCRIPT_COMPLIANT \ + RUNSCRIPT_PREAMBLE RUNSCRIPT_SETUP_ENVIROMENT_VARIABLES RUNSCRIPT_POSTSCRIPT + +#else + +#define RUNSCRIPT_COMPLIANT \ + RUNSCRIPT_PREAMBLE RUNSCRIPT_POSTSCRIPT + +#endif + +namespace PythonBindings { +PyObject* PyInit_Module_xbmcdrm(void); +PyObject* PyInit_Module_xbmcgui(void); +PyObject* PyInit_Module_xbmc(void); +PyObject* PyInit_Module_xbmcplugin(void); +PyObject* PyInit_Module_xbmcaddon(void); +PyObject* PyInit_Module_xbmcvfs(void); +} + +using namespace PythonBindings; + +typedef struct +{ + const char *name; + CPythonInvoker::PythonModuleInitialization initialization; +} PythonModule; + +static PythonModule PythonModules[] = + { + { "xbmcdrm", PyInit_Module_xbmcdrm }, + { "xbmcgui", PyInit_Module_xbmcgui }, + { "xbmc", PyInit_Module_xbmc }, + { "xbmcplugin", PyInit_Module_xbmcplugin }, + { "xbmcaddon", PyInit_Module_xbmcaddon }, + { "xbmcvfs", PyInit_Module_xbmcvfs } + }; + +CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler) + : CPythonInvoker(invocationHandler) +{ + PyImport_AppendInittab("xbmcdrm", PyInit_Module_xbmcdrm); + PyImport_AppendInittab("xbmcgui", PyInit_Module_xbmcgui); + PyImport_AppendInittab("xbmc", PyInit_Module_xbmc); + PyImport_AppendInittab("xbmcplugin", PyInit_Module_xbmcplugin); + PyImport_AppendInittab("xbmcaddon", PyInit_Module_xbmcaddon); + PyImport_AppendInittab("xbmcvfs", PyInit_Module_xbmcvfs); +} + +CAddonPythonInvoker::~CAddonPythonInvoker() = default; + +std::map<std::string, CPythonInvoker::PythonModuleInitialization> CAddonPythonInvoker::getModules() const +{ + static std::map<std::string, PythonModuleInitialization> modules; + if (modules.empty()) + { + for (const PythonModule& pythonModule : PythonModules) + modules.insert(std::make_pair(pythonModule.name, pythonModule.initialization)); + } + + return modules; +} + +const char* CAddonPythonInvoker::getInitializationScript() const +{ + return RUNSCRIPT_COMPLIANT; +} diff --git a/xbmc/interfaces/python/AddonPythonInvoker.h b/xbmc/interfaces/python/AddonPythonInvoker.h new file mode 100644 index 0000000..a846071 --- /dev/null +++ b/xbmc/interfaces/python/AddonPythonInvoker.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/python/PythonInvoker.h" + +class CAddonPythonInvoker : public CPythonInvoker +{ +public: + explicit CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler); + ~CAddonPythonInvoker() override; + +protected: + // overrides of CPythonInvoker + std::map<std::string, PythonModuleInitialization> getModules() const override; + const char* getInitializationScript() const override; +}; diff --git a/xbmc/interfaces/python/CMakeLists.txt b/xbmc/interfaces/python/CMakeLists.txt new file mode 100644 index 0000000..061cc2b --- /dev/null +++ b/xbmc/interfaces/python/CMakeLists.txt @@ -0,0 +1,21 @@ +set(SOURCES AddonPythonInvoker.cpp + CallbackHandler.cpp + ContextItemAddonInvoker.cpp + LanguageHook.cpp + PythonInvoker.cpp + XBPython.cpp + swig.cpp + PyContext.cpp) + +set(HEADERS AddonPythonInvoker.h + CallbackHandler.h + ContextItemAddonInvoker.h + LanguageHook.h + preamble.h + PyContext.h + PythonInvoker.h + pythreadstate.h + swig.h + XBPython.h) + +core_add_library(python_interface) diff --git a/xbmc/interfaces/python/CallbackHandler.cpp b/xbmc/interfaces/python/CallbackHandler.cpp new file mode 100644 index 0000000..8fd31a7 --- /dev/null +++ b/xbmc/interfaces/python/CallbackHandler.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "CallbackHandler.h" + +#include "LanguageHook.h" + +namespace XBMCAddon +{ + namespace Python + { + /** + * We are ASS-U-MEing that this construction is happening + * within the context of a Python call. This way we can + * store off the PyThreadState to later verify that we're + * handling callbacks in the appropriate thread. + */ + PythonCallbackHandler::PythonCallbackHandler() + { + XBMC_TRACE; + objectThreadState = PyThreadState_Get(); + } + + /** + * Now we are answering the question as to whether or not we are in the + * PyThreadState that we were in when we started. + */ + bool PythonCallbackHandler::isStateOk(AddonClass* obj) + { + XBMC_TRACE; + PyThreadState* state = PyThreadState_Get(); + if (objectThreadState == state) + { + // make sure the interpreter is still active. + AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> lh(XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp)); + if (lh.isNotNull() && lh->HasRegisteredAddonClassInstance(obj) && lh.get() == obj->GetLanguageHook()) + return true; + } + return false; + } + + /** + * For this method we expect the PyThreadState to be passed as the user + * data for the check. + * + * @todo This is a stupid way to get this information back to the handler. + * there should be a more language neutral means. + */ + bool PythonCallbackHandler::shouldRemoveCallback(AddonClass* obj, void* threadState) + { + XBMC_TRACE; + if (threadState == objectThreadState) + return true; + + // we also want to remove the callback if the language hook no longer exists. + // this is a belt-and-suspenders cleanup mechanism + return ! XBMCAddon::Python::PythonLanguageHook::IsAddonClassInstanceRegistered(obj); + } + } +} diff --git a/xbmc/interfaces/python/CallbackHandler.h b/xbmc/interfaces/python/CallbackHandler.h new file mode 100644 index 0000000..b128b27 --- /dev/null +++ b/xbmc/interfaces/python/CallbackHandler.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/CallbackHandler.h" + +#include <Python.h> + +namespace XBMCAddon +{ + namespace Python + { + /** + * This class represents a specialization of the callback handler + * that specifically checks to see if we're in an OK thread state + * based on Python specifics. + */ + class PythonCallbackHandler : public RetardedAsyncCallbackHandler + { + PyThreadState* objectThreadState; + public: + + /** + * We are ASS-U-MEing that this construction is happening + * within the context of a Python call. This way we can + * store off the PyThreadState to later verify that we're + * handling callbacks in the appropriate thread. + */ + PythonCallbackHandler(); + bool isStateOk(AddonClass* obj) override; + bool shouldRemoveCallback(AddonClass* obj, void* threadState) override; + }; + } +} diff --git a/xbmc/interfaces/python/ContextItemAddonInvoker.cpp b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp new file mode 100644 index 0000000..734193b --- /dev/null +++ b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +// python.h should always be included first before any other includes +#include "ContextItemAddonInvoker.h" + +#include "interfaces/python/swig.h" +#include "utils/log.h" + +#include <Python.h> +#include <osdefs.h> + + +CContextItemAddonInvoker::CContextItemAddonInvoker( + ILanguageInvocationHandler *invocationHandler, + const CFileItemPtr& item) + : CAddonPythonInvoker(invocationHandler), m_item(CFileItemPtr(new CFileItem(*item.get()))) +{ +} + +CContextItemAddonInvoker::~CContextItemAddonInvoker() = default; + +void CContextItemAddonInvoker::onPythonModuleInitialization(void* moduleDict) +{ + CAddonPythonInvoker::onPythonModuleInitialization(moduleDict); + if (m_item) + { + XBMCAddon::xbmcgui::ListItem* arg = new XBMCAddon::xbmcgui::ListItem(m_item); + PyObject* pyItem = PythonBindings::makePythonInstance(arg, true); + if (pyItem == Py_None || PySys_SetObject("listitem", pyItem) == -1) + { + CLog::Log(LOGERROR, "CPythonInvoker({}, {}): Failed to set sys parameter", GetId(), + m_sourceFile); + //FIXME: we should really abort execution + } + } +} diff --git a/xbmc/interfaces/python/ContextItemAddonInvoker.h b/xbmc/interfaces/python/ContextItemAddonInvoker.h new file mode 100644 index 0000000..b2f4d03 --- /dev/null +++ b/xbmc/interfaces/python/ContextItemAddonInvoker.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/python/AddonPythonInvoker.h" + +#include <memory> + +class CFileItem; +typedef std::shared_ptr<CFileItem> CFileItemPtr; + +class CContextItemAddonInvoker : public CAddonPythonInvoker +{ +public: + explicit CContextItemAddonInvoker(ILanguageInvocationHandler *invocationHandler, + const CFileItemPtr& item); + ~CContextItemAddonInvoker() override; + +protected: + void onPythonModuleInitialization(void* moduleDict) override; + +private: + const CFileItemPtr m_item; +}; diff --git a/xbmc/interfaces/python/LanguageHook.cpp b/xbmc/interfaces/python/LanguageHook.cpp new file mode 100644 index 0000000..0d4747f --- /dev/null +++ b/xbmc/interfaces/python/LanguageHook.cpp @@ -0,0 +1,231 @@ +/* + * 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 "LanguageHook.h" + +#include "CallbackHandler.h" +#include "PyContext.h" +#include "ServiceBroker.h" +#include "XBPython.h" +#include "interfaces/legacy/AddonUtils.h" +#include "utils/log.h" + +#include <mutex> + +namespace XBMCAddon +{ + namespace Python + { + static AddonClass::Ref<PythonLanguageHook> instance; + + static CCriticalSection hooksMutex; + static std::map<PyInterpreterState*,AddonClass::Ref<PythonLanguageHook> > hooks; + + // vtab instantiation + PythonLanguageHook::~PythonLanguageHook() + { + XBMC_TRACE; + XBMCAddon::LanguageHook::deallocating(); + } + + void PythonLanguageHook::MakePendingCalls() + { + XBMC_TRACE; + PythonCallbackHandler::makePendingCalls(); + } + + void PythonLanguageHook::DelayedCallOpen() + { + XBMC_TRACE; + PyGILLock::releaseGil(); + } + + void PythonLanguageHook::DelayedCallClose() + { + XBMC_TRACE; + PyGILLock::acquireGil(); + } + + void PythonLanguageHook::RegisterMe() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(hooksMutex); + hooks[m_interp] = AddonClass::Ref<PythonLanguageHook>(this); + } + + void PythonLanguageHook::UnregisterMe() + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(hooksMutex); + hooks.erase(m_interp); + } + + static AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> g_languageHook; + + // Ok ... we're going to get it even if it doesn't exist. If it doesn't exist then + // we're going to assume we're not in control of the interpreter. This (apparently) + // can be the case. E.g. Libspotify manages to call into a script using a ctypes + // extension but under the control of an Interpreter we know nothing about. In + // cases like this we're going to use a global interpreter + AddonClass::Ref<PythonLanguageHook> PythonLanguageHook::GetIfExists(PyInterpreterState* interp) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(hooksMutex); + std::map<PyInterpreterState*,AddonClass::Ref<PythonLanguageHook> >::iterator iter = hooks.find(interp); + if (iter != hooks.end()) + return iter->second; + + // if we got here then we need to use the global one. + if (g_languageHook.isNull()) + g_languageHook = new XBMCAddon::Python::PythonLanguageHook(); + + return g_languageHook; + } + + bool PythonLanguageHook::IsAddonClassInstanceRegistered(AddonClass* obj) + { + for (const auto& iter : hooks) + { + if (iter.second->HasRegisteredAddonClassInstance(obj)) + return true; + } + return false; + } + + /** + * PythonCallbackHandler expects to be instantiated PER AddonClass instance + * that is to be used as a callback. This is why this cannot be instantiated + * once. + * + * There is an expectation that this method is called from the Python thread + * that instantiated an AddonClass that has the potential for a callback. + * + * See RetardedAsyncCallbackHandler for more details. + * See PythonCallbackHandler for more details + * See PythonCallbackHandler::PythonCallbackHandler for more details + */ + XBMCAddon::CallbackHandler* PythonLanguageHook::GetCallbackHandler() + { + XBMC_TRACE; + return new PythonCallbackHandler(); + } + + String PythonLanguageHook::GetAddonId() + { + XBMC_TRACE; + + // Get a reference to the main module + // and global dictionary + PyObject* main_module = PyImport_AddModule("__main__"); + if (!main_module) + { + CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__); + return ""; + } + PyObject* global_dict = PyModule_GetDict(main_module); + // Extract a reference to the function "func_name" + // from the global dictionary + PyObject* pyid = PyDict_GetItemString(global_dict, "__xbmcaddonid__"); + if (pyid) + return PyUnicode_AsUTF8(pyid); + return ""; + } + + String PythonLanguageHook::GetAddonVersion() + { + XBMC_TRACE; + // Get a reference to the main module + // and global dictionary + PyObject* main_module = PyImport_AddModule("__main__"); + if (!main_module) + { + CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__); + return ""; + } + PyObject* global_dict = PyModule_GetDict(main_module); + // Extract a reference to the function "func_name" + // from the global dictionary + PyObject* pyversion = PyDict_GetItemString(global_dict, "__xbmcapiversion__"); + if (pyversion) + return PyUnicode_AsUTF8(pyversion); + return ""; + } + + long PythonLanguageHook::GetInvokerId() + { + XBMC_TRACE; + + // Get a reference to the main module + // and global dictionary + PyObject* main_module = PyImport_AddModule("__main__"); + if (!main_module) + { + CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__); + return -1; + } + PyObject* global_dict = PyModule_GetDict(main_module); + // Extract a reference to the function "func_name" + // from the global dictionary + PyObject* pyid = PyDict_GetItemString(global_dict, "__xbmcinvokerid__"); + if (pyid) + return PyLong_AsLong(pyid); + return -1; + } + + void PythonLanguageHook::RegisterPlayerCallback(IPlayerCallback* player) + { + XBMC_TRACE; + CServiceBroker::GetXBPython().RegisterPythonPlayerCallBack(player); + } + void PythonLanguageHook::UnregisterPlayerCallback(IPlayerCallback* player) + { + XBMC_TRACE; + CServiceBroker::GetXBPython().UnregisterPythonPlayerCallBack(player); + } + void PythonLanguageHook::RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) + { + XBMC_TRACE; + CServiceBroker::GetXBPython().RegisterPythonMonitorCallBack(monitor); + } + void PythonLanguageHook::UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) + { + XBMC_TRACE; + CServiceBroker::GetXBPython().UnregisterPythonMonitorCallBack(monitor); + } + + bool PythonLanguageHook::WaitForEvent(CEvent& hEvent, unsigned int milliseconds) + { + XBMC_TRACE; + return CServiceBroker::GetXBPython().WaitForEvent(hEvent, milliseconds); + } + + void PythonLanguageHook::RegisterAddonClassInstance(AddonClass* obj) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> l(*this); + obj->Acquire(); + currentObjects.insert(obj); + } + + void PythonLanguageHook::UnregisterAddonClassInstance(AddonClass* obj) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> l(*this); + if (currentObjects.erase(obj) > 0) + obj->Release(); + } + + bool PythonLanguageHook::HasRegisteredAddonClassInstance(AddonClass* obj) + { + XBMC_TRACE; + std::unique_lock<CCriticalSection> l(*this); + return currentObjects.find(obj) != currentObjects.end(); + } + } +} diff --git a/xbmc/interfaces/python/LanguageHook.h b/xbmc/interfaces/python/LanguageHook.h new file mode 100644 index 0000000..6a4e0d0 --- /dev/null +++ b/xbmc/interfaces/python/LanguageHook.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/LanguageHook.h" +#include "threads/Event.h" + +#include <map> +#include <mutex> +#include <set> + +#include <Python.h> + +namespace XBMCAddon +{ + namespace Python + { + struct MutableInteger; + + /** + * This class supplies the python specific functionality for + * plugging into the API. It's got a static only implementation + * and uses the singleton pattern for access. + */ + class PythonLanguageHook : public XBMCAddon::LanguageHook + { + PyInterpreterState* m_interp; + CCriticalSection crit; + std::set<AddonClass*> currentObjects; + + // This constructor is only used to instantiate the global LanguageHook + inline PythonLanguageHook() : m_interp(NULL) { } + + public: + + inline explicit PythonLanguageHook(PyInterpreterState* interp) : m_interp(interp) { } + ~PythonLanguageHook() override; + + void DelayedCallOpen() override; + void DelayedCallClose() override; + void MakePendingCalls() override; + + /** + * PythonCallbackHandler expects to be instantiated PER AddonClass instance + * that is to be used as a callback. This is why this cannot be instantiated + * once. + * + * There is an expectation that this method is called from the Python thread + * that instantiated an AddonClass that has the potential for a callback. + * + * See RetardedAsyncCallbackHandler for more details. + * See PythonCallbackHandler for more details + * See PythonCallbackHandler::PythonCallbackHandler for more details + */ + XBMCAddon::CallbackHandler* GetCallbackHandler() override; + + String GetAddonId() override; + String GetAddonVersion() override; + long GetInvokerId() override; + + void RegisterPlayerCallback(IPlayerCallback* player) override; + void UnregisterPlayerCallback(IPlayerCallback* player) override; + void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) override; + void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) override; + bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds) override; + + static AddonClass::Ref<PythonLanguageHook> GetIfExists(PyInterpreterState* interp); + static bool IsAddonClassInstanceRegistered(AddonClass* obj); + + void RegisterAddonClassInstance(AddonClass* obj); + void UnregisterAddonClassInstance(AddonClass* obj); + bool HasRegisteredAddonClassInstance(AddonClass* obj); + inline bool HasRegisteredAddonClasses() + { + std::unique_lock<CCriticalSection> l(*this); + return !currentObjects.empty(); + } + + // You should hold the lock on the LanguageHook itself if you're + // going to do anything with the set that gets returned. + inline std::set<AddonClass*>& GetRegisteredAddonClasses() { return currentObjects; } + + void UnregisterMe(); + void RegisterMe(); + }; + } +} + diff --git a/xbmc/interfaces/python/MethodType.groovy b/xbmc/interfaces/python/MethodType.groovy new file mode 100644 index 0000000..18597fd --- /dev/null +++ b/xbmc/interfaces/python/MethodType.groovy @@ -0,0 +1,14 @@ +/* + * 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. + */ + +public enum MethodType +{ + constructor, destructor, method +} + + diff --git a/xbmc/interfaces/python/PyContext.cpp b/xbmc/interfaces/python/PyContext.cpp new file mode 100644 index 0000000..3b64ac6 --- /dev/null +++ b/xbmc/interfaces/python/PyContext.cpp @@ -0,0 +1,117 @@ +/* + * 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 "PyContext.h" + +#include "utils/log.h" + +#include <Python.h> + +namespace XBMCAddon +{ + namespace Python + { + struct PyContextState + { + inline explicit PyContextState(bool pcreatedByGilRelease = false) : + state(NULL), createdByGilRelease(pcreatedByGilRelease) {} + + int value = 0; + PyThreadState* state; + int gilReleasedDepth = 0; + bool createdByGilRelease; + }; + + static thread_local PyContextState* tlsPyContextState; + + void* PyContext::enterContext() + { + PyContextState* cur = tlsPyContextState; + if (cur == NULL) + { + cur = new PyContextState(); + tlsPyContextState = cur; + } + + // increment the count + cur->value++; + + return cur; + } + + void PyContext::leaveContext() + { + // here we ASSUME that the constructor was called. + PyContextState* cur = tlsPyContextState; + cur->value--; + int curlevel = cur->value; + + // this is a hack but ... + if (curlevel < 0) + { + CLog::Log(LOGERROR, "FATAL: PyContext closed more than opened"); + curlevel = cur->value = 0; + } + + if (curlevel == 0) + { + // clear the tlsPyContextState + tlsPyContextState = NULL; + delete cur; + } + } + + void PyGILLock::releaseGil() + { + PyContextState* cur = tlsPyContextState; + + // This means we're not within the python context, but + // because we may be in a thread spawned by python itself, + // we need to handle this. + if (!cur) + { + cur = static_cast<PyContextState*>(PyContext::enterContext()); + cur->createdByGilRelease = true; + } + + if (cur->gilReleasedDepth == 0) // true if we are at the outermost + { + PyThreadState* _save; + // this macro sets _save + { + Py_UNBLOCK_THREADS + } + cur->state = _save; + } + cur->gilReleasedDepth++; // the first time this goes to 1 + } + + void PyGILLock::acquireGil() + { + PyContextState* cur = tlsPyContextState; + + // it's not possible for cur to be NULL (and if it is, we want to fail anyway). + + // decrement the depth and make sure we're in the right place. + cur->gilReleasedDepth--; + if (cur->gilReleasedDepth == 0) // are we back to zero? + { + PyThreadState* _save = cur->state; + // This macros uses _save + { + Py_BLOCK_THREADS + } + cur->state = NULL; // clear the state to indicate we've reacquired the gil + + // we clear it only if we created it on this level. + if (cur->createdByGilRelease) + PyContext::leaveContext(); + } + } + } +} diff --git a/xbmc/interfaces/python/PyContext.h b/xbmc/interfaces/python/PyContext.h new file mode 100644 index 0000000..216a45f --- /dev/null +++ b/xbmc/interfaces/python/PyContext.h @@ -0,0 +1,49 @@ +/* + * 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 + +namespace XBMCAddon +{ + namespace Python + { + class PyGILLock; + + /** + * These classes should NOT be used with 'new'. They are expected to reside + * as stack instances and they act as "Guard" classes that track the + * current context. + */ + class PyContext + { + protected: + friend class PyGILLock; + static void* enterContext(); + static void leaveContext(); + public: + + inline PyContext() { enterContext(); } + inline ~PyContext() { leaveContext(); } + }; + + /** + * This class supports recursive locking of the GIL. It assumes that + * all Python GIL manipulation is done through this class so that it + * can monitor the current owner. + */ + class PyGILLock + { + public: + static void releaseGil(); + static void acquireGil(); + + inline PyGILLock() { releaseGil(); } + inline ~PyGILLock() { acquireGil(); } + }; + } +} diff --git a/xbmc/interfaces/python/PythonInvoker.cpp b/xbmc/interfaces/python/PythonInvoker.cpp new file mode 100644 index 0000000..1e9d344 --- /dev/null +++ b/xbmc/interfaces/python/PythonInvoker.cpp @@ -0,0 +1,724 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +// clang-format off +// python.h should always be included first before any other includes +#include <mutex> +#include <Python.h> +// clang-format on + +#include "PythonInvoker.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/addoninfo/AddonInfo.h" +#include "addons/addoninfo/AddonType.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "interfaces/python/PyContext.h" +#include "interfaces/python/pythreadstate.h" +#include "interfaces/python/swig.h" +#include "messaging/ApplicationMessenger.h" +#include "threads/SingleLock.h" +#include "threads/SystemClock.h" +#include "utils/CharsetConverter.h" +#include "utils/FileUtils.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +// clang-format off +// This breaks fmt because of SEP define, don't include +// before anything that includes logging +#include <osdefs.h> +// clang-format on + +#include <cassert> +#include <iterator> + +#ifdef TARGET_WINDOWS +extern "C" FILE* fopen_utf8(const char* _Filename, const char* _Mode); +#else +#define fopen_utf8 fopen +#endif + +#define GC_SCRIPT \ + "import gc\n" \ + "gc.collect(2)\n" + +#define PY_PATH_SEP DELIM + +// Time before ill-behaved scripts are terminated +#define PYTHON_SCRIPT_TIMEOUT 5000ms // ms + +using namespace XFILE; +using namespace std::chrono_literals; + +#define PythonModulesSize sizeof(PythonModules) / sizeof(PythonModule) + +CCriticalSection CPythonInvoker::s_critical; + +static const std::string getListOfAddonClassesAsString( + XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook>& languageHook) +{ + std::string message; + std::unique_lock<CCriticalSection> l(*(languageHook.get())); + const std::set<XBMCAddon::AddonClass*>& acs = languageHook->GetRegisteredAddonClasses(); + bool firstTime = true; + for (const auto& iter : acs) + { + if (!firstTime) + message += ","; + else + firstTime = false; + message += iter->GetClassname(); + } + + return message; +} + +CPythonInvoker::CPythonInvoker(ILanguageInvocationHandler* invocationHandler) + : ILanguageInvoker(invocationHandler), m_threadState(NULL), m_stop(false) +{ +} + +CPythonInvoker::~CPythonInvoker() +{ + // nothing to do for the default invoker used for registration with the + // CScriptInvocationManager + if (GetId() < 0) + return; + + if (GetState() < InvokerStateExecutionDone) + CLog::Log(LOGDEBUG, "CPythonInvoker({}): waiting for python thread \"{}\" to stop", GetId(), + (!m_sourceFile.empty() ? m_sourceFile : "unknown script")); + Stop(true); + pulseGlobalEvent(); + + onExecutionFinalized(); +} + +bool CPythonInvoker::Execute( + const std::string& script, + const std::vector<std::string>& arguments /* = std::vector<std::string>() */) +{ + if (script.empty()) + return false; + + if (!CFileUtils::Exists(script)) + { + CLog::Log(LOGERROR, "CPythonInvoker({}): python script \"{}\" does not exist", GetId(), + CSpecialProtocol::TranslatePath(script)); + return false; + } + + if (!onExecutionInitialized()) + return false; + + return ILanguageInvoker::Execute(script, arguments); +} + +bool CPythonInvoker::execute(const std::string& script, const std::vector<std::string>& arguments) +{ + std::vector<std::wstring> w_arguments; + for (const auto& argument : arguments) + { + std::wstring w_argument; + g_charsetConverter.utf8ToW(argument, w_argument); + w_arguments.push_back(w_argument); + } + return execute(script, w_arguments); +} + +bool CPythonInvoker::execute(const std::string& script, std::vector<std::wstring>& arguments) +{ + // copy the code/script into a local string buffer + m_sourceFile = script; + std::set<std::string> pythonPath; + + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): start processing", GetId(), m_sourceFile); + + std::string realFilename(CSpecialProtocol::TranslatePath(m_sourceFile)); + std::string scriptDir = URIUtils::GetDirectory(realFilename); + URIUtils::RemoveSlashAtEnd(scriptDir); + + // set m_threadState if it's not set. + PyThreadState* l_threadState = nullptr; + bool newInterp = false; + { + if (!m_threadState) + { +#if PY_VERSION_HEX < 0x03070000 + // this is a TOTAL hack. We need the GIL but we need to borrow a PyThreadState in order to get it + // as of Python 3.2 since PyEval_AcquireLock is deprecated + extern PyThreadState* savestate; + PyEval_RestoreThread(savestate); +#else + PyThreadState* ts = PyInterpreterState_ThreadHead(PyInterpreterState_Main()); + PyEval_RestoreThread(ts); +#endif + l_threadState = Py_NewInterpreter(); + PyEval_ReleaseThread(l_threadState); + if (l_threadState == NULL) + { + CLog::Log(LOGERROR, "CPythonInvoker({}, {}): FAILED to get thread m_threadState!", GetId(), + m_sourceFile); + return false; + } + newInterp = true; + } + else + l_threadState = m_threadState; + } + + // get the GIL + PyEval_RestoreThread(l_threadState); + if (newInterp) + { + m_languageHook = new XBMCAddon::Python::PythonLanguageHook(l_threadState->interp); + m_languageHook->RegisterMe(); + + onInitialization(); + setState(InvokerStateInitialized); + + if (realFilename == m_sourceFile) + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): the source file to load is \"{}\"", GetId(), + m_sourceFile, m_sourceFile); + else + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): the source file to load is \"{}\" (\"{}\")", + GetId(), m_sourceFile, m_sourceFile, realFilename); + + // get path from script file name and add python path's + // this is used for python so it will search modules from script path first + pythonPath.emplace(scriptDir); + + // add all addon module dependencies to path + if (m_addon) + { + std::set<std::string> paths; + getAddonModuleDeps(m_addon, paths); + for (const auto& it : paths) + pythonPath.emplace(it); + } + else + { // for backwards compatibility. + // we don't have any addon so just add all addon modules installed + CLog::Log( + LOGWARNING, + "CPythonInvoker({}): Script invoked without an addon. Adding all addon " + "modules installed to python path as fallback. This behaviour will be removed in future " + "version.", + GetId()); + ADDON::VECADDONS addons; + CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::SCRIPT_MODULE); + for (unsigned int i = 0; i < addons.size(); ++i) + pythonPath.emplace(CSpecialProtocol::TranslatePath(addons[i]->LibPath())); + } + + PyObject* sysPath = PySys_GetObject("path"); + + std::for_each(pythonPath.crbegin(), pythonPath.crend(), + [&sysPath](const auto& path) + { + PyObject* pyPath = PyUnicode_FromString(path.c_str()); + PyList_Insert(sysPath, 0, pyPath); + + Py_DECREF(pyPath); + }); + + CLog::Log(LOGDEBUG, "CPythonInvoker({}): full python path:", GetId()); + + Py_ssize_t pathListSize = PyList_Size(sysPath); + + for (Py_ssize_t index = 0; index < pathListSize; index++) + { + if (index == 0 && !pythonPath.empty()) + CLog::Log(LOGDEBUG, "CPythonInvoker({}): custom python path:", GetId()); + + if (index == static_cast<ssize_t>(pythonPath.size())) + CLog::Log(LOGDEBUG, "CPythonInvoker({}): default python path:", GetId()); + + PyObject* pyPath = PyList_GetItem(sysPath, index); + CLog::Log(LOGDEBUG, "CPythonInvoker({}): {}", GetId(), PyUnicode_AsUTF8(pyPath)); + } + + { // set the m_threadState to this new interp + std::unique_lock<CCriticalSection> lockMe(m_critical); + m_threadState = l_threadState; + } + } + else + // swap in my thread m_threadState + PyThreadState_Swap(m_threadState); + + PyObject* sysArgv = PyList_New(0); + + if (arguments.empty()) + arguments.emplace_back(L""); + + CLog::Log(LOGDEBUG, "CPythonInvoker({}): adding args:", GetId()); + + for (const auto& arg : arguments) + { + PyObject* pyArg = PyUnicode_FromWideChar(arg.c_str(), arg.length()); + PyList_Append(sysArgv, pyArg); + CLog::Log(LOGDEBUG, "CPythonInvoker({}): {}", GetId(), PyUnicode_AsUTF8(pyArg)); + + Py_DECREF(pyArg); + } + + PySys_SetObject("argv", sysArgv); + + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): entering source directory {}", GetId(), m_sourceFile, + scriptDir); + PyObject* module = PyImport_AddModule("__main__"); + PyObject* moduleDict = PyModule_GetDict(module); + + // we need to check if we was asked to abort before we had inited + bool stopping = false; + { + GilSafeSingleLock lock(m_critical); + stopping = m_stop; + } + + bool failed = false; + std::string exceptionType, exceptionValue, exceptionTraceback; + if (!stopping) + { + try + { + // run script from file + // We need to have python open the file because on Windows the DLL that python + // is linked against may not be the DLL that xbmc is linked against so + // passing a FILE* to python from an fopen has the potential to crash. + + PyObject* pyRealFilename = Py_BuildValue("s", realFilename.c_str()); + FILE* fp = _Py_fopen_obj(pyRealFilename, "rb"); + Py_DECREF(pyRealFilename); + + if (fp != NULL) + { + PyObject* f = PyUnicode_FromString(realFilename.c_str()); + PyDict_SetItemString(moduleDict, "__file__", f); + + onPythonModuleInitialization(moduleDict); + + Py_DECREF(f); + setState(InvokerStateRunning); + XBMCAddon::Python::PyContext + pycontext; // this is a guard class that marks this callstack as being in a python context + executeScript(fp, realFilename, moduleDict); + } + else + CLog::Log(LOGERROR, "CPythonInvoker({}, {}): {} not found!", GetId(), m_sourceFile, + m_sourceFile); + } + catch (const XbmcCommons::Exception& e) + { + setState(InvokerStateFailed); + e.LogThrowMessage(); + failed = true; + } + catch (...) + { + setState(InvokerStateFailed); + CLog::Log(LOGERROR, "CPythonInvoker({}, {}): failure in script", GetId(), m_sourceFile); + failed = true; + } + } + + m_systemExitThrown = false; + InvokerState stateToSet; + if (!failed && !PyErr_Occurred()) + { + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script successfully run", GetId(), m_sourceFile); + stateToSet = InvokerStateScriptDone; + onSuccess(); + } + else if (PyErr_ExceptionMatches(PyExc_SystemExit)) + { + m_systemExitThrown = true; + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script aborted", GetId(), m_sourceFile); + stateToSet = InvokerStateFailed; + onAbort(); + } + else + { + stateToSet = InvokerStateFailed; + + // if it failed with an exception we already logged the details + if (!failed) + { + PythonBindings::PythonToCppException* e = NULL; + if (PythonBindings::PythonToCppException::ParsePythonException(exceptionType, exceptionValue, + exceptionTraceback)) + e = new PythonBindings::PythonToCppException(exceptionType, exceptionValue, + exceptionTraceback); + else + e = new PythonBindings::PythonToCppException(); + + e->LogThrowMessage(); + delete e; + } + + onError(exceptionType, exceptionValue, exceptionTraceback); + } + + std::unique_lock<CCriticalSection> lock(m_critical); + // no need to do anything else because the script has already stopped + if (failed) + { + setState(stateToSet); + return true; + } + + if (m_threadState) + { + // make sure all sub threads have finished + for (PyThreadState* old = nullptr; m_threadState != nullptr;) + { + PyThreadState* s = PyInterpreterState_ThreadHead(m_threadState->interp); + for (; s && s == m_threadState;) + s = PyThreadState_Next(s); + + if (!s) + break; + + if (old != s) + { + CLog::Log(LOGINFO, "CPythonInvoker({}, {}): waiting on thread {}", GetId(), m_sourceFile, + (uint64_t)s->thread_id); + old = s; + } + + lock.unlock(); + CPyThreadState pyState; + KODI::TIME::Sleep(100ms); + pyState.Restore(); + lock.lock(); + } + } + + // pending calls must be cleared out + XBMCAddon::RetardedAsyncCallbackHandler::clearPendingCalls(m_threadState); + + assert(m_threadState != nullptr); + PyEval_ReleaseThread(m_threadState); + + setState(stateToSet); + + return true; +} + +void CPythonInvoker::executeScript(FILE* fp, const std::string& script, PyObject* moduleDict) +{ + if (fp == NULL || script.empty() || moduleDict == NULL) + return; + + int m_Py_file_input = Py_file_input; + PyRun_FileExFlags(fp, script.c_str(), m_Py_file_input, moduleDict, moduleDict, 1, NULL); +} + +FILE* CPythonInvoker::PyFile_AsFileWithMode(PyObject* py_file, const char* mode) +{ + PyObject* ret = PyObject_CallMethod(py_file, "flush", ""); + if (ret == NULL) + return NULL; + Py_DECREF(ret); + + int fd = PyObject_AsFileDescriptor(py_file); + if (fd == -1) + return NULL; + + FILE* f = fdopen(fd, mode); + if (f == NULL) + { + PyErr_SetFromErrno(PyExc_OSError); + return NULL; + } + + return f; +} + +bool CPythonInvoker::stop(bool abort) +{ + std::unique_lock<CCriticalSection> lock(m_critical); + m_stop = true; + + if (!IsRunning() && !m_threadState) + return false; + + if (m_threadState != NULL) + { + if (IsRunning()) + { + setState(InvokerStateStopping); + lock.unlock(); + + PyEval_RestoreThread((PyThreadState*)m_threadState); + + //tell xbmc.Monitor to call onAbortRequested() + if (m_addon) + { + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): trigger Monitor abort request", GetId(), + m_sourceFile); + AbortNotification(); + } + + PyEval_ReleaseThread(m_threadState); + } + else + //Release the lock while waiting for threads to finish + lock.unlock(); + + XbmcThreads::EndTime<> timeout(PYTHON_SCRIPT_TIMEOUT); + while (!m_stoppedEvent.Wait(15ms)) + { + if (timeout.IsTimePast()) + { + CLog::Log(LOGERROR, + "CPythonInvoker({}, {}): script didn't stop in {} seconds - let's kill it", + GetId(), m_sourceFile, + std::chrono::duration_cast<std::chrono::seconds>(PYTHON_SCRIPT_TIMEOUT).count()); + break; + } + + // We can't empty-spin in the main thread and expect scripts to be able to + // dismantle themselves. Python dialogs aren't normal XBMC dialogs, they rely + // on TMSG_GUI_PYTHON_DIALOG messages, so pump the message loop. + if (CServiceBroker::GetAppMessenger()->IsProcessThread()) + { + CServiceBroker::GetAppMessenger()->ProcessMessages(); + } + } + + lock.lock(); + + setState(InvokerStateExecutionDone); + + // Useful for add-on performance metrics + if (!timeout.IsTimePast()) + CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script termination took {}ms", GetId(), + m_sourceFile, (PYTHON_SCRIPT_TIMEOUT - timeout.GetTimeLeft()).count()); + + // Since we released the m_critical it's possible that the state is cleaned up + // so we need to recheck for m_threadState == NULL + if (m_threadState != NULL) + { + { + // grabbing the PyLock while holding the m_critical is asking for a deadlock + CSingleExit ex2(m_critical); + PyEval_RestoreThread((PyThreadState*)m_threadState); + } + + + PyThreadState* state = PyInterpreterState_ThreadHead(m_threadState->interp); + while (state) + { + // Raise a SystemExit exception in python threads + Py_XDECREF(state->async_exc); + state->async_exc = PyExc_SystemExit; + Py_XINCREF(state->async_exc); + state = PyThreadState_Next(state); + } + + // If a dialog entered its doModal(), we need to wake it to see the exception + pulseGlobalEvent(); + + PyEval_ReleaseThread(m_threadState); + } + lock.unlock(); + + setState(InvokerStateFailed); + } + + return true; +} + +// Always called from Invoker thread +void CPythonInvoker::onExecutionDone() +{ + std::unique_lock<CCriticalSection> lock(m_critical); + if (m_threadState != NULL) + { + CLog::Log(LOGDEBUG, "{}({}, {})", __FUNCTION__, GetId(), m_sourceFile); + + PyEval_RestoreThread(m_threadState); + + onDeinitialization(); + + // run the gc before finishing + // + // if the script exited by throwing a SystemExit exception then going back + // into the interpreter causes this python bug to get hit: + // http://bugs.python.org/issue10582 + // and that causes major failures. So we are not going to go back in + // to run the GC if that's the case. + if (!m_stop && m_languageHook->HasRegisteredAddonClasses() && !m_systemExitThrown && + PyRun_SimpleString(GC_SCRIPT) == -1) + CLog::Log(LOGERROR, + "CPythonInvoker({}, {}): failed to run the gc to clean up after running prior to " + "shutting down the Interpreter", + GetId(), m_sourceFile); + + Py_EndInterpreter(m_threadState); + + // If we still have objects left around, produce an error message detailing what's been left behind + if (m_languageHook->HasRegisteredAddonClasses()) + CLog::Log(LOGWARNING, + "CPythonInvoker({}, {}): the python script \"{}\" has left several " + "classes in memory that we couldn't clean up. The classes include: {}", + GetId(), m_sourceFile, m_sourceFile, getListOfAddonClassesAsString(m_languageHook)); + + // unregister the language hook + m_languageHook->UnregisterMe(); + +#if PY_VERSION_HEX < 0x03070000 + PyEval_ReleaseLock(); +#else + PyThreadState_Swap(PyInterpreterState_ThreadHead(PyInterpreterState_Main())); + PyEval_SaveThread(); +#endif + + // set stopped event - this allows ::stop to run and kill remaining threads + // this event has to be fired without holding m_critical + // also the GIL (PyEval_AcquireLock) must not be held + // if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!) + m_stoppedEvent.Set(); + + m_threadState = nullptr; + + setState(InvokerStateExecutionDone); + } + ILanguageInvoker::onExecutionDone(); +} + +void CPythonInvoker::onExecutionFailed() +{ + PyEval_SaveThread(); + + setState(InvokerStateFailed); + CLog::Log(LOGERROR, "CPythonInvoker({}, {}): abnormally terminating python thread", GetId(), + m_sourceFile); + + std::unique_lock<CCriticalSection> lock(m_critical); + m_threadState = NULL; + + ILanguageInvoker::onExecutionFailed(); +} + +void CPythonInvoker::onInitialization() +{ + XBMC_TRACE; + { + GilSafeSingleLock lock(s_critical); + initializeModules(getModules()); + } + + // get a possible initialization script + const char* runscript = getInitializationScript(); + if (runscript != NULL && strlen(runscript) > 0) + { + // redirecting default output to debug console + if (PyRun_SimpleString(runscript) == -1) + CLog::Log(LOGFATAL, "CPythonInvoker({}, {}): initialize error", GetId(), m_sourceFile); + } +} + +void CPythonInvoker::onPythonModuleInitialization(void* moduleDict) +{ + if (m_addon.get() == NULL || moduleDict == NULL) + return; + + PyObject* moduleDictionary = (PyObject*)moduleDict; + + PyObject* pyaddonid = PyUnicode_FromString(m_addon->ID().c_str()); + PyDict_SetItemString(moduleDictionary, "__xbmcaddonid__", pyaddonid); + + ADDON::CAddonVersion version = m_addon->GetDependencyVersion("xbmc.python"); + PyObject* pyxbmcapiversion = PyUnicode_FromString(version.asString().c_str()); + PyDict_SetItemString(moduleDictionary, "__xbmcapiversion__", pyxbmcapiversion); + + PyObject* pyinvokerid = PyLong_FromLong(GetId()); + PyDict_SetItemString(moduleDictionary, "__xbmcinvokerid__", pyinvokerid); + + CLog::Log(LOGDEBUG, + "CPythonInvoker({}, {}): instantiating addon using automatically obtained id of \"{}\" " + "dependent on version {} of the xbmc.python api", + GetId(), m_sourceFile, m_addon->ID(), version.asString()); +} + +void CPythonInvoker::onDeinitialization() +{ + XBMC_TRACE; +} + +void CPythonInvoker::onError(const std::string& exceptionType /* = "" */, + const std::string& exceptionValue /* = "" */, + const std::string& exceptionTraceback /* = "" */) +{ + CPyThreadState releaseGil; + std::unique_lock<CCriticalSection> gc(CServiceBroker::GetWinSystem()->GetGfxContext()); + + CGUIDialogKaiToast* pDlgToast = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>( + WINDOW_DIALOG_KAI_TOAST); + if (pDlgToast != NULL) + { + std::string message; + if (m_addon && !m_addon->Name().empty()) + message = StringUtils::Format(g_localizeStrings.Get(2102), m_addon->Name()); + else + message = g_localizeStrings.Get(2103); + pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, message, g_localizeStrings.Get(2104)); + } +} + +void CPythonInvoker::initializeModules( + const std::map<std::string, PythonModuleInitialization>& modules) +{ + for (const auto& module : modules) + { + if (!initializeModule(module.second)) + CLog::Log(LOGWARNING, "CPythonInvoker({}, {}): unable to initialize python module \"{}\"", + GetId(), m_sourceFile, module.first); + } +} + +bool CPythonInvoker::initializeModule(PythonModuleInitialization module) +{ + if (module == NULL) + return false; + + return module() != nullptr; +} + +void CPythonInvoker::getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths) +{ + for (const auto& it : addon->GetDependencies()) + { + //Check if dependency is a module addon + ADDON::AddonPtr dependency; + if (CServiceBroker::GetAddonMgr().GetAddon(it.id, dependency, ADDON::AddonType::SCRIPT_MODULE, + ADDON::OnlyEnabled::CHOICE_YES)) + { + std::string path = CSpecialProtocol::TranslatePath(dependency->LibPath()); + if (paths.find(path) == paths.end()) + { + // add it and its dependencies + paths.insert(path); + getAddonModuleDeps(dependency, paths); + } + } + } +} diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h new file mode 100644 index 0000000..dd093ed --- /dev/null +++ b/xbmc/interfaces/python/PythonInvoker.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2013-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/generic/ILanguageInvoker.h" +#include "interfaces/legacy/Addon.h" +#include "interfaces/python/LanguageHook.h" +#include "threads/CriticalSection.h" +#include "threads/Event.h" + +#include <map> +#include <string> +#include <vector> + +typedef struct _object PyObject; + +class CPythonInvoker : public ILanguageInvoker +{ +public: + explicit CPythonInvoker(ILanguageInvocationHandler* invocationHandler); + ~CPythonInvoker() override; + + bool Execute(const std::string& script, + const std::vector<std::string>& arguments = std::vector<std::string>()) override; + + bool IsStopping() const override { return m_stop || ILanguageInvoker::IsStopping(); } + + typedef PyObject* (*PythonModuleInitialization)(); + +protected: + // implementation of ILanguageInvoker + bool execute(const std::string& script, const std::vector<std::string>& arguments) override; + virtual void executeScript(FILE* fp, const std::string& script, PyObject* moduleDict); + bool stop(bool abort) override; + void onExecutionDone() override; + void onExecutionFailed() override; + + // custom virtual methods + virtual std::map<std::string, PythonModuleInitialization> getModules() const = 0; + virtual const char* getInitializationScript() const = 0; + virtual void onInitialization(); + // actually a PyObject* but don't wanna draw Python.h include into the header + virtual void onPythonModuleInitialization(void* moduleDict); + virtual void onDeinitialization(); + + virtual void onSuccess() {} + virtual void onAbort() {} + virtual void onError(const std::string& exceptionType = "", + const std::string& exceptionValue = "", + const std::string& exceptionTraceback = ""); + + std::string m_sourceFile; + CCriticalSection m_critical; + +private: + void initializeModules(const std::map<std::string, PythonModuleInitialization>& modules); + bool initializeModule(PythonModuleInitialization module); + void getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths); + bool execute(const std::string& script, std::vector<std::wstring>& arguments); + FILE* PyFile_AsFileWithMode(PyObject* py_file, const char* mode); + + PyThreadState* m_threadState; + bool m_stop; + CEvent m_stoppedEvent; + + XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> m_languageHook; + bool m_systemExitThrown = false; + + static CCriticalSection s_critical; +}; diff --git a/xbmc/interfaces/python/PythonSwig.cpp.template b/xbmc/interfaces/python/PythonSwig.cpp.template new file mode 100644 index 0000000..24756ea --- /dev/null +++ b/xbmc/interfaces/python/PythonSwig.cpp.template @@ -0,0 +1,942 @@ +<% +/* + * 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. + */ +%> +<% +import Helper +import SwigTypeParser +import PythonTools + +import groovy.xml.XmlUtil +import groovy.text.SimpleTemplateEngine +import java.util.regex.Pattern + +/** + * All of the method nodes and all of the class nodes are used several + * times over, so they are pulled out once here. + */ + +// --------------------------------------------------------- +// initialize the SwigTypeParser with the module's typetables +module.findAll( { it.name() == 'typetab' } ).each { SwigTypeParser.appendTypeTable(it) } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Flatten out all of the method/function nodes, whether inside +// classes or not, into 'methods' +List methods = module.depthFirst().findAll { it.name() == 'function' || it.name() == 'constructor' || it.name() == 'destructor' } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Flatten out all of the class nodes into 'classes' +List classes = module.depthFirst().findAll { it.name() == 'class' } +// --------------------------------------------------------- + +// --------------------------------------------------------- +// Initialize the Helper with the type conversions +Helper.setup(this,classes, + /** + * This is meant to contain mini-templates for converting the return type + * of the native call to be returned to the python caller. + */ + [ 'void' : 'Py_INCREF(Py_None);\n ${result} = Py_None;', + 'long': '${result} = PyLong_FromLong(${api});', + 'unsigned long': '${result} = PyLong_FromLong(${api});', + 'bool': '${result} = ${api} ? Py_True : Py_False; Py_INCREF(${result});', + 'long long': '${result} = Py_BuildValue("L", ${api});', + 'int': '${result} = Py_BuildValue("i", ${api});', + 'unsigned int': '${result} = Py_BuildValue("I", ${api});', + 'double': '${result} = PyFloat_FromDouble(${api});', + 'float': '${result} = Py_BuildValue("f", static_cast<double>(${api}));', + 'std::string' : new File('typemaps/python.string.outtm'), + 'p.q(const).char' : '${result} = PyUnicode_FromString(${api});', + (Pattern.compile('''(p.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.outtm'), + (Pattern.compile('''std::shared_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'), + (Pattern.compile('''std::unique_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'), + (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.outtm'), + (Pattern.compile('''(p.){0,1}Tuple<\\(.*\\)>''')) : new File('typemaps/python.Tuple.outtm'), + (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.outtm') + ], '${result} = makePythonInstance(${api},true);', + /** + * This is meant to contain mini-templates for converting the parameter types + * of the native call to be converted from the python types provided by the caller. + * + * Note: if the type can be handled by PythonTools.ltypeToFormatChar then it wont + * appear here as it gets converted directly within the PyArg_ParseTupleAndKeywords + * call. + */ + [ + 'std::string' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},false,"${api}","${method.@name}");', + (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.intm'), + (Pattern.compile('''(p.){0,1}Tuple(3){0,1}<\\(.*\\)>''')) : new File('typemaps/python.Tuple.intm'), + (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.intm'), + (Pattern.compile('''(r.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.intm'), + (Pattern.compile('''(p.){0,1}std::map<\\(.*\\)>''')) : new File('typemaps/python.map.intm'), + (Pattern.compile('''(r.){0,1}XBMCAddon::Dictionary<\\(.*\\)>''')) : new File('typemaps/python.dict.intm'), + (Pattern.compile('''p.void''')) : '${api} = (void*)${slarg};', + 'bool' : '${api} = (PyLong_AsLong(${slarg}) == 0L ? false : true);', + 'long' : '${api} = PyLong_AsLong(${slarg});', + 'unsigned long' : '${api} = PyLong_AsUnsignedLong(${slarg});', + 'long long' : '${api} = PyLong_AsLongLong(${slarg});', + 'unsigned long long' : '${api} = PyLong_AsUnsignedLongLong(${slarg});', + 'int' : '${api} = (int)PyLong_AsLong(${slarg});', + 'double' : '${api} = PyFloat_AsDouble(${slarg});', + 'float' : '${api} = (float)PyFloat_AsDouble(${slarg});', + 'XBMCAddon::StringOrInt' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},PyLong_Check(${slarg}) || PyFloat_Check(${slarg}),"${api}","${method.@name}");' + ], '${api} = (${swigTypeParser.SwigType_str(ltype)})retrieveApiInstance(${slarg},"${ltype}","${helper.findNamespace(method)}","${helper.callingName(method)}");') +// --------------------------------------------------------- + +/*******************************************************************************/ +/** + * The doMethod will actually write out the CPython method call for + * the method/function represented by the provided Node ('method'). + */ +void doMethod(Node method, MethodType methodType) +{ + boolean isOperator = method.@name.startsWith("operator ") + boolean doAsMappingIndex = false + boolean doAsCallable = false + + if (isOperator) + { + if("[]" == method.@name.substring(9)) + doAsMappingIndex = true + else if("()" == method.@name.substring(9)) + doAsCallable = true + else + return; + } + + boolean constructor = methodType == MethodType.constructor + + // if we're a constructor, but we're private, then we're outta here + if (constructor && method.@access != null && method.@access != "public") + return + + boolean destructor = methodType == MethodType.destructor + List params = method?.parm + int numParams = params?.size() + String clazz = Helper.findFullClassName(method) + String returns = constructor ? 'p.' + clazz : (destructor ? 'void' : Helper.getReturnSwigType(method)) + Node classnode = Helper.findClassNode(method) + String classNameAsVariable = clazz == null ? null : PythonTools.getClassNameAsVariable(classnode) + boolean useKeywordParsing = !('true' == classnode?.@feature_python_nokwds || 'true' == method?.@feature_python_nokwds) + + // do the docs + if (!constructor && !destructor) + { + if (Helper.hasDoc(method)) + { +%> + PyDoc_STRVAR(${PythonTools.getPyMethodName(method,methodType)}__doc__, + ${PythonTools.makeDocString(method.doc[0])}); +<% } + } +%> + static <% if(methodType == MethodType.destructor) { %>void<% } else { %>PyObject*<% } %> ${module.@name}_${PythonTools.getPyMethodName(method,methodType)} (<%= ((clazz == null) ? "PyObject" : + (constructor ? "PyTypeObject" : 'PyHolder')) %>* ${constructor ? 'pytype' : 'self'} <% + if (doAsMappingIndex) { %>, PyObject* py${params[0].@name}<% } + else if (methodType != MethodType.destructor) { %> , PyObject *args, PyObject *kwds <%} %> ) + { + XBMC_TRACE; +<% if (numParams > 0) + { + if (useKeywordParsing && !doAsMappingIndex) + { %> + static const char *keywords[] = {<% + params.each { %> + "${it.@name}",<% } %> + NULL}; +<% } + params.each { +%> + ${SwigTypeParser.SwigType_str(SwigTypeParser.convertTypeToLTypeForParam(it.@type))} ${it.@name} ${it.@value != null ? ' = ' + it.@value : SwigTypeParser.SwigType_ispointer(it.@type) ? ' = nullptr' : ''};<% + if (!PythonTools.parameterCanBeUsedDirectly(it) && !doAsMappingIndex) + { %> + PyObject* py${it.@name} = NULL;<% + } + } + if (!doAsMappingIndex) + { %> + if (!${useKeywordParsing ? 'PyArg_ParseTupleAndKeywords' : 'PyArg_ParseTuple'}( + args, + <% if (useKeywordParsing) { %>kwds,<% } %> + "<%= PythonTools.makeFormatStringFromParameters(method) %>", + <% if (useKeywordParsing) { %>const_cast<char**>(keywords),<% } %><% params.eachWithIndex { param,i -> %> + &${PythonTools.parameterCanBeUsedDirectly(param) ? '' : 'py'}${param.@name}${i < params.size() - 1 ? "," : ""}<% } %> + )) + { + return NULL; + } + +<% } + } + // now actually invoke the method + if (returns != "void") { %> ${SwigTypeParser.SwigType_str(returns)} apiResult;<% } +%> + try + { +<% + // now do the input conversion if any are necessary + params.findAll({ !PythonTools.parameterCanBeUsedDirectly(it) || doAsMappingIndex }).each { %> ${Helper.getInConversion(it.@type, it.@name, 'py' + it.@name, method)} <% println() } +%> +<% + // check to see if this method is a call to a virtual function on a director class. + boolean isDirectorCall = Helper.isDirector(method) + if (isDirectorCall) + { +%> // This is a director call coming from python so it explicitly calls the base class method. +<% + } + // now do the method call itself + if (!destructor) { + if (constructor || !clazz) { %> XBMCAddon::SetLanguageHookGuard slhg(XBMCAddon::Python::PythonLanguageHook::GetIfExists(PyThreadState_Get()->interp).get());<% println() } +%> <% + if (returns != "void") { %>apiResult = <% } + if (clazz && !constructor) { + %>((${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(method)}","${clazz}"))-> <% + } + if (constructor && classnode.@feature_director) { + %>(&(Ty${classNameAsVariable}_Type.pythonType) != pytype) ? new ${classNameAsVariable}_Director(<% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) : <% } + + // Here is the actual call ... if this is a Director we need to do an upCall (from Python) + if (isDirectorCall){ %>${clazz}::<% } + %>${Helper.callingName(method)}( <% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %> ); +<% + if (constructor) { %> prepareForReturn(apiResult);<% } + } // close the 'if method is not a destructor' + else { // it is a destructor +%> + ${clazz}* theObj = (${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"~${Helper.callingName(method)}","${clazz}"); + cleanForDealloc(theObj); +<% + } +%> + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); <% + if (!destructor) { %> + return NULL; <% + } %> + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); <% + if (!destructor) { %> + return NULL; <% + } %> + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${Helper.callingName(method)}\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${Helper.callingName(method)}\""); <% + if (!destructor) { %> + return NULL; <% + } %> + } +<% + if (!destructor) { %> + PyObject* result = Py_None; + + // transform the result +<% + if (constructor) { + %> result = makePythonInstance(apiResult,pytype,false);<% + } + else { +%> ${Helper.getOutConversion(returns,'result',method)}<% + } + if (constructor && method.@feature_director) { %> + if (&(Ty${classNameAsVariable}_Type.pythonType) != pytype) + ((${classNameAsVariable}_Director*)apiResult)->setPyObjectForDirector(result);<% + } + %> + + return result; <% } + else { %> + (((PyObject*)(self))->ob_type)->tp_free((PyObject*)self); + <% + } + %> + } <% +} +/*******************************************************************************/ + +/** + * This method writes out the instance of a TypeInfo (which includes + * The PyTypeObject as a member) for the class node provided. + * + * If classNameAsVariable is not null then the class name as a + * variable will be appended to it. + */ +void doClassTypeInfo(Node clazz, List classNameAsVariables = null) +{ + String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz) + String fullClassName = Helper.findFullClassName(clazz) + classNameAsVariables?.add(classNameAsVariable) +%> + //========================================================================= + // These variables will hold the Python Type information for ${fullClassName} + TypeInfo Ty${classNameAsVariable}_Type(typeid(${fullClassName}));<% +%> + //========================================================================= +<% +} + +/** + * This method will take the name of an API class from another module and + * create an external reference to its TypeInfo instance. + */ +void doExternClassTypeInfo(String knownType) +{ + String classNameAsVariable = knownType.replaceAll('::','_') +%> + //========================================================================= + // These variables define the type ${knownType} from another module + extern TypeInfo Ty${classNameAsVariable}_Type; + //========================================================================= +<% +} + +/*******************************************************************************/ +/** + * This method takes the class node and outputs all of the python meta-data + * and class oddities (like comparators, as_mapping, etc.). These include: + * + * 1) comparator *_cmp python method as long as there's an operator==, an + * operator>, AND an operator<. + * 2) it will create a python "as_mapping" method as long as there's both + * an operator[], AND a .size() method on the class. + * 3) it will handle the explicitly defined rich compare (_rcmp) if the + * feature is included in the .i file using %feature("python:rcmp") + * 4) The array of PyMethodDefs for the class definition + * 5) It will handle public fields as if the were python properties by: + * a) Creating a get/set_member if there are read/write properties. + * b) Creating only a get if there are only read-only properties. + * 6) It will write the init[Classname] method for the class which will + * initialize the TypeInfo and PyTypeObject structs. + * + * If initTypeCalls is not null then the method name for the generated init + * method (see #6 above) will be appended to it. + */ +void doClassMethodInfo(Node clazz, List initTypeCalls) +{ + String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz) + String fullClassName = Helper.findFullClassName(clazz) + String initTypeCall = "initPy${classNameAsVariable}_Type" + initTypeCalls?.add(initTypeCall) + + // see if we have any valid (or invalid) operators + boolean doComparator = false + boolean doAsMapping = false + boolean hasEquivalenceOp = false + boolean hasLtOp = false + boolean hasGtOp = false + Node indexOp = null + Node callableOp = null + Node sizeNode = null + + List normalMethods = clazz.function.findAll { !it.@name.startsWith("operator ") } + List operators = clazz.function.findAll { it.@name.startsWith("operator ") } + List properties = clazz.variable.findAll { it.@access != null && it.@access == "public" } + List properties_set = properties.findAll { it.@feature_immutable == null || it.@feature_immutable == 0 } + + operators.each { + // we have an operator. The only one we can handle is == + if (it.@name.substring(9).startsWith("==")) + hasEquivalenceOp = true + else if (it.@name.substring(9) == "<") + hasLtOp = true + else if (it.@name.substring(9) == ">") + hasGtOp = true + else if (it.@name.substring(9) == "[]") + indexOp = it + else if (it.@name.substring(9) == "()") + callableOp = it + else + System.err.println ("Warning: class ${fullClassName} has an operator \"${it.@name}\" that is being ignored."); + } + + if (hasGtOp || hasLtOp || hasEquivalenceOp) + { + if (!(hasLtOp && hasGtOp && hasEquivalenceOp)) + System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a comparator you must implement all 3 operators >,<,==.") + else + doComparator = true + } + + if (indexOp) + { + sizeNode = clazz.function.find { it.@name == "size" } + if (sizeNode) + doAsMapping = true + else + System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a as_mapping you must implement 'size' as well as operator[]") + } + + if (doAsMapping) + { +%> + static Py_ssize_t ${module.@name}_${classNameAsVariable}_size_(PyObject* self) + { + return (Py_ssize_t)((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(indexOp)}","${fullClassName}"))-> size(); + } + + //========================================================================= + // tp_as_mapping struct for ${fullClassName} + //========================================================================= + PyMappingMethods ${module.@name}_${classNameAsVariable}_as_mapping = { + ${module.@name}_${classNameAsVariable}_size_, /* inquiry mp_length; __len__ */ + (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(indexOp,MethodType.method)}, /* binaryfunc mp_subscript __getitem__ */ + 0, /* objargproc mp_ass_subscript; __setitem__ */ + }; +<% + } + + if (clazz.@feature_python_rcmp) + { %> + static PyObject* ${module.@name}_${classNameAsVariable}_rcmp(PyObject* obj1, PyObject *obj2, int method) + ${Helper.unescape(clazz.@feature_python_rcmp)} +<% + } +%> + //========================================================================= + // This section contains the initialization for the + // Python extension for the Api class ${fullClassName} + //========================================================================= + // All of the methods on this class + static PyMethodDef ${classNameAsVariable}_methods[] = { <% + normalMethods.each { %> + {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% } + + // now do all of the explicit feature:python:method's that may be in this class + List tmpl = [] + tmpl.addAll(clazz.attributes().keySet()) + List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') } + newMethodKeys.each { key -> + String featureEntry = clazz.attribute(key) + String methodName = key.substring('feature_python_method_'.length()) %> + {"${methodName}", (PyCFunction)${module.@name}_${PythonTools.getClassNameAsVariable(clazz)}_${methodName}, METH_VARARGS|METH_KEYWORDS, NULL}, +<% + } +%> + {NULL, NULL, 0, NULL} + }; + +<% + if (properties.size() > 0) { +%> static PyObject* ${classNameAsVariable}_getMember(PyHolder *self, void *name) + { + if (self == NULL) + return NULL; +<% + String clazzName = Helper.findFullClassName(properties[0]) +%> + try + { + ${clazzName}* theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}"); + + PyObject* result = NULL; + <% + properties.each { + String returns = Helper.getPropertyReturnSwigType(it); +%> if (strcmp((char*)name, "${it.@sym_name}") == 0) + { + ${SwigTypeParser.SwigType_lstr(returns)} apiResult = theObj->${it.@sym_name}; + ${Helper.getOutConversion(returns, 'result', it)} + } + else<% + } %> + { + Py_INCREF(Py_None); + return Py_None; + } + + return result; + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + return NULL; + } + + return NULL; + } + +<% + if (properties_set.size() > 0) { +%> int ${classNameAsVariable}_setMember(PyHolder *self, PyObject *value, void *name) + { + if (self == NULL) + return -1; + + ${clazzName}* theObj = NULL; + try + { + theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}"); + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return -1; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return -1; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\""); + return -1; + } + +<% + properties_set.each { + String returns = Helper.getPropertyReturnSwigType(it); +%> if (strcmp((char*)name, "${it.@sym_name}") == 0) + { + ${SwigTypeParser.SwigType_lstr(returns)} tmp; + ${Helper.getInConversion(returns, 'tmp', 'value', it)} + if (PyErr_Occurred()) + throw PythonBindings::PythonToCppException(); + + theObj->${it.@sym_name} = tmp; + } + else<% + } %> + return -1; + + return 0; + } <% + } +%> + + // All of the methods on this class + static PyGetSetDef ${classNameAsVariable}_getsets[] = { <% + properties.each { %> + {(char*)"${it.@sym_name}", (getter)${classNameAsVariable}_getMember, ${(it.@feature_immutable == null || it.@feature_immutable == 0) ? '(setter)' + classNameAsVariable + '_setMember' : 'NULL'}, (char*)${Helper.hasDoc(it) ? PythonTools.makeDocString(it.doc[0]) : 'NULL'}, (char*)"${it.@sym_name}" }, <% } +%> + {NULL} + }; +<% + } + + if ((clazz.@feature_iterator && clazz.@feature_iterator != '') || + (clazz.@feature_iterable && clazz.@feature_iterable != '')) { %> + static PyObject* ${module.@name}_${classNameAsVariable}_iter(PyObject* self) + { <% + if (clazz.@feature_iterator) { %> + return self; <% + } + else { %> + PyObject* result = NULL; + try + { + ${clazz.@feature_iterable}* apiResult = ((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}"))->begin(); + + ${Helper.getOutConversion('p.' + clazz.@feature_iterable,'result',clazz)} + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + return NULL; + } + + return result; <% + } %> + } +<% + + if (clazz.@feature_iterator) { %> + static PyObject* ${module.@name}_${classNameAsVariable}_iternext(PyObject* self) + { + PyObject* result = NULL; + try + { + ${fullClassName}* iter = (${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}"); + + // check if we have reached the end + if (!iter->end()) + { + ++(*iter); + + ${clazz.@feature_iterator} apiResult = **iter; + ${Helper.getOutConversion(clazz.@feature_iterator,'result',clazz)} + } + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_TypeError, e.GetExMessage()); + return NULL; + } + catch (const XbmcCommons::Exception& e) + { + CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + return NULL; + } + catch (...) + { + CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\""); + return NULL; + } + + return result; + } +<% + } + } +%> + + // This method initializes the above mentioned Python Type structure + static void ${initTypeCall}() + { +<% + if (Helper.hasDoc(clazz)) + { +%> + PyDoc_STRVAR(${classNameAsVariable}__doc__, + ${PythonTools.makeDocString(clazz.doc[0])} + ); +<% } %> + + PyTypeObject& pythonType = Ty${classNameAsVariable}_Type.pythonType; + pythonType.tp_name = "${module.@name}.${clazz.@sym_name}"; + pythonType.tp_basicsize = sizeof(PyHolder); + pythonType.tp_dealloc = (destructor)${module.@name}_${classNameAsVariable}_Dealloc; <% + + if (clazz.@feature_python_rcmp) { %> + pythonType.tp_richcompare=(richcmpfunc)${module.@name}_${classNameAsVariable}_rcmp;<% + } %> + + pythonType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + + pythonType.tp_doc = ${Helper.hasDoc(clazz) ? (classNameAsVariable + '__doc__') : 'NULL' }; + pythonType.tp_methods = ${classNameAsVariable}_methods; <% + if (properties.size() > 0) { %> + pythonType.tp_getset = ${classNameAsVariable}_getsets; +<% + } + if (callableOp) { %> + pythonType.tp_call = (ternaryfunc)${module.@name}_${PythonTools.getPyMethodName(callableOp,MethodType.method)}; +<% + } + if (doAsMapping) { %> + pythonType.tp_as_mapping = &${module.@name}_${classNameAsVariable}_as_mapping; +<% + } + + if (clazz.@feature_iterator) { %> + pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter; + pythonType.tp_iternext = (iternextfunc)${module.@name}_${classNameAsVariable}_iternext; +<% + } + else if (clazz.@feature_iterable && clazz.@feature_iterable != '') { %> + pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter; +<% + } + + Node baseclass = PythonTools.findValidBaseClass(clazz, module) +%> + + pythonType.tp_base = ${baseclass ? ('&(Ty' + PythonTools.getClassNameAsVariable(baseclass) + '_Type.pythonType)') : "NULL"}; + pythonType.tp_new = <% !Helper.hasDefinedConstructor(clazz) || Helper.hasHiddenConstructor(clazz) ? print('NULL') : print("${module.@name}_${classNameAsVariable}_New") %>; + pythonType.tp_init = dummy_tp_init; + + Ty${classNameAsVariable}_Type.swigType="p.${fullClassName}";<% + if (baseclass) { %> + Ty${classNameAsVariable}_Type.parentType=&Ty${PythonTools.getClassNameAsVariable(baseclass)}_Type; +<%} + + if (!Helper.hasHiddenConstructor(clazz)) { %> + registerAddonClassTypeInformation(&Ty${classNameAsVariable}_Type); +<%} %> + } + //========================================================================= +<% +} +/*******************************************************************************/ + + +List getAllVirtualMethods(Node clazz) +{ + List ret = [] + ret.addAll(clazz.findAll({ it.name() == 'function' && it.@storage && it.@storage == 'virtual' })) + if (clazz.baselist) { + if (clazz.baselist[0].base) clazz.baselist[0].base.each { + Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz) + if (baseclassnode && baseclassnode.@feature_director) ret.addAll(getAllVirtualMethods(baseclassnode)) + } + } + return ret; +} + +%> +/* + * 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. + */ + +// ************************************************************************ +// This file was generated by xbmc compile process. DO NOT EDIT!! +// It was created by running the code generator on the spec file for +// the module "${module.@name}" on the template file PythonSwig.template.cpp +// ************************************************************************ + +<% +Helper.getInsertNodes(module, 'begin').each { %>${Helper.unescape(it)}<% } +%> + +#include <Python.h> +#include <string> +#include "CompileInfo.h" +#include "interfaces/python/LanguageHook.h" +#include "interfaces/python/swig.h" +#include "interfaces/python/PyContext.h" + +<% +Helper.getInsertNodes(module, 'header').each { %>${Helper.unescape(it)}<% } +%> + +namespace PythonBindings +{ +<% + // initTypeCalls is the + List initTypeCalls = [] + List classNameAsVariables = [] + + classes.each { clazz -> doClassTypeInfo(clazz, classNameAsVariables) } + + // make sure known api types are declared as externs + + // first, find all of the declared known api types + Set<String> knownApiTypes = new HashSet<String>() + module.depthFirst().each + { + String attr = it.attribute('feature_knownapitypes') + if (attr != null) + { + attr.trim().split(',').each { knownApiTypes.add(it) } + } + } + + // now declare an extern for each one + knownApiTypes.each { doExternClassTypeInfo(it) } + +%> + +<% +//========================================================================= +// Do the directors. For every class that can be extended in python, we +// need to create a Director instance with bridging calls. This chunk of +// code will generate those classes. + classes.findAll({ it.@feature_director != null }).each { clazz -> + // find the constructor for this class + constructor = clazz.constructor[0] +%> + //========================================================================= + // This class is the Director for ${Helper.findFullClassName(clazz)}. + // It provides the "reverse bridge" from C++ to Python to support + // cross-language polymorphism. + //========================================================================= + class ${PythonTools.getClassNameAsVariable(clazz)}_Director : public Director, public ${clazz.@name} + { + public: +<% + if (constructor) + {%> + inline ${PythonTools.getClassNameAsVariable(clazz)}_Director(<% + List params = constructor?.parm + params.eachWithIndex { param, i -> %>${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% } + %>) : ${Helper.findFullClassName(constructor)}(<% + params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) { } <% + } +%> +<% + getAllVirtualMethods(clazz).each + { %> + virtual ${SwigTypeParser.SwigType_str(Helper.getReturnSwigType(it))} ${Helper.callingName(it)}( <% + List params = it?.parm + String paramFormatStr = '' + params.each { paramFormatStr += 'O' } + params.eachWithIndex { param, i -> %> ${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% } + %> ) + { <% + params.each + { param -> + %> + PyObject* py${param.@name} = NULL; + ${Helper.getOutConversion(param.@type,'result',it,['result' : 'py' + param.@name, 'api' : param.@name])}<% + } +%> + XBMCAddon::Python::PyContext pyContext; + PyObject_CallMethod(self,"${Helper.callingName(it)}","(${paramFormatStr})"<% + params.each { + %>, py${it.@name} <% + } + %>); + if (PyErr_Occurred()) + throw PythonBindings::PythonToCppException(); + } +<% } + +%> + }; +<% + } +//========================================================================= + + // types used as method parameter or return values need to be declared + // as extern if they are unknown types. + methods.each { if (it.name() != 'destructor') { doMethod(it, (it.name() == 'constructor' ? MethodType.constructor : MethodType.method)); println(); } } + classes.each { clazz -> doMethod(clazz, MethodType.destructor) } + + // now find any methods that have been added explicitly + classes.each { node -> + List tmpl = [] + tmpl.addAll(node.attributes().keySet()) + List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') } + newMethodKeys.each { key -> + String featureEntry = node.attribute(key) + String methodName = key.substring('feature_python_method_'.length()) %> + static PyObject* ${module.@name}_${PythonTools.getClassNameAsVariable(node)}_${methodName}(PyObject* self, PyObject *args, PyObject *kwds) + ${Helper.unescape(featureEntry)} +<% + } + } + + classes.each { clazz -> doClassMethodInfo(clazz, initTypeCalls) } + +%> + + static PyMethodDef ${module.@name}_methods[] = { <% + module.depthFirst().findAll({ it.name() == 'function' && Helper.parents(it, { Node lnode -> lnode.name() == 'class'}).size() == 0 }).each { %> + {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% } +%> + {NULL, NULL, 0, NULL} + }; + + // This is the call that will call all of the other initializes + // for all of the classes in this module + static void initTypes() + { + static bool typesAlreadyInitialized = false; + if (!typesAlreadyInitialized) + { + typesAlreadyInitialized = true; +<% + initTypeCalls.each { %> + ${it}();<% + } + + classNameAsVariables.each { %> + if (PyType_Ready(&(Ty${it}_Type.pythonType)) < 0) + return;<% + }%> + } + } + + static struct PyModuleDef createModule + { + PyModuleDef_HEAD_INIT, + "${module.@name}", + "", + -1, + ${module.@name}_methods, + nullptr, + nullptr, + nullptr, + nullptr, + }; + + PyObject *PyInit_Module_${module.@name}() + { + initTypes(); + + // init general ${module.@name} modules + PyObject* module; + +<% classNameAsVariables.each { %> + Py_INCREF(&(Ty${it}_Type.pythonType));<% + }%> + + module = PyModule_Create(&createModule); + if (module == NULL) return NULL; + +<% classes.each { clazz -> %> + PyModule_AddObject(module, "${clazz.@sym_name}", (PyObject*)(&(Ty${PythonTools.getClassNameAsVariable(clazz)}_Type.pythonType)));<% + }%> + + // constants + PyModule_AddStringConstant(module, "__author__", "Team Kodi <http://kodi.tv>"); + PyModule_AddStringConstant(module, "__date__", CCompileInfo::GetBuildDate().c_str()); + PyModule_AddStringConstant(module, "__version__", "3.0.1"); + PyModule_AddStringConstant(module, "__credits__", "Team Kodi"); + PyModule_AddStringConstant(module, "__platform__", "ALL"); + + // need to handle constants + // #define constants +<% module.depthFirst().findAll( { it.name() == 'constant'} ).each { + String pyCall = + (it.@type == 'int' || it.@type == 'long' || it.@type == 'unsigned int' || it.@type == 'unsigned long' || it.@type == 'bool') ? + 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %> + ${pyCall}(module,"${it.@sym_name}",${it.@value}); <% + } %> + // constexpr constants +<% module.depthFirst().findAll( { it.name() == 'variable' && it.@storage && it.@storage == "constexpr" && !it.@error } ).each { + String pyCall = + ( it.@type == 'q(const).int' || it.@type == 'q(const).long' || it.@type == 'q(const).unsigned int' || it.@type == 'q(const).unsigned long' || it.@type == 'q(const).bool' ) ? + 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %> + ${pyCall}(module,"${it.@sym_name}",${it.@value}); <% + } %> + return module; + } + +} // end PythonBindings namespace for python type definitions + +<% +Helper.getInsertNodes(module, 'footer').each { %>${Helper.unescape(it)}<% } +%> diff --git a/xbmc/interfaces/python/PythonTools.groovy b/xbmc/interfaces/python/PythonTools.groovy new file mode 100644 index 0000000..e41db6c --- /dev/null +++ b/xbmc/interfaces/python/PythonTools.groovy @@ -0,0 +1,140 @@ +/* + * 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. + */ + +import Helper +import SwigTypeParser + +public class PythonTools +{ + /** + * This array defines a mapping of the api spec type to the python parse format character. + * By default, if a lookup here results in 'null' then the format char is 'O' + */ + private static Map ltypeToFormatChar = [ + 'p.char':"s", bool:"b", + int:"i", 'unsigned int' : 'I', + long:"l", 'unsigned long' : 'k', + 'double':"d", 'float':"f", + 'long long' : "L" + ] + + /** + * if the parameter can be directly read from python then its type should be in the ltypeToFormatChar + * otherwise we need an intermediate pyobject + */ + public static boolean parameterCanBeUsedDirectly(Node param) { return ltypeToFormatChar[SwigTypeParser.convertTypeToLTypeForParam(param.@type)] != null } + + /** + * This method will take the parameter list from the method node passed + * and will convert it to a Python argument string for PyArg_ParseTupleAndKeywords + */ + public static String makeFormatStringFromParameters(Node method) + { + if (!method) + return '' + List params = method.parm + String format = "" + boolean previousDefaulted = false + params.eachWithIndex { param, i -> + String defaultValue = param.@value + String paramtype = SwigTypeParser.convertTypeToLTypeForParam(param.@type) + String curFormat = ltypeToFormatChar[paramtype]; + if (curFormat == null) // then we will assume it's an object + curFormat = "O"; + + if (defaultValue != null && !previousDefaulted) + { + format +="|" + previousDefaulted = true + } + format += curFormat + } + return format; + } + + /** + * This method gets the FULL class name as a variable including the + * namespace. If converts all of the '::' references to '_' so + * that the result can be used in part, or in whole, as a variable name + */ + public static String getClassNameAsVariable(Node clazz) { return Helper.findFullClassName(clazz).replaceAll('::','_') } + + public static String getPyMethodName(Node method, MethodType methodType) + { + String clazz = Helper.findFullClassName(method)?.replaceAll('::','_') + + // if we're not in a class then this must be a method node + assert (clazz != null || methodType == MethodType.method), 'Cannot use a non-class function as a constructor or destructor ' + method + + // it's ok to pass a 'class' node if the methodType is either constructor or destructor + assert (method.name() != 'class' || (methodType == MethodType.constructor || methodType == MethodType.destructor)) + + // if this is a constructor node then the methodtype best reflect that + assert (method.name() != 'constructor' || methodType == MethodType.constructor), 'Cannot use a constructor node and not identify the type as a constructor' + method + + // if this is a destructor node then the methodtype best reflect that + assert (method.name() != 'destructor' || methodType == MethodType.destructor), 'Cannot use a destructor node and not identify the type as a destructor' + method + + if (clazz == null) + return method.@sym_name + + if (methodType == MethodType.constructor) + return clazz + "_New" + + if (methodType == MethodType.destructor) + return clazz + "_Dealloc" + + if (method.@name.startsWith("operator ")) + { + if ("[]" == method.@name.substring(9)) + return clazz + "_operatorIndex_" + + if ("()" == method.@name.substring(9)) + return clazz + "_callable_" + } + + return clazz + "_" + method.@sym_name; + } + + public static String makeDocString(Node docnode) + { + if (docnode?.name() != 'doc') + throw new RuntimeException("Invalid doc Node passed to PythonTools.makeDocString (" + docnode + ")") + + String[] lines = (docnode.@value).split(Helper.newline) + def ret = '' + lines.eachWithIndex { val, index -> + val = ((val =~ /\\n/).replaceAll('')) // remove extraneous \n's + val = val.replaceAll("\\\\","\\\\\\\\") // escape backslash + val = ((val =~ /\"/).replaceAll("\\\\\"")) // escape quotes + ret += ('"' + val + '\\n"' + (index != lines.length - 1 ? Helper.newline : '')) + } + + return ret + } + + public static Node findValidBaseClass(Node clazz, Node module, boolean warn = false) + { + // I need to find the base type if there is a known class with it + assert clazz.baselist.size() < 2, "${clazz} has multiple baselists - need to write code to separate out the public one." + String baseclass = 'NULL' + List knownbases = [] + if (clazz.baselist) + { + if (clazz.baselist[0].base) clazz.baselist[0].base.each { + Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz) + if (baseclassnode) knownbases.add(baseclassnode) + else if (warn && !Helper.isKnownBaseType(it.@name,clazz)) + System.out.println("WARNING: the base class ${it.@name} for ${Helper.findFullClassName(clazz)} is unrecognized within ${module.@name}.") + } + } + assert knownbases.size() < 2, + "The class ${Helper.findFullClassName(clazz)} has too many known base classes. Multiple inheritance isn't supported in the code generator. Please \"#ifdef SWIG\" out all but one." + return knownbases.size() > 0 ? knownbases[0] : null + } +} diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp new file mode 100644 index 0000000..ee8ed93 --- /dev/null +++ b/xbmc/interfaces/python/XBPython.cpp @@ -0,0 +1,627 @@ +/* + * 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. + */ + +// clang-format off +// python.h should always be included first before any other includes +#include <mutex> +#include <Python.h> +// clang-format on + +#include "XBPython.h" + +#include "ServiceBroker.h" +#include "Util.h" +#include "cores/DllLoader/DllLoaderContainer.h" +#include "filesystem/SpecialProtocol.h" +#include "interfaces/AnnouncementManager.h" +#include "interfaces/legacy/AddonUtils.h" +#include "interfaces/legacy/Monitor.h" +#include "interfaces/python/AddonPythonInvoker.h" +#include "interfaces/python/PythonInvoker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/JSONVariantWriter.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "utils/CharsetConverter.h" + +#ifdef TARGET_WINDOWS +#include "platform/Environment.h" +#endif + +#include <algorithm> + +// Only required for Py3 < 3.7 +PyThreadState* savestate; + +bool XBPython::m_bInitialized = false; + +XBPython::XBPython() +{ + CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); +} + +XBPython::~XBPython() +{ + XBMC_TRACE; + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); +} + +#define LOCK_AND_COPY(type, dest, src) \ + if (!m_bInitialized) \ + return; \ + std::unique_lock<CCriticalSection> lock(src); \ + src.hadSomethingRemoved = false; \ + type dest; \ + dest = src + +#define CHECK_FOR_ENTRY(l, v) \ + (l.hadSomethingRemoved ? (std::find(l.begin(), l.end(), v) != l.end()) : true) + +void XBPython::Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) +{ + if (flag & ANNOUNCEMENT::VideoLibrary) + { + if (message == "OnScanFinished") + OnScanFinished("video"); + else if (message == "OnScanStarted") + OnScanStarted("video"); + else if (message == "OnCleanStarted") + OnCleanStarted("video"); + else if (message == "OnCleanFinished") + OnCleanFinished("video"); + } + else if (flag & ANNOUNCEMENT::AudioLibrary) + { + if (message == "OnScanFinished") + OnScanFinished("music"); + else if (message == "OnScanStarted") + OnScanStarted("music"); + else if (message == "OnCleanStarted") + OnCleanStarted("music"); + else if (message == "OnCleanFinished") + OnCleanFinished("music"); + } + else if (flag & ANNOUNCEMENT::GUI) + { + if (message == "OnScreensaverDeactivated") + OnScreensaverDeactivated(); + else if (message == "OnScreensaverActivated") + OnScreensaverActivated(); + else if (message == "OnDPMSDeactivated") + OnDPMSDeactivated(); + else if (message == "OnDPMSActivated") + OnDPMSActivated(); + } + + std::string jsonData; + if (CJSONVariantWriter::Write( + data, jsonData, + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact)) + OnNotification(sender, + std::string(ANNOUNCEMENT::AnnouncementFlagToString(flag)) + "." + + std::string(message), + jsonData); +} + +// message all registered callbacks that we started playing +void XBPython::OnPlayBackStarted(const CFileItem& file) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackStarted(file); + } +} + +// message all registered callbacks that we changed stream +void XBPython::OnAVStarted(const CFileItem& file) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnAVStarted(file); + } +} + +// message all registered callbacks that we changed stream +void XBPython::OnAVChange() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnAVChange(); + } +} + +// message all registered callbacks that we paused playing +void XBPython::OnPlayBackPaused() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackPaused(); + } +} + +// message all registered callbacks that we resumed playing +void XBPython::OnPlayBackResumed() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackResumed(); + } +} + +// message all registered callbacks that xbmc stopped playing +void XBPython::OnPlayBackEnded() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackEnded(); + } +} + +// message all registered callbacks that user stopped playing +void XBPython::OnPlayBackStopped() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackStopped(); + } +} + +// message all registered callbacks that playback stopped due to error +void XBPython::OnPlayBackError() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackError(); + } +} + +// message all registered callbacks that playback speed changed (FF/RW) +void XBPython::OnPlayBackSpeedChanged(int iSpeed) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackSpeedChanged(iSpeed); + } +} + +// message all registered callbacks that player is seeking +void XBPython::OnPlayBackSeek(int64_t iTime, int64_t seekOffset) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackSeek(iTime, seekOffset); + } +} + +// message all registered callbacks that player chapter seeked +void XBPython::OnPlayBackSeekChapter(int iChapter) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnPlayBackSeekChapter(iChapter); + } +} + +// message all registered callbacks that next item has been queued +void XBPython::OnQueueNextItem() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it)) + ((IPlayerCallback*)it)->OnQueueNextItem(); + } +} + +void XBPython::RegisterPythonPlayerCallBack(IPlayerCallback* pCallback) +{ + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList); + m_vecPlayerCallbackList.push_back(pCallback); +} + +void XBPython::UnregisterPythonPlayerCallBack(IPlayerCallback* pCallback) +{ + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList); + PlayerCallbackList::iterator it = m_vecPlayerCallbackList.begin(); + while (it != m_vecPlayerCallbackList.end()) + { + if (*it == pCallback) + { + it = m_vecPlayerCallbackList.erase(it); + m_vecPlayerCallbackList.hadSomethingRemoved = true; + } + else + ++it; + } +} + +void XBPython::RegisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback) +{ + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList); + m_vecMonitorCallbackList.push_back(pCallback); +} + +void XBPython::UnregisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback) +{ + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList); + MonitorCallbackList::iterator it = m_vecMonitorCallbackList.begin(); + while (it != m_vecMonitorCallbackList.end()) + { + if (*it == pCallback) + { + it = m_vecMonitorCallbackList.erase(it); + m_vecMonitorCallbackList.hadSomethingRemoved = true; + } + else + ++it; + } +} + +void XBPython::OnSettingsChanged(const std::string& ID) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it) && (it->GetId() == ID)) + it->OnSettingsChanged(); + } +} + +void XBPython::OnScreensaverActivated() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnScreensaverActivated(); + } +} + +void XBPython::OnScreensaverDeactivated() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnScreensaverDeactivated(); + } +} + +void XBPython::OnDPMSActivated() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnDPMSActivated(); + } +} + +void XBPython::OnDPMSDeactivated() +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnDPMSDeactivated(); + } +} + +void XBPython::OnScanStarted(const std::string& library) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnScanStarted(library); + } +} + +void XBPython::OnScanFinished(const std::string& library) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnScanFinished(library); + } +} + +void XBPython::OnCleanStarted(const std::string& library) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnCleanStarted(library); + } +} + +void XBPython::OnCleanFinished(const std::string& library) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnCleanFinished(library); + } +} + +void XBPython::OnNotification(const std::string& sender, + const std::string& method, + const std::string& data) +{ + XBMC_TRACE; + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + it->OnNotification(sender, method, data); + } +} + +void XBPython::Uninitialize() +{ + // don't handle any more announcements as most scripts are probably already + // stopped and executing a callback on one of their already destroyed classes + // would lead to a crash + CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this); + + LOCK_AND_COPY(std::vector<PyElem>, tmpvec, m_vecPyList); + m_vecPyList.clear(); + m_vecPyList.hadSomethingRemoved = true; + + lock.unlock(); //unlock here because the python thread might lock when it exits + + // cleanup threads that are still running + tmpvec.clear(); +} + +void XBPython::Process() +{ + if (m_bInitialized) + { + PyList tmpvec; + std::unique_lock<CCriticalSection> lock(m_vecPyList); + for (PyList::iterator it = m_vecPyList.begin(); it != m_vecPyList.end();) + { + if (it->bDone) + { + tmpvec.push_back(*it); + it = m_vecPyList.erase(it); + m_vecPyList.hadSomethingRemoved = true; + } + else + ++it; + } + lock.unlock(); + + //delete scripts which are done + tmpvec.clear(); + } +} + +bool XBPython::OnScriptInitialized(ILanguageInvoker* invoker) +{ + if (invoker == NULL) + return false; + + XBMC_TRACE; + CLog::Log(LOGDEBUG, "initializing python engine."); + std::unique_lock<CCriticalSection> lock(m_critSection); + m_iDllScriptCounter++; + if (!m_bInitialized) + { + // Darwin packs .pyo files, we need PYTHONOPTIMIZE on in order to load them. + // linux built with unified builds only packages the pyo files so need it +#if defined(TARGET_DARWIN) || defined(TARGET_LINUX) + setenv("PYTHONOPTIMIZE", "1", 1); +#endif + // Info about interesting python envvars available + // at http://docs.python.org/using/cmdline.html#environment-variables + +#if !defined(TARGET_WINDOWS) && !defined(TARGET_ANDROID) + // check if we are running as real xbmc.app or just binary + if (!CUtil::GetFrameworksPath(true).empty()) + { + // using external python, it's build looking for xxx/lib/python3.8 + // so point it to frameworks which is where python3.8 is located + setenv("PYTHONHOME", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1); + setenv("PYTHONPATH", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1); + CLog::Log(LOGDEBUG, "PYTHONHOME -> {}", + CSpecialProtocol::TranslatePath("special://frameworks")); + CLog::Log(LOGDEBUG, "PYTHONPATH -> {}", + CSpecialProtocol::TranslatePath("special://frameworks")); + } +#elif defined(TARGET_WINDOWS) + +#ifdef TARGET_WINDOWS_STORE +#ifdef _DEBUG + CEnvironment::putenv("PYTHONCASEOK=1"); +#endif + CEnvironment::putenv("OS=win10"); +#else // TARGET_WINDOWS_DESKTOP + CEnvironment::putenv("OS=win32"); +#endif + + std::wstring pythonHomeW; + CCharsetConverter::utf8ToW(CSpecialProtocol::TranslatePath("special://xbmc/system/python"), + pythonHomeW); + Py_SetPythonHome(pythonHomeW.c_str()); + + std::string pythonPath = CSpecialProtocol::TranslatePath("special://xbmc/system/python/DLLs"); + pythonPath += ";"; + pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib"); + pythonPath += ";"; + pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib/site-packages"); + std::wstring pythonPathW; + CCharsetConverter::utf8ToW(pythonPath, pythonPathW); + + Py_SetPath(pythonPathW.c_str()); + + Py_OptimizeFlag = 1; +#endif + + Py_Initialize(); + +#if PY_VERSION_HEX < 0x03070000 + // Python >= 3.7 Py_Initialize implicitly calls PyEval_InitThreads + // Python < 3.7 we have to manually call initthreads. + // PyEval_InitThreads is a no-op on subsequent calls, No need to wrap in + // PyEval_ThreadsInitialized() check + PyEval_InitThreads(); +#endif + + // Acquire GIL if thread doesn't currently hold. + if (!PyGILState_Check()) + PyEval_RestoreThread((PyThreadState*)m_mainThreadState); + + if (!(m_mainThreadState = PyThreadState_Get())) + CLog::Log(LOGERROR, "Python threadstate is NULL."); + savestate = PyEval_SaveThread(); + + m_bInitialized = true; + } + + return m_bInitialized; +} + +void XBPython::OnScriptStarted(ILanguageInvoker* invoker) +{ + if (invoker == NULL) + return; + + if (!m_bInitialized) + return; + + PyElem inf; + inf.id = invoker->GetId(); + inf.bDone = false; + inf.pyThread = static_cast<CPythonInvoker*>(invoker); + std::unique_lock<CCriticalSection> lock(m_vecPyList); + m_vecPyList.push_back(inf); +} + +void XBPython::NotifyScriptAborting(ILanguageInvoker* invoker) +{ + XBMC_TRACE; + + long invokerId(-1); + if (invoker != NULL) + invokerId = invoker->GetId(); + + LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList); + for (auto& it : tmp) + { + if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it)) + { + if (invokerId < 0 || it->GetInvokerId() == invokerId) + it->AbortNotify(); + } + } +} + +void XBPython::OnExecutionEnded(ILanguageInvoker* invoker) +{ + std::unique_lock<CCriticalSection> lock(m_vecPyList); + PyList::iterator it = m_vecPyList.begin(); + while (it != m_vecPyList.end()) + { + if (it->id == invoker->GetId()) + { + if (it->pyThread->IsStopping()) + CLog::Log(LOGDEBUG, "Python interpreter interrupted by user"); + else + CLog::Log(LOGDEBUG, "Python interpreter stopped"); + it->bDone = true; + } + ++it; + } +} + +void XBPython::OnScriptFinalized(ILanguageInvoker* invoker) +{ + XBMC_TRACE; + std::unique_lock<CCriticalSection> lock(m_critSection); + // for linux - we never release the library. its loaded and stays in memory. + if (m_iDllScriptCounter) + m_iDllScriptCounter--; + else + CLog::Log(LOGERROR, "Python script counter attempted to become negative"); +} + +ILanguageInvoker* XBPython::CreateInvoker() +{ + return new CAddonPythonInvoker(this); +} + +void XBPython::PulseGlobalEvent() +{ + m_globalEvent.Set(); +} + +bool XBPython::WaitForEvent(CEvent& hEvent, unsigned int milliseconds) +{ + // wait for either this event our our global event + XbmcThreads::CEventGroup eventGroup{&hEvent, &m_globalEvent}; + CEvent* ret = eventGroup.wait(std::chrono::milliseconds(milliseconds)); + if (ret) + m_globalEvent.Reset(); + return ret != NULL; +} diff --git a/xbmc/interfaces/python/XBPython.h b/xbmc/interfaces/python/XBPython.h new file mode 100644 index 0000000..e54b4a2 --- /dev/null +++ b/xbmc/interfaces/python/XBPython.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "cores/IPlayerCallback.h" +#include "interfaces/IAnnouncer.h" +#include "interfaces/generic/ILanguageInvocationHandler.h" +#include "threads/CriticalSection.h" +#include "threads/Event.h" +#include "threads/Thread.h" + +#include <memory> +#include <vector> + +class CPythonInvoker; +class CVariant; + +typedef struct +{ + int id; + bool bDone; + CPythonInvoker* pyThread; +} PyElem; + +class LibraryLoader; + +namespace XBMCAddon +{ +namespace xbmc +{ +class Monitor; +} +} // namespace XBMCAddon + +template<class T> +struct LockableType : public T, public CCriticalSection +{ + bool hadSomethingRemoved; +}; + +typedef LockableType<std::vector<void*>> PlayerCallbackList; +typedef LockableType<std::vector<XBMCAddon::xbmc::Monitor*>> MonitorCallbackList; +typedef LockableType<std::vector<PyElem>> PyList; +typedef std::vector<LibraryLoader*> PythonExtensionLibraries; + +class XBPython : public IPlayerCallback, + public ANNOUNCEMENT::IAnnouncer, + public ILanguageInvocationHandler +{ +public: + XBPython(); + ~XBPython() override; + void OnPlayBackEnded() override; + void OnPlayBackStarted(const CFileItem& file) override; + void OnAVStarted(const CFileItem& file) override; + void OnAVChange() override; + void OnPlayBackPaused() override; + void OnPlayBackResumed() override; + void OnPlayBackStopped() override; + void OnPlayBackError() override; + void OnPlayBackSpeedChanged(int iSpeed) override; + void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) override; + void OnPlayBackSeekChapter(int iChapter) override; + void OnQueueNextItem() override; + + void Announce(ANNOUNCEMENT::AnnouncementFlag flag, + const std::string& sender, + const std::string& message, + const CVariant& data) override; + void RegisterPythonPlayerCallBack(IPlayerCallback* pCallback); + void UnregisterPythonPlayerCallBack(IPlayerCallback* pCallback); + void RegisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback); + void UnregisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback); + void OnSettingsChanged(const std::string& strings); + void OnScreensaverActivated(); + void OnScreensaverDeactivated(); + void OnDPMSActivated(); + void OnDPMSDeactivated(); + void OnScanStarted(const std::string& library); + void OnScanFinished(const std::string& library); + void OnCleanStarted(const std::string& library); + void OnCleanFinished(const std::string& library); + void OnNotification(const std::string& sender, + const std::string& method, + const std::string& data); + + void Process() override; + void PulseGlobalEvent() override; + void Uninitialize() override; + bool OnScriptInitialized(ILanguageInvoker* invoker) override; + void OnScriptStarted(ILanguageInvoker* invoker) override; + void NotifyScriptAborting(ILanguageInvoker* invoker) override; + void OnExecutionEnded(ILanguageInvoker* invoker) override; + void OnScriptFinalized(ILanguageInvoker* invoker) override; + ILanguageInvoker* CreateInvoker() override; + + bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds); + +private: + static bool m_bInitialized; // whether global python runtime was already initialized + + CCriticalSection m_critSection; + void* m_mainThreadState{nullptr}; + int m_iDllScriptCounter{0}; // to keep track of the total scripts running that need the dll + + //Vector with list of threads used for running scripts + PyList m_vecPyList; + PlayerCallbackList m_vecPlayerCallbackList; + MonitorCallbackList m_vecMonitorCallbackList; + + // any global events that scripts should be using + CEvent m_globalEvent; + + // in order to finalize and unload the python library, need to save all the extension libraries that are + // loaded by it and unload them first (not done by finalize) + PythonExtensionLibraries m_extensions; +}; diff --git a/xbmc/interfaces/python/preamble.h b/xbmc/interfaces/python/preamble.h new file mode 100644 index 0000000..080a4fb --- /dev/null +++ b/xbmc/interfaces/python/preamble.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#ifdef SWIGPYTHON + +#include <Python.h> + +#endif diff --git a/xbmc/interfaces/python/pythreadstate.h b/xbmc/interfaces/python/pythreadstate.h new file mode 100644 index 0000000..2cae5a7 --- /dev/null +++ b/xbmc/interfaces/python/pythreadstate.h @@ -0,0 +1,64 @@ +/* + * 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 + + +//WARNING: since this will unlock/lock the python global interpreter lock, +// it will not work recursively + +//this is basically a scoped version of a Py_BEGIN_ALLOW_THREADS .. Py_END_ALLOW_THREADS block +class CPyThreadState +{ + public: + explicit CPyThreadState(bool save = true) + { + m_threadState = NULL; + + if (save) + Save(); + } + + ~CPyThreadState() + { + Restore(); + } + + void Save() + { + if (!m_threadState) + m_threadState = PyEval_SaveThread(); //same as Py_BEGIN_ALLOW_THREADS + } + + void Restore() + { + if (m_threadState) + { + PyEval_RestoreThread(m_threadState); //same as Py_END_ALLOW_THREADS + m_threadState = NULL; + } + } + + private: + PyThreadState* m_threadState; +}; + +/** + * A std::unique_lock<CCriticalSection> that will relinquish the GIL during the time + * it takes to obtain the CriticalSection + */ +class GilSafeSingleLock : public CPyThreadState, public std::unique_lock<CCriticalSection> +{ +public: + explicit GilSafeSingleLock(CCriticalSection& critSec) + : CPyThreadState(true), std::unique_lock<CCriticalSection>(critSec) + { + CPyThreadState::Restore(); + } +}; + diff --git a/xbmc/interfaces/python/swig.cpp b/xbmc/interfaces/python/swig.cpp new file mode 100644 index 0000000..0c49f87 --- /dev/null +++ b/xbmc/interfaces/python/swig.cpp @@ -0,0 +1,443 @@ +/* + * 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 "swig.h" + +#include "LanguageHook.h" +#include "interfaces/legacy/AddonString.h" +#include "utils/StringUtils.h" + +#include <string> + +namespace PythonBindings +{ + TypeInfo::TypeInfo(const std::type_info& ti) : swigType(NULL), parentType(NULL), typeIndex(ti) + { + static PyTypeObject py_type_object_header = { + PyVarObject_HEAD_INIT(nullptr, 0) 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, +#if PY_VERSION_HEX > 0x03080000 + 0, + 0, +#endif +#if PY_VERSION_HEX < 0x03090000 + 0, +#endif +#if PY_VERSION_HEX >= 0x030C00A1 + 0, +#endif + }; + + static int size = (long*)&(py_type_object_header.tp_name) - (long*)&py_type_object_header; + memcpy(&(this->pythonType), &py_type_object_header, size); + } + + class PyObjectDecrementor + { + PyObject* obj; + public: + inline explicit PyObjectDecrementor(PyObject* pyobj) : obj(pyobj) {} + inline ~PyObjectDecrementor() { Py_XDECREF(obj); } + + inline PyObject* get() { return obj; } + }; + + void PyXBMCGetUnicodeString(std::string& buf, PyObject* pObject, bool coerceToString, + const char* argumentName, const char* methodname) + { + // It's okay for a string to be "None". In this case the buf returned + // will be the emptyString. + if (pObject == Py_None) + { + buf = XBMCAddon::emptyString; + return; + } + + //! @todo UTF-8: Does python use UTF-16? + //! Do we need to convert from the string charset to UTF-8 + //! for non-unicode data? + if (PyUnicode_Check(pObject)) + { + // Python unicode objects are UCS2 or UCS4 depending on compilation + // options, wchar_t is 16-bit or 32-bit depending on platform. + // Avoid the complexity by just letting python convert the string. + + buf = PyUnicode_AsUTF8(pObject); + return; + } + + if (PyBytes_Check(pObject)) // If pobject is of type Bytes + { + buf = PyBytes_AsString(pObject); + return; + } + + // if we got here then we need to coerce the value to a string + if (coerceToString) + { + PyObjectDecrementor dec(PyObject_Str(pObject)); + PyObject* pyStrCast = dec.get(); + if (pyStrCast) + { + PyXBMCGetUnicodeString(buf,pyStrCast,false,argumentName,methodname); + return; + } + } + + // Object is not a unicode or a normal string. + buf = ""; + throw XBMCAddon::WrongTypeException("argument \"%s\" for method \"%s\" must be unicode or str", argumentName, methodname); + } + + // need to compare the typestring + bool isParameterRightType(const char* passedType, const char* expectedType, const char* methodNamespacePrefix, bool tryReverse) + { + if (strcmp(expectedType,passedType) == 0) + return true; + + // well now things are a bit more complicated. We need to see if the passed type + // is a subset of the overall type + std::string et(expectedType); + bool isPointer = (et[0] == 'p' && et[1] == '.'); + std::string baseType(et,(isPointer ? 2 : 0)); // this may contain a namespace + + std::string ns(methodNamespacePrefix); + // cut off trailing '::' + if (ns.size() > 2 && ns[ns.size() - 1] == ':' && ns[ns.size() - 2] == ':') + ns = ns.substr(0,ns.size()-2); + + bool done = false; + while(! done) + { + done = true; + + // now we need to see if the expected type can be munged + // into the passed type by tacking on the namespace of + // of the method. + std::string check(isPointer ? "p." : ""); + check += ns; + check += "::"; + check += baseType; + + if (strcmp(check.c_str(),passedType) == 0) + return true; + + // see if the namespace is nested. + int posOfScopeOp = ns.find("::"); + if (posOfScopeOp >= 0) + { + done = false; + // cur off the outermost namespace + ns = ns.substr(posOfScopeOp + 2); + } + } + + // so far we applied the namespace to the expected type. Now lets try + // the reverse if we haven't already. + if (tryReverse) + return isParameterRightType(expectedType, passedType, methodNamespacePrefix, false); + + return false; + } + + PythonToCppException::PythonToCppException() : XbmcCommons::UncheckedException(" ") + { + setClassname("PythonToCppException"); + + std::string msg; + std::string type, value, traceback; + if (!ParsePythonException(type, value, traceback)) + UncheckedException::SetMessage("Strange: No Python exception occurred"); + else + SetMessage(type, value, traceback); + } + + PythonToCppException::PythonToCppException(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback) : XbmcCommons::UncheckedException(" ") + { + setClassname("PythonToCppException"); + + SetMessage(exceptionType, exceptionValue, exceptionTraceback); + } + + bool PythonToCppException::ParsePythonException(std::string &exceptionType, std::string &exceptionValue, std::string &exceptionTraceback) + { + PyObject* exc_type; + PyObject* exc_value; + PyObject* exc_traceback; + PyObject* pystring = NULL; + + PyErr_Fetch(&exc_type, &exc_value, &exc_traceback); + if (exc_type == NULL && exc_value == NULL && exc_traceback == NULL) + return false; + + // See https://docs.python.org/3/c-api/exceptions.html#c.PyErr_NormalizeException + PyErr_NormalizeException(&exc_type, &exc_value, &exc_traceback); + if (exc_traceback != NULL) { + PyException_SetTraceback(exc_value, exc_traceback); + } + + exceptionType.clear(); + exceptionValue.clear(); + exceptionTraceback.clear(); + + if (exc_type != NULL && (pystring = PyObject_Str(exc_type)) != NULL && PyUnicode_Check(pystring)) + { + const char* str = PyUnicode_AsUTF8(pystring); + if (str != NULL) + exceptionType = str; + + pystring = PyObject_Str(exc_value); + if (pystring != NULL) + { + str = PyUnicode_AsUTF8(pystring); + exceptionValue = str; + } + + PyObject *tracebackModule = PyImport_ImportModule("traceback"); + if (tracebackModule != NULL) + { + char method[] = "format_exception"; + char format[] = "OOO"; + PyObject *tbList = PyObject_CallMethod(tracebackModule, method, format, exc_type, exc_value == NULL ? Py_None : exc_value, exc_traceback == NULL ? Py_None : exc_traceback); + + if (tbList) + { + PyObject* emptyString = PyUnicode_FromString(""); + char method[] = "join"; + char format[] = "O"; + PyObject *strRetval = PyObject_CallMethod(emptyString, method, format, tbList); + Py_DECREF(emptyString); + + if (strRetval) + { + str = PyUnicode_AsUTF8(strRetval); + if (str != NULL) + exceptionTraceback = str; + Py_DECREF(strRetval); + } + Py_DECREF(tbList); + } + Py_DECREF(tracebackModule); + + } + } + + Py_XDECREF(exc_type); + Py_XDECREF(exc_value); + Py_XDECREF(exc_traceback); + Py_XDECREF(pystring); + + return true; + } + + void PythonToCppException::SetMessage(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback) + { + std::string msg = "-->Python callback/script returned the following error<--\n"; + msg += " - NOTE: IGNORING THIS CAN LEAD TO MEMORY LEAKS!\n"; + + if (!exceptionType.empty()) + { + msg += StringUtils::Format("Error Type: {}\n", exceptionType); + + if (!exceptionValue.empty()) + msg += StringUtils::Format("Error Contents: {}\n", exceptionValue); + + if (!exceptionTraceback.empty()) + msg += exceptionTraceback; + + msg += "-->End of Python script error report<--\n"; + } + else + msg += "<unknown exception type>"; + + UncheckedException::SetMessage("%s", msg.c_str()); + } + + XBMCAddon::AddonClass* doretrieveApiInstance(const PyHolder* pythonObj, const TypeInfo* typeInfo, const char* expectedType, + const char* methodNamespacePrefix, const char* methodNameForErrorString) + { + if (pythonObj->magicNumber != XBMC_PYTHON_TYPE_MAGIC_NUMBER) + throw XBMCAddon::WrongTypeException("Non api type passed to \"%s\" in place of the expected type \"%s.\"", + methodNameForErrorString, expectedType); + if (!isParameterRightType(typeInfo->swigType,expectedType,methodNamespacePrefix)) + { + // maybe it's a child class + if (typeInfo->parentType) + return doretrieveApiInstance(pythonObj, typeInfo->parentType,expectedType, + methodNamespacePrefix, methodNameForErrorString); + else + throw XBMCAddon::WrongTypeException("Incorrect type passed to \"%s\", was expecting a \"%s\" but received a \"%s\"", + methodNameForErrorString,expectedType,typeInfo->swigType); + } + return const_cast<XBMCAddon::AddonClass*>(pythonObj->pSelf); + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class constructor being returned from the generated code to Python + */ + void prepareForReturn(XBMCAddon::AddonClass* c) + { + XBMC_TRACE; + if(c) { + c->Acquire(); + PyThreadState* state = PyThreadState_Get(); + XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp)->RegisterAddonClassInstance(c); + } + } + + static bool handleInterpRegistrationForClean(XBMCAddon::AddonClass* c) + { + XBMC_TRACE; + if(c){ + XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> lh = + XBMCAddon::AddonClass::Ref<XBMCAddon::AddonClass>(c->GetLanguageHook()); + + if (lh.isNotNull()) + { + lh->UnregisterAddonClassInstance(c); + return true; + } + else + { + PyThreadState* state = PyThreadState_Get(); + lh = XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp); + if (lh.isNotNull()) lh->UnregisterAddonClassInstance(c); + return true; + } + } + return false; + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + */ + void cleanForDealloc(XBMCAddon::AddonClass* c) + { + XBMC_TRACE; + if (handleInterpRegistrationForClean(c)) + c->Release(); + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + * + * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be + * called on destruction but cannot be called from the destructor. + * This overrides the default cleanForDealloc to resolve that. + */ + void cleanForDealloc(XBMCAddon::xbmcgui::Window* c) + { + XBMC_TRACE; + if (handleInterpRegistrationForClean(c)) + { + c->dispose(); + c->Release(); + } + } + + /** + * This method allows for conversion of the native api Type to the Python type. + * + * When this form of the call is used (and pytype isn't NULL) then the + * passed type is used in the instance. This is for classes that extend API + * classes in python. The type passed may not be the same type that's stored + * in the class metadata of the AddonClass of which 'api' is an instance, + * it can be a subclass in python. + * + * if pytype is NULL then the type is inferred using the class metadata + * stored in the AddonClass instance 'api'. + */ + PyObject* makePythonInstance(XBMCAddon::AddonClass* api, PyTypeObject* pytype, bool incrementRefCount) + { + // null api types result in Py_None + if (!api) + { + Py_INCREF(Py_None); + return Py_None; + } + + // retrieve the TypeInfo from the api class + const TypeInfo* typeInfo = getTypeInfoForInstance(api); + PyTypeObject* typeObj = pytype == NULL ? const_cast<PyTypeObject*>(&(typeInfo->pythonType)) : pytype; + + PyHolder* self = reinterpret_cast<PyHolder*>(typeObj->tp_alloc(typeObj,0)); + if (!self) return NULL; + self->magicNumber = XBMC_PYTHON_TYPE_MAGIC_NUMBER; + self->typeInfo = typeInfo; + self->pSelf = api; + if (incrementRefCount) + Py_INCREF((PyObject*)self); + return (PyObject*)self; + } + + std::map<std::type_index, const TypeInfo*> typeInfoLookup; + + void registerAddonClassTypeInformation(const TypeInfo* classInfo) + { + typeInfoLookup[classInfo->typeIndex] = classInfo; + } + + const TypeInfo* getTypeInfoForInstance(XBMCAddon::AddonClass* obj) + { + std::type_index ti(typeid(*obj)); + return typeInfoLookup[ti]; + } + + int dummy_tp_init(PyObject* self, PyObject* args, PyObject* kwds) + { + return 0; + } +} + diff --git a/xbmc/interfaces/python/swig.h b/xbmc/interfaces/python/swig.h new file mode 100644 index 0000000..353d968 --- /dev/null +++ b/xbmc/interfaces/python/swig.h @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "interfaces/legacy/AddonClass.h" +#include "interfaces/legacy/Exception.h" +#include "interfaces/legacy/Window.h" + +#include <stdint.h> +#include <string> +#include <typeindex> + +#include <Python.h> + +namespace PythonBindings +{ + /** + * This call will convert the python object passed to a string. The object + * passed must be a python str or unicode object unless coerceToString is + * true. If coerceToString is true then the type must be castable to a string + * using the python call str(pObject). + * + * This method will handle a 'None' that's passed in. If 'None' is passed then + * the resulting buf will contain the value of XBMCAddon::emptyString (which + * is simply a std::string instantiated with the default constructor. + */ + void PyXBMCGetUnicodeString(std::string& buf, PyObject* pObject, bool coerceToString = false, + const char* pos = "unknown", + const char* methodname = "unknown"); + + struct TypeInfo + { + const char* swigType; + TypeInfo* parentType; + PyTypeObject pythonType; + const std::type_index typeIndex; + + explicit TypeInfo(const std::type_info& ti); + }; + + // This will hold the pointer to the api type, whether known or unknown + struct PyHolder + { + PyObject_HEAD + int32_t magicNumber; + const TypeInfo* typeInfo; + XBMCAddon::AddonClass* pSelf; + }; + +#define XBMC_PYTHON_TYPE_MAGIC_NUMBER 0x58626D63 + + /** + * This method retrieves the pointer from the PyHolder. The return value should + * be cast to the appropriate type. + * + * Since the calls to this are generated there's no NULL pointer checks + */ + inline XBMCAddon::AddonClass* retrieveApiInstance(PyObject* pythonObj, const TypeInfo* typeToCheck, + const char* methodNameForErrorString, + const char* typenameForErrorString) + { + if (pythonObj == NULL || pythonObj == Py_None) + return NULL; + if (reinterpret_cast<PyHolder*>(pythonObj)->magicNumber != XBMC_PYTHON_TYPE_MAGIC_NUMBER || !PyObject_TypeCheck(pythonObj, const_cast<PyTypeObject*>((&(typeToCheck->pythonType))))) + throw XBMCAddon::WrongTypeException("Incorrect type passed to \"%s\", was expecting a \"%s\".",methodNameForErrorString,typenameForErrorString); + return reinterpret_cast<PyHolder*>(pythonObj)->pSelf; + } + + bool isParameterRightType(const char* passedType, const char* expectedType, const char* methodNamespacePrefix, bool tryReverse = true); + + XBMCAddon::AddonClass* doretrieveApiInstance(const PyHolder* pythonObj, const TypeInfo* typeInfo, const char* expectedType, + const char* methodNamespacePrefix, const char* methodNameForErrorString); + + /** + * This method retrieves the pointer from the PyHolder. The return value should + * be cast to the appropriate type. + * + * Since the calls to this are generated there's no NULL pointer checks + * + * This method will return NULL if either the pythonObj is NULL or the + * pythonObj is Py_None. + */ + inline XBMCAddon::AddonClass* retrieveApiInstance(const PyObject* pythonObj, const char* expectedType, const char* methodNamespacePrefix, + const char* methodNameForErrorString) + { + return (pythonObj == NULL || pythonObj == Py_None) ? NULL : + doretrieveApiInstance(reinterpret_cast<const PyHolder*>(pythonObj),reinterpret_cast<const PyHolder*>(pythonObj)->typeInfo, expectedType, methodNamespacePrefix, methodNameForErrorString); + } + + /** + * This method is a helper for the generated API. It's called prior to any API + * class constructor being returned from the generated code to Python + */ + void prepareForReturn(XBMCAddon::AddonClass* c); + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + */ + void cleanForDealloc(XBMCAddon::AddonClass* c); + + /** + * This method is a helper for the generated API. It's called prior to any API + * class destructor being dealloc-ed from the generated code from Python + * + * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be + * called on destruction but cannot be called from the destructor. + * This overrides the default cleanForDealloc to resolve that. + */ + void cleanForDealloc(XBMCAddon::xbmcgui::Window* c); + + /** + * This method allows for conversion of the native api Type to the Python type. + * + * When this form of the call is used (and pythonType isn't NULL) then the + * passed type is used in the instance. This is for classes that extend API + * classes in python. The type passed may not be the same type that's stored + * in the class metadata of the AddonClass of which 'api' is an instance, + * it can be a subclass in python. + * + * if pythonType is NULL then the type is inferred using the class metadata + * stored in the AddonClass instance 'api'. + */ + PyObject* makePythonInstance(XBMCAddon::AddonClass* api, PyTypeObject* pythonType, bool incrementRefCount); + + /** + * This method allows for conversion of the native api Type to the Python type. + * + * When this form of the call is used then the python type constructed will be the + * type given by the class metadata in the AddonClass instance 'api'. + * + * This is just a helper inline to call the other makePythonInstance with NULL as + * the pythonType. + */ + inline PyObject* makePythonInstance(XBMCAddon::AddonClass* api, bool incrementRefCount) + { + return makePythonInstance(api,NULL,incrementRefCount); + } + + void registerAddonClassTypeInformation(const TypeInfo* classInfo); + const TypeInfo* getTypeInfoForInstance(XBMCAddon::AddonClass* obj); + + int dummy_tp_init(PyObject* self, PyObject* args, PyObject* kwds); + + class Director + { + protected: + PyObject* self; + public: + inline Director() : self(NULL) {} + inline void setPyObjectForDirector(PyObject* pyargself) { self = pyargself; } + }; + + /** + * This exception is thrown from Director calls that call into python when the + * Python error is + */ + class PythonToCppException : public XbmcCommons::UncheckedException + { + public: + /** + * Assuming a PyErr_Occurred, this will fill the exception message with all + * of the appropriate information including the traceback if it can be + * obtained. It will also clear the python message. + */ + PythonToCppException(); + PythonToCppException(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback); + + static bool ParsePythonException(std::string &exceptionType, std::string &exceptionValue, std::string &exceptionTraceback); + + protected: + void SetMessage(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback); + }; + + template<class T> struct PythonCompare + { + static inline int compare(PyObject* obj1, PyObject* obj2, const char* swigType, const char* methodNamespacePrefix, const char* methodNameForErrorString) + { + XBMC_TRACE; + try + { + T* o1 = (T*)retrieveApiInstance(obj1, swigType, methodNamespacePrefix, methodNameForErrorString); + T* o2 = (T*)retrieveApiInstance(obj2, swigType, methodNamespacePrefix, methodNameForErrorString); + + return ((*o1) < (*o2) ? -1 : + ((*o1) > (*o2) ? 1 : 0)); + } + catch (const XBMCAddon::WrongTypeException& e) + { + CLog::Log(LOGERROR, "EXCEPTION: {}", e.GetExMessage()); + PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); + } + return -1; + } + }; +} diff --git a/xbmc/interfaces/python/test/CMakeLists.txt b/xbmc/interfaces/python/test/CMakeLists.txt new file mode 100644 index 0000000..ec38a51 --- /dev/null +++ b/xbmc/interfaces/python/test/CMakeLists.txt @@ -0,0 +1,5 @@ +if(PYTHON_FOUND) + set(SOURCES TestSwig.cpp) + + core_add_test_library(python_test) +endif() diff --git a/xbmc/interfaces/python/test/TestSwig.cpp b/xbmc/interfaces/python/test/TestSwig.cpp new file mode 100644 index 0000000..463bb98 --- /dev/null +++ b/xbmc/interfaces/python/test/TestSwig.cpp @@ -0,0 +1,21 @@ +/* + * 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 "../swig.h" + +#include <gtest/gtest.h> + +using namespace PythonBindings; + +TEST(TestSwig, TypeConversion) +{ + EXPECT_TRUE(isParameterRightType("p.XBMCAddon::xbmcgui::ListItem","p.XBMCAddon::xbmcgui::ListItem","XBMCAddon::xbmc::")); + EXPECT_TRUE(isParameterRightType("p.XBMCAddon::xbmc::PlayList","p.PlayList","XBMCAddon::xbmc::")); + EXPECT_TRUE(isParameterRightType("p.PlayList","p.XBMCAddon::xbmc::PlayList","XBMCAddon::xbmc::")); +} + diff --git a/xbmc/interfaces/python/typemaps/python.Alternative.intm b/xbmc/interfaces/python/typemaps/python.Alternative.intm new file mode 100644 index 0000000..12a5e65 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.Alternative.intm @@ -0,0 +1,43 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + boolean ispointer = swigTypeParser.SwigType_ispointer(ltype) + String accessor = ispointer ? '->' : '.' + int seq = sequence.increment() + altAccess = [ 'former', 'later' ] + altSwitch = [ 'first', 'second' ] + + List types = swigTypeParser.SwigType_templateparmlist(ltype) +%> + { + // we need to check the parameter type and see if it matches + PyObject *pyentry_${seq} = ${slarg}; + try + { + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(types[0]))} entry0_${seq}; + ${helper.getInConversion(types[0], 'entry0' + '_' + seq, 'pyentry' + '_' + seq, method, + [ 'sequence' : sequence ])} + ${api}${accessor}${altAccess[0]}() = entry0_${seq}; + } + catch (const XBMCAddon::WrongTypeException&) + { + try + { + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(types[1]))} entry1_${seq}; + ${helper.getInConversion(types[1], 'entry1' + '_' + seq, 'pyentry' + '_' + seq, method, + [ 'sequence' : sequence ])} + ${api}${accessor}${altAccess[1]}() = entry1_${seq}; + } + catch (const XBMCAddon::WrongTypeException&) + { + throw XBMCAddon::WrongTypeException("Failed to convert to input type to either a " + "${swigTypeParser.SwigType_ltype(types[0])} or a " + "${swigTypeParser.SwigType_ltype(types[1])}" ); + } + } + }
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.Alternative.outtm b/xbmc/interfaces/python/typemaps/python.Alternative.outtm new file mode 100644 index 0000000..31f4205 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.Alternative.outtm @@ -0,0 +1,34 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + List types = swigTypeParser.SwigType_templateparmlist(type) + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + int seq = sequence.increment() + String accessor = ispointer ? '->' : '.' + altAccess = [ 'former', 'later' ] + altSwitch = [ 'first', 'second' ] +%> + WhichAlternative pos = ${api}${accessor}which(); + + if (<%if (ispointer) { %>${api} != NULL && <%}%>pos != XBMCAddon::none) + { <% + types.eachWithIndex { curType, entryIndex -> +%> + if (pos == XBMCAddon::${altSwitch[entryIndex]}) + { + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_lrtype(curType))} entry${seq} = ${api}${accessor}${altAccess[entryIndex]}(); + { + ${helper.getOutConversion(curType,result,method,[ 'api' : 'entry' + seq, 'sequence' : sequence ])} + } + } +<% + } +%> + } + else + ${result} = Py_None;
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.Tuple.intm b/xbmc/interfaces/python/typemaps/python.Tuple.intm new file mode 100644 index 0000000..c426856 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.Tuple.intm @@ -0,0 +1,35 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + List types = swigTypeParser.SwigType_templateparmlist(ltype) + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + String accessor = ispointer ? '->' : '.' + int seq = sequence.increment() + tupleAccess = [ 'first', 'second', 'third', 'fourth' ] +%> + if(${slarg}) + { + bool isTuple = PyObject_TypeCheck(${slarg},&PyTuple_Type); + if (!isTuple && !PyObject_TypeCheck(${slarg},&PyList_Type)) + throw WrongTypeException("The parameter \"${api}\" must be either a Tuple or a List."); + Py_ssize_t vecSize = (isTuple ? PyTuple_Size(${slarg}) : PyList_Size(${slarg})); +<% + types.eachWithIndex { curType, entryIndex -> +%> + if (vecSize > ${entryIndex}) + { + PyObject *pyentry${entryIndex}_${seq} = NULL; + pyentry${entryIndex}_${seq} = (isTuple ? PyTuple_GetItem(${slarg}, ${entryIndex}) : PyList_GetItem(${slarg}, ${entryIndex})); + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(curType))} entry${entryIndex}_${seq}; + ${helper.getInConversion(curType, 'entry' + entryIndex + '_' + seq, 'pyentry' + entryIndex + '_' + seq, method,[ 'sequence' : sequence ])} + ${api}${accessor}${tupleAccess[entryIndex]}() = entry${entryIndex}_${seq}; + } +<% + } +%> + } diff --git a/xbmc/interfaces/python/typemaps/python.Tuple.outtm b/xbmc/interfaces/python/typemaps/python.Tuple.outtm new file mode 100644 index 0000000..18655fe --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.Tuple.outtm @@ -0,0 +1,39 @@ +<% +/* + * 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. + */ + List types = swigTypeParser.SwigType_templateparmlist(type) + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + int seq = sequence.increment() + String accessor = ispointer ? '->' : '.' + tupleAccess = [ 'first', 'second', 'third', 'fourth' ] +%> + int vecSize = ${api}${accessor}GetNumValuesSet(); + ${result} = PyTuple_New(vecSize); +<% + if (ispointer) + { +%> + if (${api} != NULL) +<% } // this ends the if (ispointer) +%> { + PyObject* pyentry${seq}; <% + types.eachWithIndex { curType, entryIndex -> +%> + + if (vecSize > ${entryIndex}) + { + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_lrtype(curType))} entry${seq} = ${api}${accessor}${tupleAccess[entryIndex]}(); + { + ${helper.getOutConversion(curType,'result',method,[ 'result' : 'pyentry' + seq, 'api' : 'entry' + seq, 'sequence' : sequence ])} + } + PyTuple_SetItem(${result}, ${entryIndex}, pyentry${seq}); + } +<% + } +%> + }
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.buffer.intm b/xbmc/interfaces/python/typemaps/python.buffer.intm new file mode 100644 index 0000000..f074b2b --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.buffer.intm @@ -0,0 +1,36 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ +%> + if (PyUnicode_Check(${slarg})) + { + Py_ssize_t pysize; + const char* str = PyUnicode_AsUTF8AndSize(${slarg}, &pysize); + size_t size = static_cast<size_t>(pysize); + ${api}.allocate(size); + ${api}.put(str, size); + ${api}.flip(); // prepare the buffer for reading from + } + else if (PyBytes_Check(${slarg})) + { + Py_ssize_t pysize = PyBytes_GET_SIZE(${slarg}); + const char* str = PyBytes_AS_STRING(${slarg}); + size_t size = static_cast<size_t>(pysize); + ${api}.allocate(size); + ${api}.put(str, size); + ${api}.flip(); // prepare the buffer for reading from + } + else if (PyByteArray_Check(${slarg})) + { + size_t size = PyByteArray_Size(${slarg}); + ${api}.allocate(size); + ${api}.put(PyByteArray_AsString(${slarg}),size); + ${api}.flip(); // prepare the buffer for reading from + } + else + throw XBMCAddon::WrongTypeException("argument \"%s\" for \"%s\" must be a string, bytes or a bytearray", "${api}", "${method.@name}");
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.buffer.outtm b/xbmc/interfaces/python/typemaps/python.buffer.outtm new file mode 100644 index 0000000..8e38d81 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.buffer.outtm @@ -0,0 +1,11 @@ +<% +/* + * 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. + */ + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + String accessor = ispointer ? '->' : '.' +%>${result} = PyByteArray_FromStringAndSize((char*)${api}${accessor}curPosition(),${api}${accessor}remaining());
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.dict.intm b/xbmc/interfaces/python/typemaps/python.dict.intm new file mode 100644 index 0000000..ea3c78c --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.dict.intm @@ -0,0 +1,23 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype) + valtype = templateArgs[0] +%> + { + PyObject *pykey, *pyvalue; + Py_ssize_t pos = 0; + while(PyDict_Next(${slarg}, &pos, &pykey, &pyvalue)) + { + std::string key; + PyXBMCGetUnicodeString(key,pykey,false,"${api}","${method.@name}"); + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(valtype))} value; + ${helper.getInConversion(valtype, 'value', 'pyvalue' ,method)} + ${api}.emplace(std::move(key), std::move(value)); + } + }
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.map.intm b/xbmc/interfaces/python/typemaps/python.map.intm new file mode 100644 index 0000000..095d23b --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.map.intm @@ -0,0 +1,24 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype) + keytype = templateArgs[0] + valtype = templateArgs[1] +%> + { + PyObject *pykey, *pyvalue; + Py_ssize_t pos = 0; + while(PyDict_Next(${slarg}, &pos, &pykey, &pyvalue)) + { + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(keytype))} key; + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(valtype))} value; + ${helper.getInConversion(keytype, 'key', 'pykey', method)} + ${helper.getInConversion(valtype, 'value', 'pyvalue' ,method)} + ${api}.emplace(std::move(key), std::move(value)); + } + }
\ No newline at end of file diff --git a/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm b/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm new file mode 100644 index 0000000..0d7fa31 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm @@ -0,0 +1,14 @@ +<% +/* + * 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. + */ + itype = swigTypeParser.SwigType_templateparmlist(type)[0] + pointertype = swigTypeParser.SwigType_makepointer(itype) + int seq = sequence.increment() +%> + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(pointertype))} entry${seq} = ${api}.get(); + ${helper.getOutConversion(pointertype,'result',method,[ 'api' : 'entry' + seq, 'sequence' : sequence ])} diff --git a/xbmc/interfaces/python/typemaps/python.string.outtm b/xbmc/interfaces/python/typemaps/python.string.outtm new file mode 100644 index 0000000..f9eb068 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.string.outtm @@ -0,0 +1,11 @@ +<% +/* + * 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. + */ +%>${result} = <% + if(method.@feature_python_strictUnicode) { %>PyUnicode_DecodeUTF8(${api}.c_str(),${api}.size(),"strict");<% } + else { %>PyUnicode_DecodeUTF8(${api}.c_str(),${api}.size(),"surrogateescape");<% } %> diff --git a/xbmc/interfaces/python/typemaps/python.vector.intm b/xbmc/interfaces/python/typemaps/python.vector.intm new file mode 100644 index 0000000..6bc71ec --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.vector.intm @@ -0,0 +1,36 @@ +<% +/* + * 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. + */ + List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype) + vectype = templateArgs[0] + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + String accessor = ispointer ? '->' : '.' + int seq = sequence.increment() +%> + if (${slarg}) + { + bool isTuple = PyObject_TypeCheck(${slarg},&PyTuple_Type); + if (!isTuple && !PyObject_TypeCheck(${slarg},&PyList_Type)) + throw WrongTypeException("The parameter \"${api}\" must be either a Tuple or a List."); + + <% if (ispointer) print("${api} = new std::vector<${swigTypeParser.SwigType_str(vectype)}>();") %> + PyObject *pyentry${seq} = NULL; + Py_ssize_t vecSize = (isTuple ? PyTuple_Size(${slarg}) : PyList_Size(${slarg})); + ${api}${accessor}reserve(vecSize); + for(Py_ssize_t i = 0; i < vecSize; i++) + { + pyentry${seq} = (isTuple ? PyTuple_GetItem(${slarg}, i) : PyList_GetItem(${slarg}, i)); + ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(vectype))} entry${seq}; + ${helper.getInConversion(vectype, 'entry' + seq, 'pyentry' + seq, method, + [ 'type' : vectype, + 'ltype' : swigTypeParser.SwigType_ltype(vectype), + 'sequence' : sequence + ])} + ${api}${accessor}push_back(<% if (swigTypeParser.SwigType_ispointer(vectype) || vectype in ["bool", "double", "int"]) { %>entry${seq}<% } else { %>std::move(entry${seq})<% } %>); + } + } diff --git a/xbmc/interfaces/python/typemaps/python.vector.outtm b/xbmc/interfaces/python/typemaps/python.vector.outtm new file mode 100644 index 0000000..c1c4c79 --- /dev/null +++ b/xbmc/interfaces/python/typemaps/python.vector.outtm @@ -0,0 +1,35 @@ +<% +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + List templateArgs = swigTypeParser.SwigType_templateparmlist(type) + vectype = templateArgs[0] + boolean ispointer = swigTypeParser.SwigType_ispointer(type) + String accessor = ispointer ? '->' : '.' + seq = sequence.increment() + + if (ispointer) + { +%> + if (${api} != NULL) + { +<% } %> + ${result} = PyList_New(0); + + for (std::vector<${swigTypeParser.SwigType_str(vectype)}>::iterator iter = ${api}${accessor}begin(); iter != ${api}${accessor}end(); ++iter) + { + PyObject* pyentry${seq}; + ${helper.getOutConversion(vectype,'result',method,[ 'result' : 'pyentry' + seq, 'api' : '(*iter)', 'sequence' : sequence ])} + PyList_Append(${result}, pyentry${seq}); + Py_DECREF(pyentry${seq}); + } +<% + if (ispointer) + { +%> + } +<% } %> diff --git a/xbmc/interfaces/swig/AddonModuleXbmc.i b/xbmc/interfaces/swig/AddonModuleXbmc.i new file mode 100644 index 0000000..0b54435 --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmc.i @@ -0,0 +1,63 @@ +/* + * 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. + */ + +%module(directors="1") xbmc + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/Player.h" +#include "interfaces/legacy/RenderCapture.h" +#include "interfaces/legacy/Keyboard.h" +#include "interfaces/legacy/ModuleXbmc.h" +#include "interfaces/legacy/Monitor.h" + +using namespace XBMCAddon; +using namespace xbmc; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif +%} + +// This is all about warning suppression. It's OK that these base classes are +// not part of what swig parses. +%feature("knownbasetypes") XBMCAddon::xbmc "AddonClass,IPlayerCallback,AddonCallback" +%feature("knownapitypes") XBMCAddon::xbmc "XBMCAddon::xbmcgui::ListItem,XBMCAddon::xbmc::PlayListItem" + +%include "interfaces/legacy/swighelper.h" + +%include "interfaces/legacy/AddonString.h" +%include "interfaces/legacy/ModuleXbmc.h" +%include "interfaces/legacy/Dictionary.h" + +%feature("director") Player; + +%feature("python:nokwds") XBMCAddon::xbmc::Keyboard::Keyboard "true" +%feature("python:nokwds") XBMCAddon::xbmc::Player::Player "true" +%feature("python:nokwds") XBMCAddon::xbmc::PlayList::PlayList "true" + +%include "interfaces/legacy/Player.h" + +%include "interfaces/legacy/RenderCapture.h" + +%include "interfaces/legacy/InfoTagGame.h" +%include "interfaces/legacy/InfoTagMusic.h" +%include "interfaces/legacy/InfoTagPicture.h" +%include "interfaces/legacy/InfoTagRadioRDS.h" +%include "interfaces/legacy/InfoTagVideo.h" +%include "interfaces/legacy/Keyboard.h" +%include "interfaces/legacy/PlayList.h" + +%feature("director") Monitor; + +%include "interfaces/legacy/Monitor.h" + + diff --git a/xbmc/interfaces/swig/AddonModuleXbmcaddon.i b/xbmc/interfaces/swig/AddonModuleXbmcaddon.i new file mode 100644 index 0000000..6c00a1c --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcaddon.i @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +%module xbmcaddon + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/Addon.h" +#include "interfaces/legacy/Settings.h" + +using namespace XBMCAddon; +using namespace xbmcaddon; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +// This is all about warning suppression. It's OK that these base classes are +// not part of what swig parses. +%feature("knownbasetypes") XBMCAddon::xbmcaddon "AddonClass" + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" + +%include "interfaces/legacy/Addon.h" +%include "interfaces/legacy/Settings.h" + diff --git a/xbmc/interfaces/swig/AddonModuleXbmcdrm.i b/xbmc/interfaces/swig/AddonModuleXbmcdrm.i new file mode 100644 index 0000000..055652a --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcdrm.i @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +%module xbmcdrm + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/DrmCryptoSession.h" +#include "utils/log.h" + +using namespace XBMCAddon; +using namespace xbmcdrm; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" +%include "interfaces/legacy/DrmCryptoSession.h" diff --git a/xbmc/interfaces/swig/AddonModuleXbmcgui.i b/xbmc/interfaces/swig/AddonModuleXbmcgui.i new file mode 100644 index 0000000..b869401 --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcgui.i @@ -0,0 +1,111 @@ +/* + * 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. + */ + +%module(directors="1") xbmcgui + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/Dialog.h" +#include "interfaces/legacy/ModuleXbmcgui.h" +#include "interfaces/legacy/Control.h" +#include "interfaces/legacy/Window.h" +#include "interfaces/legacy/WindowDialog.h" +#include "interfaces/legacy/Dialog.h" +#include "interfaces/legacy/WindowXML.h" +#include "input/actions/ActionIDs.h" +#include "input/Key.h" + +using namespace XBMCAddon; +using namespace xbmcgui; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +// This is all about warning suppression. It's OK that these base classes are +// not part of what swig parses. +%feature("knownbasetypes") XBMCAddon::xbmcgui "AddonClass,AddonCallback" + +%feature("knownapitypes") XBMCAddon::xbmcgui "XBMCAddon::xbmc::InfoTagVideo,xbmc::InfoTagMusic,xbmc::InfoTagPicture,xbmc::InfoTagGame" + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" + +%include "interfaces/legacy/ModuleXbmcgui.h" + +%include "interfaces/legacy/Exception.h" + +%include "interfaces/legacy/Dictionary.h" + +%include "interfaces/legacy/ListItem.h" + +%include "interfaces/legacy/Control.h" + +%include "interfaces/legacy/Dialog.h" + +%feature("python:nokwds") XBMCAddon::xbmcgui::Dialog::Dialog "true" +%feature("python:nokwds") XBMCAddon::xbmcgui::Window::Window "true" +%feature("python:nokwds") XBMCAddon::xbmcgui::WindowXML::WindowXML "true" +%feature("python:nokwds") XBMCAddon::xbmcgui::WindowXMLDialog::WindowXMLDialog "true" +%feature("python:nokwds") XBMCAddon::xbmcgui::WindowDialog::WindowDialog "true" + +%feature("director") Window; +%feature("director") WindowDialog; +%feature("director") WindowXML; +%feature("director") WindowXMLDialog; + +// This is such a damn hack it makes me nauseous +%feature("python:rcmp") XBMCAddon::xbmcgui::Action + { XBMC_TRACE; + if (method == Py_EQ) + { + XBMCAddon::xbmcgui::Action* a1 = (Action*)retrieveApiInstance(obj1,&TyXBMCAddon_xbmcgui_Action_Type,"rcmp","XBMCAddon::xbmcgui::Action"); + if (PyObject_TypeCheck(obj2, &(TyXBMCAddon_xbmcgui_Action_Type.pythonType))) + { + // both are Action objects + XBMCAddon::xbmcgui::Action* a2 = (Action*)retrieveApiInstance(obj2,&TyXBMCAddon_xbmcgui_Action_Type,"rcmp","XBMCAddon::xbmcgui::Action"); + + if (a1->id == a2->id && + a1->buttonCode == a2->buttonCode && + a1->fAmount1 == a2->fAmount1 && + a1->fAmount2 == a2->fAmount2 && + a1->fRepeat == a2->fRepeat && + a1->strAction == a2->strAction) + { + Py_RETURN_TRUE; + } + else + { + Py_RETURN_FALSE; + } + } + else + { + // for backwards compatibility in python scripts + PyObject* o1 = PyLong_FromLong(a1->id); + return PyObject_RichCompare(o1, obj2, method); + } + } + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + +%include "interfaces/legacy/Window.h" +%include "interfaces/legacy/WindowDialog.h" +%include "interfaces/legacy/Dialog.h" + +%include "interfaces/legacy/WindowXML.h" + +%include "input/actions/ActionIDs.h" +%include "input/Key.h" diff --git a/xbmc/interfaces/swig/AddonModuleXbmcplugin.i b/xbmc/interfaces/swig/AddonModuleXbmcplugin.i new file mode 100644 index 0000000..24e42b1 --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcplugin.i @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +%module xbmcplugin + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/ModuleXbmcplugin.h" + +using namespace XBMCAddon; +using namespace xbmcplugin; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +%feature("knownapitypes") XBMCAddon::xbmcplugin "XBMCAddon::xbmcgui::ListItem" + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" +%include "interfaces/legacy/ModuleXbmcplugin.h" + diff --git a/xbmc/interfaces/swig/AddonModuleXbmcvfs.i b/xbmc/interfaces/swig/AddonModuleXbmcvfs.i new file mode 100644 index 0000000..bf18f38 --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcvfs.i @@ -0,0 +1,44 @@ +/* + * 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. + */ + +%module xbmcvfs + +%{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#include "interfaces/legacy/ModuleXbmcvfs.h" +#include "interfaces/legacy/File.h" +#include "interfaces/legacy/Stat.h" +#include "utils/log.h" + +using namespace XBMCAddon; +using namespace xbmcvfs; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" + +%feature("python:strictUnicode") XBMCAddon::xbmcvfs::File::read "true" + +%include "interfaces/legacy/File.h" + +%rename ("st_atime") XBMCAddon::xbmcvfs::Stat::atime; +%rename ("st_mtime") XBMCAddon::xbmcvfs::Stat::mtime; +%rename ("st_ctime") XBMCAddon::xbmcvfs::Stat::ctime; +%include "interfaces/legacy/Stat.h" + +%rename ("delete") XBMCAddon::xbmcvfs::deleteFile; +%include "interfaces/legacy/ModuleXbmcvfs.h" + diff --git a/xbmc/interfaces/swig/AddonModuleXbmcwsgi.i b/xbmc/interfaces/swig/AddonModuleXbmcwsgi.i new file mode 100644 index 0000000..6df84e0 --- /dev/null +++ b/xbmc/interfaces/swig/AddonModuleXbmcwsgi.i @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +%begin %{ +#if defined(TARGET_WINDOWS) +# include <windows.h> +#endif + +#ifdef HAS_WEB_SERVER +%} + +%module xbmcwsgi + +%{ +#include "interfaces/legacy/wsgi/WsgiErrorStream.h" +#include "interfaces/legacy/wsgi/WsgiInputStream.h" +#include "interfaces/legacy/wsgi/WsgiResponse.h" +#include "interfaces/legacy/wsgi/WsgiResponseBody.h" + +using namespace XBMCAddon; +using namespace xbmcwsgi; + +#if defined(__GNUG__) +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + +%} + +// This is all about warning suppression. It's OK that these base classes are +// not part of what swig parses. +%feature("knownbasetypes") XBMCAddon::xbmcaddon "AddonClass" + +%feature("iterator") WsgiInputStreamIterator "std::string" +%feature("iterable") WsgiInputStream "XBMCAddon::xbmcwsgi::WsgiInputStreamIterator" + +%include "interfaces/legacy/swighelper.h" +%include "interfaces/legacy/AddonString.h" + +%include "interfaces/legacy/wsgi/WsgiErrorStream.h" +%include "interfaces/legacy/wsgi/WsgiInputStream.h" +%include "interfaces/legacy/wsgi/WsgiResponse.h" +%include "interfaces/legacy/wsgi/WsgiResponseBody.h" + +%insert("footer") %{ +#endif +%} + diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt new file mode 100644 index 0000000..f89eb98 --- /dev/null +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -0,0 +1,63 @@ +function(generate_file file) + set(classpath ${GROOVY_DIR}/groovy-${GROOVY_VER}.jar + ${GROOVY_DIR}/groovy-xml-${GROOVY_VER}.jar + ${GROOVY_DIR}/groovy-templates-${GROOVY_VER}.jar + ${GROOVY_DIR}/commons-lang-${COMMONS_VER}.jar + ${CMAKE_SOURCE_DIR}/tools/codegenerator + ${CMAKE_CURRENT_SOURCE_DIR}/../python) + if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) + set(devnull "/dev/null") + string(REPLACE ";" ":" classpath "${classpath}") + else() + set(devnull "nul") + endif() + + set(CPP_FILE ${file}.cpp) + if(CLANGFORMAT_FOUND) + set(CLANG_FORMAT_COMMAND COMMAND ${CLANG_FORMAT_EXECUTABLE} ARGS -i ${CPP_FILE}) + endif() + + if(Java_VERSION_MAJOR GREATER 8) + set(JAVA_OPEN_OPTS --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.util.regex=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED) + endif() + + add_custom_command(OUTPUT ${CPP_FILE} + COMMAND ${SWIG_EXECUTABLE} + ARGS -w401 -c++ -o ${file}.xml -xml -I${CMAKE_SOURCE_DIR}/xbmc -xmllang python ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} + COMMAND ${Java_JAVA_EXECUTABLE} + ARGS ${JAVA_OPEN_OPTS} -cp "${classpath}" groovy.ui.GroovyMain ${CMAKE_SOURCE_DIR}/tools/codegenerator/Generator.groovy ${file}.xml ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template ${file}.cpp > ${devnull} + ${CLANG_FORMAT_COMMAND} + DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/../swig/${file} ${CMAKE_CURRENT_SOURCE_DIR}/../python/PythonSwig.cpp.template) + set(SOURCES ${SOURCES} "${CPP_FILE}" PARENT_SCOPE) +endfunction() + +find_package(Java COMPONENTS Runtime REQUIRED) +find_package(SWIG REQUIRED) + +# The generated bindings +set(INPUTS AddonModuleXbmcaddon.i + AddonModuleXbmcdrm.i + AddonModuleXbmcgui.i + AddonModuleXbmc.i + AddonModuleXbmcplugin.i + AddonModuleXbmcvfs.i + AddonModuleXbmcwsgi.i) + +set(GROOVY_DIR ${CMAKE_SOURCE_DIR}/tools/codegenerator/groovy) +set(GROOVY_VER 4.0.6) +set(COMMONS_VER 2.6) + +foreach(INPUT IN LISTS INPUTS) + generate_file(${INPUT}) + list(APPEND GEN_SRCS ${CMAKE_CURRENT_BINARY_DIR}/${INPUT}.cpp) +endforeach() + +add_library(python_binding STATIC ${SOURCES}) +set_target_properties(python_binding PROPERTIES POSITION_INDEPENDENT_CODE TRUE + FOLDER "Build Utilities") +set(core_DEPENDS python_binding ${core_DEPENDS} CACHE STRING "" FORCE) +add_dependencies(python_binding ${GLOBAL_TARGET_DEPS}) + +if(CORE_SYSTEM_NAME STREQUAL windowsstore) + set_target_properties(python_binding PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") +endif() |