From 75f69ac275ddfa9382f02a3ffbc96861d6c5e4d5 Mon Sep 17 00:00:00 2001 From: repojohnray <8113421+repojohnray@users.noreply.github.com> Date: Mon, 16 Jan 2023 13:09:31 +0100 Subject: [PATCH 001/811] [CGUIDialogContextMenu] fix memory leak This memory leak is happening when a context menu is opened. This change addresses two issues: - Missing delete. - Group list not defined (the behavior is changed). --- xbmc/dialogs/GUIDialogContextMenu.cpp | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/xbmc/dialogs/GUIDialogContextMenu.cpp b/xbmc/dialogs/GUIDialogContextMenu.cpp index 76ceefc287903..dcc9edec3459a 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.cpp +++ b/xbmc/dialogs/GUIDialogContextMenu.cpp @@ -126,17 +126,17 @@ void CGUIDialogContextMenu::SetupButtons() CGUIControlGroupList* pGroupList = dynamic_cast(GetControl(GROUP_LIST)); // add our buttons - for (unsigned int i = 0; i < m_buttons.size(); i++) + if (pGroupList) { - CGUIButtonControl *pButton = new CGUIButtonControl(*pButtonTemplate); - if (pButton) - { // set the button's ID and position - int id = BUTTON_START + i; - pButton->SetID(id); - pButton->SetVisible(true); - pButton->SetLabel(m_buttons[i].second); - if (pGroupList) - { + for (unsigned int i = 0; i < m_buttons.size(); i++) + { + CGUIButtonControl* pButton = new CGUIButtonControl(*pButtonTemplate); + if (pButton) + { // set the button's ID and position + int id = BUTTON_START + i; + pButton->SetID(id); + pButton->SetVisible(true); + pButton->SetLabel(m_buttons[i].second); pButton->SetPosition(pButtonTemplate->GetXPosition(), pButtonTemplate->GetYPosition()); // try inserting context buttons at position specified by template // button, if template button is not in grouplist fallback to adding @@ -591,7 +591,10 @@ void CGUIDialogContextMenu::OnDeinitWindow(int nextWindowID) { const CGUIControl *control = GetControl(BUTTON_START + i); if (control) + { RemoveControl(control); + delete control; + } } m_buttons.clear(); From bec470a7b9bb4f169fc6aa3a09c48392a0d4528c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:48:20 +0200 Subject: [PATCH 002/811] CUtil: Optimize ValidatePath + MakeLegal(FileName|Path) for move semantic --- xbmc/Util.cpp | 60 +++++++++---------- xbmc/Util.h | 13 ++-- xbmc/cdrip/CDDARipper.cpp | 2 +- xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp | 2 +- xbmc/filesystem/SpecialProtocol.cpp | 2 +- xbmc/music/windows/GUIWindowMusicPlaylist.cpp | 2 +- xbmc/peripherals/devices/Peripheral.cpp | 10 ++-- .../platform/win32/threads/Win32Exception.cpp | 10 ++-- xbmc/playlists/PlayListB4S.cpp | 2 +- xbmc/utils/FileOperationJob.cpp | 2 +- xbmc/video/VideoDatabase.cpp | 2 +- xbmc/video/windows/GUIWindowVideoPlaylist.cpp | 2 +- 12 files changed, 56 insertions(+), 53 deletions(-) diff --git a/xbmc/Util.cpp b/xbmc/Util.cpp index e394d18e43333..0daf54f9acb67 100644 --- a/xbmc/Util.cpp +++ b/xbmc/Util.cpp @@ -898,31 +898,29 @@ bool CUtil::CreateDirectoryEx(const std::string& strPath) return CDirectory::Exists(strPath); } -std::string CUtil::MakeLegalFileName(const std::string &strFile, int LegalType) +std::string CUtil::MakeLegalFileName(std::string strFile, int LegalType) { - std::string result = strFile; - - StringUtils::Replace(result, '/', '_'); - StringUtils::Replace(result, '\\', '_'); - StringUtils::Replace(result, '?', '_'); + StringUtils::Replace(strFile, '/', '_'); + StringUtils::Replace(strFile, '\\', '_'); + StringUtils::Replace(strFile, '?', '_'); if (LegalType == LEGAL_WIN32_COMPAT) { // just filter out some illegal characters on windows - StringUtils::Replace(result, ':', '_'); - StringUtils::Replace(result, '*', '_'); - StringUtils::Replace(result, '?', '_'); - StringUtils::Replace(result, '\"', '_'); - StringUtils::Replace(result, '<', '_'); - StringUtils::Replace(result, '>', '_'); - StringUtils::Replace(result, '|', '_'); - StringUtils::TrimRight(result, ". "); + StringUtils::Replace(strFile, ':', '_'); + StringUtils::Replace(strFile, '*', '_'); + StringUtils::Replace(strFile, '?', '_'); + StringUtils::Replace(strFile, '\"', '_'); + StringUtils::Replace(strFile, '<', '_'); + StringUtils::Replace(strFile, '>', '_'); + StringUtils::Replace(strFile, '|', '_'); + StringUtils::TrimRight(strFile, ". "); } - return result; + return strFile; } // legalize entire path -std::string CUtil::MakeLegalPath(const std::string &strPathAndFile, int LegalType) +std::string CUtil::MakeLegalPath(std::string strPathAndFile, int LegalType) { if (URIUtils::IsStack(strPathAndFile)) return MakeLegalPath(CStackDirectory::GetFirstStackedFile(strPathAndFile)); @@ -946,9 +944,8 @@ std::string CUtil::MakeLegalPath(const std::string &strPathAndFile, int LegalTyp return dir; } -std::string CUtil::ValidatePath(const std::string &path, bool bFixDoubleSlashes /* = false */) +std::string CUtil::ValidatePath(std::string path, bool bFixDoubleSlashes /* = false */) { - std::string result = path; // Don't do any stuff on URLs containing %-characters or protocols that embed // filenames. NOTE: Don't use IsInZip or IsInRar here since it will infinitely @@ -961,46 +958,47 @@ std::string CUtil::ValidatePath(const std::string &path, bool bFixDoubleSlashes StringUtils::StartsWithNoCase(path, "stack:") || StringUtils::StartsWithNoCase(path, "bluray:") || StringUtils::StartsWithNoCase(path, "multipath:") )) - return result; + return path; - // check the path for incorrect slashes + // check the path for incorrect slashes #ifdef TARGET_WINDOWS if (URIUtils::IsDOSPath(path)) { - StringUtils::Replace(result, '/', '\\'); + StringUtils::Replace(path, '/', '\\'); /* The double slash correction should only be used when *absolutely* necessary! This applies to certain DLLs or use from Python DLLs/scripts that incorrectly generate double (back) slashes. */ - if (bFixDoubleSlashes && !result.empty()) + if (bFixDoubleSlashes && !path.empty()) { // Fixup for double back slashes (but ignore the \\ of unc-paths) - for (size_t x = 1; x < result.size() - 1; x++) + for (size_t x = 1; x < path.size() - 1; x++) { - if (result[x] == '\\' && result[x+1] == '\\') - result.erase(x, 1); + if (path[x] == '\\' && path[x + 1] == '\\') + path.erase(x, 1); } } } else if (path.find("://") != std::string::npos || path.find(":\\\\") != std::string::npos) #endif { - StringUtils::Replace(result, '\\', '/'); + StringUtils::Replace(path, '\\', '/'); /* The double slash correction should only be used when *absolutely* necessary! This applies to certain DLLs or use from Python DLLs/scripts that incorrectly generate double (back) slashes. */ - if (bFixDoubleSlashes && !result.empty()) + if (bFixDoubleSlashes && !path.empty()) { // Fixup for double forward slashes(/) but don't touch the :// of URLs - for (size_t x = 2; x < result.size() - 1; x++) + for (size_t x = 2; x < path.size() - 1; x++) { - if ( result[x] == '/' && result[x + 1] == '/' && !(result[x - 1] == ':' || (result[x - 1] == '/' && result[x - 2] == ':')) ) - result.erase(x, 1); + if (path[x] == '/' && path[x + 1] == '/' && + !(path[x - 1] == ':' || (path[x - 1] == '/' && path[x - 2] == ':'))) + path.erase(x, 1); } } } - return result; + return path; } void CUtil::SplitParams(const std::string ¶mString, std::vector ¶meters) diff --git a/xbmc/Util.h b/xbmc/Util.h index fe677484812c5..88e58c48207cd 100644 --- a/xbmc/Util.h +++ b/xbmc/Util.h @@ -99,13 +99,16 @@ class CUtil static bool CreateDirectoryEx(const std::string& strPath); #ifdef TARGET_WINDOWS - static std::string MakeLegalFileName(const std::string &strFile, int LegalType=LEGAL_WIN32_COMPAT); - static std::string MakeLegalPath(const std::string &strPath, int LegalType=LEGAL_WIN32_COMPAT); + static std::string MakeLegalFileName(std::string strFile, int LegalType = LEGAL_WIN32_COMPAT); + static std::string MakeLegalPath(std::string strPath, int LegalType = LEGAL_WIN32_COMPAT); #else - static std::string MakeLegalFileName(const std::string &strFile, int LegalType=LEGAL_NONE); - static std::string MakeLegalPath(const std::string &strPath, int LegalType=LEGAL_NONE); + static std::string MakeLegalFileName(std::string strFile, int LegalType = LEGAL_NONE); + static std::string MakeLegalPath(std::string strPath, int LegalType = LEGAL_NONE); #endif - static std::string ValidatePath(const std::string &path, bool bFixDoubleSlashes = false); ///< return a validated path, with correct directory separators. + static std::string ValidatePath( + std::string path, + bool bFixDoubleSlashes = + false); ///< return a validated path, with correct directory separators. /*! * \brief Check if a filename contains a supported font extension. diff --git a/xbmc/cdrip/CDDARipper.cpp b/xbmc/cdrip/CDDARipper.cpp index 073dd05a365d1..41dcc8d5d6102 100644 --- a/xbmc/cdrip/CDDARipper.cpp +++ b/xbmc/cdrip/CDDARipper.cpp @@ -184,7 +184,7 @@ bool CCDDARipper::CreateAlbumDir(const MUSIC_INFO::CMusicInfoTag& infoTag, URIUtils::AddSlashAtEnd(strDirectory); } - strDirectory = CUtil::MakeLegalPath(strDirectory, legalType); + strDirectory = CUtil::MakeLegalPath(std::move(strDirectory), legalType); // Create directory if it doesn't exist if (!CUtil::CreateDirectoryEx(strDirectory)) diff --git a/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp index 49d6398406745..dadd08a121087 100644 --- a/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp +++ b/xbmc/dialogs/GUIDialogSmartPlaylistEditor.cpp @@ -226,7 +226,7 @@ void CGUIDialogSmartPlaylistEditor::OnOK() if (CGUIKeyboardFactory::ShowAndGetInput(filename, CVariant{g_localizeStrings.Get(16013)}, false)) { path = URIUtils::AddFileToFolder(systemPlaylistsPath, m_playlist.GetSaveLocation(), - CUtil::MakeLegalFileName(filename)); + CUtil::MakeLegalFileName(std::move(filename))); } else return; diff --git a/xbmc/filesystem/SpecialProtocol.cpp b/xbmc/filesystem/SpecialProtocol.cpp index a53c82de197b0..189ddfa5dcac9 100644 --- a/xbmc/filesystem/SpecialProtocol.cpp +++ b/xbmc/filesystem/SpecialProtocol.cpp @@ -202,7 +202,7 @@ std::string CSpecialProtocol::TranslatePath(const CURL &url) } // Validate the final path, just in case - return CUtil::ValidatePath(translatedPath); + return CUtil::ValidatePath(std::move(translatedPath)); } std::string CSpecialProtocol::TranslatePathConvertCase(const std::string& path) diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp index 87afafa240011..c5b3004e8d615 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp @@ -305,7 +305,7 @@ void CGUIWindowMusicPlayList::SavePlayList() std::string strNewFileName; if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) { - strNewFileName = CUtil::MakeLegalFileName(strNewFileName); + strNewFileName = CUtil::MakeLegalFileName(std::move(strNewFileName)); strNewFileName += ".m3u8"; std::string strPath = URIUtils::AddFileToFolder( CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp index 2c656a392b0b4..272a4d723a1c6 100644 --- a/xbmc/peripherals/devices/Peripheral.cpp +++ b/xbmc/peripherals/devices/Peripheral.cpp @@ -151,10 +151,10 @@ bool CPeripheral::Initialise(void) if (m_iVendorId == 0x0000 && m_iProductId == 0x0000) { - m_strSettingsFile = - StringUtils::Format("special://profile/peripheral_data/{}_{}.xml", - PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), - CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT)); + m_strSettingsFile = StringUtils::Format( + "special://profile/peripheral_data/{}_{}.xml", + PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), + CUtil::MakeLegalFileName(std::move(safeDeviceName), LEGAL_WIN32_COMPAT)); } else { @@ -167,7 +167,7 @@ bool CPeripheral::Initialise(void) m_strSettingsFile = StringUtils::Format( "special://profile/peripheral_data/{}_{}_{}_{}.xml", PeripheralTypeTranslator::BusTypeToString(m_mappedBusType), m_strVendorId, m_strProductId, - CUtil::MakeLegalFileName(safeDeviceName, LEGAL_WIN32_COMPAT)); + CUtil::MakeLegalFileName(std::move(safeDeviceName), LEGAL_WIN32_COMPAT)); } LoadPersistedSettings(); diff --git a/xbmc/platform/win32/threads/Win32Exception.cpp b/xbmc/platform/win32/threads/Win32Exception.cpp index bb80ca6d080fd..6f360eb9ac52b 100644 --- a/xbmc/platform/win32/threads/Win32Exception.cpp +++ b/xbmc/platform/win32/threads/Win32Exception.cpp @@ -73,8 +73,9 @@ bool win32_exception::write_minidump(EXCEPTION_POINTERS* pEp) mVersion, stLocalTime.year, stLocalTime.month, stLocalTime.day, stLocalTime.hour, stLocalTime.minute, stLocalTime.second); - dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder( - CWIN32Util::GetProfilePath(m_platformDir), CUtil::MakeLegalFileName(dumpFileName))); + dumpFileName = CWIN32Util::SmbToUnc( + URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(m_platformDir), + CUtil::MakeLegalFileName(std::move(dumpFileName)))); dumpFileNameW = KODI::PLATFORM::WINDOWS::ToW(dumpFileName); HANDLE hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); @@ -189,8 +190,9 @@ bool win32_exception::write_stacktrace(EXCEPTION_POINTERS* pEp) mVersion, stLocalTime.year, stLocalTime.month, stLocalTime.day, stLocalTime.hour, stLocalTime.minute, stLocalTime.second); - dumpFileName = CWIN32Util::SmbToUnc(URIUtils::AddFileToFolder( - CWIN32Util::GetProfilePath(m_platformDir), CUtil::MakeLegalFileName(dumpFileName))); + dumpFileName = CWIN32Util::SmbToUnc( + URIUtils::AddFileToFolder(CWIN32Util::GetProfilePath(m_platformDir), + CUtil::MakeLegalFileName(std::move(dumpFileName)))); dumpFileNameW = KODI::PLATFORM::WINDOWS::ToW(dumpFileName); hDumpFile = CreateFileW(dumpFileNameW.c_str(), GENERIC_WRITE, 0, 0, CREATE_ALWAYS, 0, 0); diff --git a/xbmc/playlists/PlayListB4S.cpp b/xbmc/playlists/PlayListB4S.cpp index 52a06fb610530..693e62f81b0f3 100644 --- a/xbmc/playlists/PlayListB4S.cpp +++ b/xbmc/playlists/PlayListB4S.cpp @@ -105,7 +105,7 @@ void CPlayListB4S::Save(const std::string& strFileName) const { if (!m_vecItems.size()) return ; std::string strPlaylist = strFileName; - strPlaylist = CUtil::MakeLegalPath(strPlaylist); + strPlaylist = CUtil::MakeLegalPath(std::move(strPlaylist)); CFile file; if (!file.OpenForWrite(strPlaylist, true)) { diff --git a/xbmc/utils/FileOperationJob.cpp b/xbmc/utils/FileOperationJob.cpp index 7313a1892c6fa..8b5066a7e0428 100644 --- a/xbmc/utils/FileOperationJob.cpp +++ b/xbmc/utils/FileOperationJob.cpp @@ -168,7 +168,7 @@ bool CFileOperationJob::DoProcess(FileAction action, strFileName += URIUtils::GetExtension(pItem->GetPath()); } - strFileName = CUtil::MakeLegalFileName(strFileName); + strFileName = CUtil::MakeLegalFileName(std::move(strFileName)); } std::string strnewDestFile; diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 2bb425c4d013c..ae6205f6bec4e 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -10927,7 +10927,7 @@ std::string CVideoDatabase::GetSafeFile(const std::string &dir, const std::strin { std::string safeThumb(name); StringUtils::Replace(safeThumb, ' ', '_'); - return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(safeThumb)); + return URIUtils::AddFileToFolder(dir, CUtil::MakeLegalFileName(std::move(safeThumb))); } void CVideoDatabase::AnnounceRemove(const std::string& content, int id, bool scanning /* = false */) diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp index 4fa3633f12a16..6b96fde98f282 100644 --- a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp @@ -418,7 +418,7 @@ void CGUIWindowVideoPlaylist::SavePlayList() if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) { // need 2 rename it - strNewFileName = CUtil::MakeLegalFileName(strNewFileName); + strNewFileName = CUtil::MakeLegalFileName(std::move(strNewFileName)); strNewFileName += ".m3u8"; std::string strPath = URIUtils::AddFileToFolder( CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), From fab2972ba8a98b59539a4210fb6352323c027717 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:45:58 +0200 Subject: [PATCH 003/811] CURL: Take advantage of std::string move semantic --- xbmc/URL.cpp | 44 ++++++++++++++++++++++---------------------- xbmc/URL.h | 41 +++++++++++++---------------------------- 2 files changed, 35 insertions(+), 50 deletions(-) diff --git a/xbmc/URL.cpp b/xbmc/URL.cpp index 877c9d9585746..99b661e36f835 100644 --- a/xbmc/URL.cpp +++ b/xbmc/URL.cpp @@ -49,11 +49,11 @@ void CURL::Reset() m_iPort = 0; } -void CURL::Parse(const std::string& strURL1) +void CURL::Parse(std::string strURL1) { Reset(); // start by validating the path - std::string strURL = CUtil::ValidatePath(strURL1); + std::string strURL = CUtil::ValidatePath(std::move(strURL1)); // strURL can be one of the following: // format 1: protocol://[username:password]@hostname[:port]/directoryandfile @@ -88,7 +88,7 @@ void CURL::Parse(const std::string& strURL1) if (iPos == std::string::npos) { /* set filename and update extension*/ - SetFileName(strURL); + SetFileName(std::move(strURL)); return ; } iPos += extLen + 1; @@ -105,12 +105,12 @@ void CURL::Parse(const std::string& strURL1) archiveName = Encode(archiveName); if (is_apk) { - CURL c("apk://" + archiveName + "/" + strURL.substr(iPos + 1)); + CURL c("apk://" + archiveName + "/" + std::move(strURL).substr(iPos + 1)); *this = c; } else { - CURL c("zip://" + archiveName + "/" + strURL.substr(iPos + 1)); + CURL c("zip://" + archiveName + "/" + std::move(strURL).substr(iPos + 1)); *this = c; } return; @@ -137,7 +137,7 @@ void CURL::Parse(const std::string& strURL1) IsProtocol("resource") ) { - SetFileName(strURL.substr(iPos)); + SetFileName(std::move(strURL).substr(iPos)); return; } @@ -150,7 +150,7 @@ void CURL::Parse(const std::string& strURL1) isoPos = lower.find(".udf\\", iPos); if (isoPos != std::string::npos) { - strURL = strURL.replace(isoPos + 4, 1, "/"); + strURL.replace(isoPos + 4, 1, "/"); } } @@ -242,7 +242,7 @@ void CURL::Parse(const std::string& strURL1) // username else { - m_strUserName = strUserNamePassword; + m_strUserName = std::move(strUserNamePassword); } iPos = iAlphaSign + 1; @@ -275,7 +275,7 @@ void CURL::Parse(const std::string& strURL1) // if we still don't have hostname, the strHostNameAndPort substring // is 'just' hostname without :port specification - so use it as is. if (m_strHostName.empty()) - m_strHostName = strHostNameAndPort; + m_strHostName = std::move(strHostNameAndPort); if (iSlash != std::string::npos) { @@ -317,9 +317,9 @@ void CURL::Parse(const std::string& strURL1) m_strPassword = Decode(m_strPassword); } -void CURL::SetFileName(const std::string& strFileName) +void CURL::SetFileName(std::string strFileName) { - m_strFileName = strFileName; + m_strFileName = std::move(strFileName); size_t slash = m_strFileName.find_last_of(GetDirectorySeparator()); size_t period = m_strFileName.find_last_of('.'); @@ -338,13 +338,13 @@ void CURL::SetFileName(const std::string& strFileName) StringUtils::ToLower(m_strFileType); } -void CURL::SetProtocol(const std::string& strProtocol) +void CURL::SetProtocol(std::string strProtocol) { - m_strProtocol = strProtocol; + m_strProtocol = std::move(strProtocol); StringUtils::ToLower(m_strProtocol); } -void CURL::SetOptions(const std::string& strOptions) +void CURL::SetOptions(std::string strOptions) { m_strOptions.clear(); m_options.Clear(); @@ -355,7 +355,7 @@ void CURL::SetOptions(const std::string& strOptions) strOptions[0] == ';' || strOptions.find("xml") != std::string::npos) { - m_strOptions = strOptions; + m_strOptions = std::move(strOptions); m_options.AddOptions(m_strOptions); } else @@ -363,21 +363,21 @@ void CURL::SetOptions(const std::string& strOptions) } } -void CURL::SetProtocolOptions(const std::string& strOptions) +void CURL::SetProtocolOptions(std::string strOptions) { m_strProtocolOptions.clear(); m_protocolOptions.Clear(); if (strOptions.length() > 0) { if (strOptions[0] == '|') - m_strProtocolOptions = strOptions.substr(1); + m_strProtocolOptions = std::move(strOptions).substr(1); else - m_strProtocolOptions = strOptions; + m_strProtocolOptions = std::move(strOptions); m_protocolOptions.AddOptions(m_strProtocolOptions); } } -const std::string CURL::GetTranslatedProtocol() const +std::string CURL::GetTranslatedProtocol() const { if (IsProtocol("shout") || IsProtocol("dav") @@ -391,7 +391,7 @@ const std::string CURL::GetTranslatedProtocol() const return GetProtocol(); } -const std::string CURL::GetFileNameWithoutPath() const +std::string CURL::GetFileNameWithoutPath() const { // *.zip and *.rar store the actual zip/rar path in the hostname of the url if ((IsProtocol("rar") || @@ -620,9 +620,9 @@ std::string CURL::GetRedacted() const return GetWithoutUserDetails(true); } -std::string CURL::GetRedacted(const std::string& path) +std::string CURL::GetRedacted(std::string path) { - return CURL(path).GetRedacted(); + return CURL(std::move(path)).GetRedacted(); } bool CURL::IsLocal() const diff --git a/xbmc/URL.h b/xbmc/URL.h index af0be6fb12918..ae84335cc74ff 100644 --- a/xbmc/URL.h +++ b/xbmc/URL.h @@ -21,10 +21,7 @@ class CURL { public: - explicit CURL(const std::string& strURL) - { - Parse(strURL); - } + explicit CURL(std::string strURL) { Parse(std::move(strURL)); } CURL() = default; virtual ~CURL(void); @@ -33,31 +30,19 @@ class CURL bool operator==(const std::string &url) const { return Get() == url; } void Reset(); - void Parse(const std::string& strURL); - void SetFileName(const std::string& strFileName); - void SetHostName(const std::string& strHostName) - { - m_strHostName = strHostName; - } + void Parse(std::string strURL); + void SetFileName(std::string strFileName); + void SetHostName(std::string strHostName) { m_strHostName = std::move(strHostName); } - void SetUserName(const std::string& strUserName) - { - m_strUserName = strUserName; - } + void SetUserName(std::string strUserName) { m_strUserName = std::move(strUserName); } - void SetDomain(const std::string& strDomain) - { - m_strDomain = strDomain; - } + void SetDomain(std::string strDomain) { m_strDomain = std::move(strDomain); } - void SetPassword(const std::string& strPassword) - { - m_strPassword = strPassword; - } + void SetPassword(std::string strPassword) { m_strPassword = std::move(strPassword); } - void SetProtocol(const std::string& strProtocol); - void SetOptions(const std::string& strOptions); - void SetProtocolOptions(const std::string& strOptions); + void SetProtocol(std::string strProtocol); + void SetOptions(std::string strOptions); + void SetProtocolOptions(std::string strOptions); void SetPort(int port) { m_iPort = port; @@ -103,7 +88,7 @@ class CURL return m_strProtocol; } - const std::string GetTranslatedProtocol() const; + std::string GetTranslatedProtocol() const; const std::string& GetFileType() const { @@ -125,7 +110,7 @@ class CURL return m_strProtocolOptions; } - const std::string GetFileNameWithoutPath() const; /* return the filename excluding path */ + std::string GetFileNameWithoutPath() const; /* return the filename excluding path */ char GetDirectorySeparator() const; @@ -134,7 +119,7 @@ class CURL std::string GetWithoutUserDetails(bool redact = false) const; std::string GetWithoutFilename() const; std::string GetRedacted() const; - static std::string GetRedacted(const std::string& path); + static std::string GetRedacted(std::string path); bool IsLocal() const; bool IsLocalHost() const; static bool IsFileOnly(const std::string &url); ///< return true if there are no directories in the url. From 6869dc34c6ea8160253629dabde779cb59c537a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 28 May 2023 21:01:12 +0200 Subject: [PATCH 004/811] dbiplus::field_value: Remove temporary in get_asString() that my prevent ROV Any decent compiler should be able to perform NROV but better be save than sorry. --- xbmc/dbwrappers/qry_dat.cpp | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp index 11d7e237e7a28..4a57a70105c59 100644 --- a/xbmc/dbwrappers/qry_dat.cpp +++ b/xbmc/dbwrappers/qry_dat.cpp @@ -171,69 +171,67 @@ field_value::~field_value() = default; //Conversations functions std::string field_value::get_asString() const { - std::string tmp; switch (field_type) { case ft_String: { - tmp = str_value; - return tmp; + return str_value; } case ft_Boolean: { if (bool_value) - return tmp = "True"; + return "True"; else - return tmp = "False"; + return "False"; } case ft_Char: { - return tmp = char_value; + return {char_value}; } case ft_Short: { char t[10]; snprintf(t, sizeof(t), "%i", short_value); - return tmp = t; + return t; } case ft_UShort: { char t[10]; snprintf(t, sizeof(t), "%i", ushort_value); - return tmp = t; + return t; } case ft_Int: { char t[12]; snprintf(t, sizeof(t), "%d", int_value); - return tmp = t; + return t; } case ft_UInt: { char t[12]; snprintf(t, sizeof(t), "%u", uint_value); - return tmp = t; + return t; } case ft_Float: { char t[16]; snprintf(t, sizeof(t), "%f", static_cast(float_value)); - return tmp = t; + return t; } case ft_Double: { char t[32]; snprintf(t, sizeof(t), "%f", double_value); - return tmp = t; + return t; } case ft_Int64: { char t[23]; snprintf(t, sizeof(t), "%" PRId64, int64_value); - return tmp = t; + return t; } default: - return tmp = ""; + return ""; } } From b10e83f7697af47edff343c9b8b0a86e5ea6178f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 28 May 2023 21:12:14 +0200 Subject: [PATCH 005/811] dbiplus::field_value: Overload get_asString() for rvalues --- xbmc/dbwrappers/qry_dat.cpp | 13 ++++++++++++- xbmc/dbwrappers/qry_dat.h | 3 ++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp index 4a57a70105c59..4d1d82b033144 100644 --- a/xbmc/dbwrappers/qry_dat.cpp +++ b/xbmc/dbwrappers/qry_dat.cpp @@ -169,7 +169,7 @@ field_value::field_value(const field_value& fv) field_value::~field_value() = default; //Conversations functions -std::string field_value::get_asString() const +std::string field_value::get_asString() const& { switch (field_type) { @@ -235,6 +235,17 @@ std::string field_value::get_asString() const } } +std::string field_value::get_asString() && +{ + switch (field_type) + { + case ft_String: + return std::move(str_value); + default: + return get_asString(); + } +} + bool field_value::get_asBool() const { switch (field_type) diff --git a/xbmc/dbwrappers/qry_dat.h b/xbmc/dbwrappers/qry_dat.h index 150de2da7168d..b0228a8ef3d70 100644 --- a/xbmc/dbwrappers/qry_dat.h +++ b/xbmc/dbwrappers/qry_dat.h @@ -81,7 +81,8 @@ class field_value fType get_fType() const { return field_type; } bool get_isNull() const { return is_null; } - std::string get_asString() const; + std::string get_asString() const&; + std::string get_asString() &&; bool get_asBool() const; char get_asChar() const; short get_asShort() const; From 5f9801545f1f2641b3f28cc90292205746230365 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:23:02 +0200 Subject: [PATCH 006/811] dbiplus::Dataset: Return by const reference instead of const value --- xbmc/dbwrappers/dataset.cpp | 4 ++-- xbmc/dbwrappers/dataset.h | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/xbmc/dbwrappers/dataset.cpp b/xbmc/dbwrappers/dataset.cpp index 4b4acb4110a30..f559da03eb63f 100644 --- a/xbmc/dbwrappers/dataset.cpp +++ b/xbmc/dbwrappers/dataset.cpp @@ -357,7 +357,7 @@ bool Dataset::set_field_value(const char* f_name, const field_value& value) // return false; } -const field_value Dataset::get_field_value(const char* f_name) +const field_value& Dataset::get_field_value(const char* f_name) { if (ds_state != dsInactive) { @@ -391,7 +391,7 @@ const field_value Dataset::get_field_value(const char* f_name) throw DbErrors("Dataset state is Inactive"); } -const field_value Dataset::get_field_value(int index) +const field_value& Dataset::get_field_value(int index) { if (ds_state != dsInactive) { diff --git a/xbmc/dbwrappers/dataset.h b/xbmc/dbwrappers/dataset.h index 5e420b14bed80..20d02b73e2e36 100644 --- a/xbmc/dbwrappers/dataset.h +++ b/xbmc/dbwrappers/dataset.h @@ -388,11 +388,11 @@ class Dataset // virtual char *field_name(int f_index) { return field_by_index(f_index)->get_field_name(); } /* Getting value of field for current record */ - virtual const field_value get_field_value(const char* f_name); - virtual const field_value get_field_value(int index); + virtual const field_value& get_field_value(const char* f_name); + virtual const field_value& get_field_value(int index); /* Alias to get_field_value */ - const field_value fv(const char* f) { return get_field_value(f); } - const field_value fv(int index) { return get_field_value(index); } + const field_value& fv(const char* f) { return get_field_value(f); } + const field_value& fv(int index) { return get_field_value(index); } /* ------------ for transaction ------------------- */ void set_autocommit(bool v) { autocommit = v; } From c0c00cdb8b4248bb3971c3aafa679d8126e44c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 28 May 2023 21:16:54 +0200 Subject: [PATCH 007/811] dbiplus::Dataset: Remove 'const' when returning by value --- xbmc/dbwrappers/dataset.cpp | 5 ++--- xbmc/dbwrappers/dataset.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/xbmc/dbwrappers/dataset.cpp b/xbmc/dbwrappers/dataset.cpp index f559da03eb63f..69aa5efd374b1 100644 --- a/xbmc/dbwrappers/dataset.cpp +++ b/xbmc/dbwrappers/dataset.cpp @@ -421,14 +421,13 @@ const sql_record* Dataset::get_sql_record() return result.records[frecno]; } -const field_value Dataset::f_old(const char* f_name) +field_value Dataset::f_old(const char* f_name) { if (ds_state != dsInactive) for (int unsigned i = 0; i < fields_object->size(); i++) if ((*fields_object)[i].props.name == f_name) return (*fields_object)[i].val; - field_value fv; - return fv; + return {}; } void Dataset::setParamList(const ParamList& params) diff --git a/xbmc/dbwrappers/dataset.h b/xbmc/dbwrappers/dataset.h index 20d02b73e2e36..03ced6394a346 100644 --- a/xbmc/dbwrappers/dataset.h +++ b/xbmc/dbwrappers/dataset.h @@ -266,7 +266,7 @@ class Dataset void parse_sql(std::string& sql); /* Returns old field value (for :OLD) */ - virtual const field_value f_old(const char* f); + virtual field_value f_old(const char* f); /* fast string tolower helper */ char* str_toLower(char* s); From 5f379cb4568147e1fe19c19db904a43dd1e00ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:26:47 +0200 Subject: [PATCH 008/811] dbiplus::field_value: Add move constructor and assignment operator --- xbmc/dbwrappers/qry_dat.cpp | 22 ++++++++++++++++++++++ xbmc/dbwrappers/qry_dat.h | 2 ++ 2 files changed, 24 insertions(+) diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp index 4d1d82b033144..7527c6c8e2ef6 100644 --- a/xbmc/dbwrappers/qry_dat.cpp +++ b/xbmc/dbwrappers/qry_dat.cpp @@ -165,6 +165,11 @@ field_value::field_value(const field_value& fv) is_null = fv.get_isNull(); } +field_value::field_value(field_value&& fv) noexcept +{ + *this = std::move(fv); +} + //empty destructor field_value::~field_value() = default; @@ -784,6 +789,23 @@ field_value& field_value::operator=(const field_value& fv) } } +field_value& field_value::operator=(field_value&& fv) noexcept +{ + if (this == &fv) + return *this; + + is_null = fv.get_isNull(); + + switch (fv.get_fType()) + { + case ft_String: + set_asString(std::move(fv.str_value)); + return *this; + default: + return *this = fv; + } +} + //Set functions void field_value::set_asString(const char* s) { diff --git a/xbmc/dbwrappers/qry_dat.h b/xbmc/dbwrappers/qry_dat.h index b0228a8ef3d70..dc7255fbfc0df 100644 --- a/xbmc/dbwrappers/qry_dat.h +++ b/xbmc/dbwrappers/qry_dat.h @@ -77,6 +77,7 @@ class field_value explicit field_value(const double d); explicit field_value(const int64_t i); field_value(const field_value& fv); + field_value(field_value&& fv) noexcept; ~field_value(); fType get_fType() const { return field_type; } @@ -144,6 +145,7 @@ class field_value return *this; } field_value& operator=(const field_value& fv); + field_value& operator=(field_value&& fv) noexcept; //class ostream; friend std::ostream& operator<<(std::ostream& os, const field_value& fv) From 44a6a9e33e4e032870198a9f98fb0ebfc8137b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:34:42 +0200 Subject: [PATCH 009/811] dbiplus::field_value: Add overloads for c-strings with known length --- xbmc/dbwrappers/qry_dat.cpp | 11 +++++++++++ xbmc/dbwrappers/qry_dat.h | 2 ++ 2 files changed, 13 insertions(+) diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp index 7527c6c8e2ef6..39432c55a3ea4 100644 --- a/xbmc/dbwrappers/qry_dat.cpp +++ b/xbmc/dbwrappers/qry_dat.cpp @@ -105,6 +105,11 @@ field_value::field_value(const int64_t i) is_null = false; } +field_value::field_value(const char* s, std::size_t len) + : field_type(ft_String), str_value(s, len), is_null(false) +{ +} + field_value::field_value(const field_value& fv) { switch (fv.get_fType()) @@ -813,6 +818,12 @@ void field_value::set_asString(const char* s) field_type = ft_String; } +void field_value::set_asString(const char* s, std::size_t len) +{ + str_value = std::string_view(s, len); + field_type = ft_String; +} + void field_value::set_asString(const std::string& s) { str_value = s; diff --git a/xbmc/dbwrappers/qry_dat.h b/xbmc/dbwrappers/qry_dat.h index dc7255fbfc0df..b73c79f78a6a3 100644 --- a/xbmc/dbwrappers/qry_dat.h +++ b/xbmc/dbwrappers/qry_dat.h @@ -76,6 +76,7 @@ class field_value explicit field_value(const float f); explicit field_value(const double d); explicit field_value(const int64_t i); + field_value(const char* s, std::size_t len); field_value(const field_value& fv); field_value(field_value&& fv) noexcept; ~field_value(); @@ -212,6 +213,7 @@ class field_value void set_isNull() { is_null = true; } void set_asString(const char* s); + void set_asString(const char* s, std::size_t len); void set_asString(const std::string& s); void set_asBool(const bool b); void set_asChar(const char c); From fd575a81ff685d05ea8fa33ace9975821bddd3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:38:40 +0200 Subject: [PATCH 010/811] SqliteDataset: Use field_value::set_asString() with length --- xbmc/dbwrappers/sqlitedataset.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xbmc/dbwrappers/sqlitedataset.cpp b/xbmc/dbwrappers/sqlitedataset.cpp index a7100bca79fa7..254286833505b 100644 --- a/xbmc/dbwrappers/sqlitedataset.cpp +++ b/xbmc/dbwrappers/sqlitedataset.cpp @@ -915,10 +915,12 @@ bool SqliteDataset::query(const std::string& query) v.set_asDouble(sqlite3_column_double(stmt, i)); break; case SQLITE_TEXT: - v.set_asString((const char*)sqlite3_column_text(stmt, i)); + v.set_asString(reinterpret_cast(sqlite3_column_text(stmt, i)), + sqlite3_column_bytes(stmt, i)); break; case SQLITE_BLOB: - v.set_asString((const char*)sqlite3_column_text(stmt, i)); + v.set_asString(reinterpret_cast(sqlite3_column_text(stmt, i)), + sqlite3_column_bytes(stmt, i)); break; case SQLITE_NULL: default: From 1fe054a05fb0fbd29b5d0e602afeafc2b7fe2881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Mon, 29 May 2023 01:43:43 +0200 Subject: [PATCH 011/811] dbiplus::field_value: Add overloads for std::string rvalue refs --- xbmc/dbwrappers/qry_dat.cpp | 6 ++++++ xbmc/dbwrappers/qry_dat.h | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/xbmc/dbwrappers/qry_dat.cpp b/xbmc/dbwrappers/qry_dat.cpp index 39432c55a3ea4..093db8feaf38f 100644 --- a/xbmc/dbwrappers/qry_dat.cpp +++ b/xbmc/dbwrappers/qry_dat.cpp @@ -830,6 +830,12 @@ void field_value::set_asString(const std::string& s) field_type = ft_String; } +void field_value::set_asString(std::string&& s) +{ + str_value = std::move(s); + field_type = ft_String; +} + void field_value::set_asBool(const bool b) { bool_value = b; diff --git a/xbmc/dbwrappers/qry_dat.h b/xbmc/dbwrappers/qry_dat.h index b73c79f78a6a3..88d708b5c0fba 100644 --- a/xbmc/dbwrappers/qry_dat.h +++ b/xbmc/dbwrappers/qry_dat.h @@ -105,6 +105,11 @@ class field_value set_asString(s); return *this; } + field_value& operator=(std::string&& s) + { + set_asString(std::move(s)); + return *this; + } field_value& operator=(const bool b) { set_asBool(b); @@ -215,6 +220,7 @@ class field_value void set_asString(const char* s); void set_asString(const char* s, std::size_t len); void set_asString(const std::string& s); + void set_asString(std::string&& s); void set_asBool(const bool b); void set_asChar(const char c); void set_asShort(const short s); From 35cf5301b855a70fa65bdd3c6d92995a41a1c48f Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Mon, 31 Jul 2023 08:14:18 +0200 Subject: [PATCH 012/811] [StringUtils] Add Contains method --- xbmc/utils/StringUtils.cpp | 16 ++++++++++++++++ xbmc/utils/StringUtils.h | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/xbmc/utils/StringUtils.cpp b/xbmc/utils/StringUtils.cpp index 7429223183891..dce34da879bbe 100644 --- a/xbmc/utils/StringUtils.cpp +++ b/xbmc/utils/StringUtils.cpp @@ -1889,6 +1889,22 @@ std::string StringUtils::FormatFileSize(uint64_t bytes) return Format("{:.{}f}{}", value, decimals, units[i]); } +bool StringUtils::Contains(std::string_view str, + std::string_view keyword, + bool isCaseInsensitive /* = true */) +{ + if (isCaseInsensitive) + { + auto itStr = std::search(str.begin(), str.end(), keyword.begin(), keyword.end(), + [](unsigned char ch1, unsigned char ch2) { + return std::toupper(ch1) == std::toupper(ch2); + }); + return (itStr != str.end()); + } + + return str.find(keyword) != std::string_view::npos; +} + const std::locale& StringUtils::GetOriginalLocale() noexcept { return g_langInfo.GetOriginalLocale(); diff --git a/xbmc/utils/StringUtils.h b/xbmc/utils/StringUtils.h index 29d9985201a76..110988f9b0cf5 100644 --- a/xbmc/utils/StringUtils.h +++ b/xbmc/utils/StringUtils.h @@ -417,6 +417,16 @@ class StringUtils */ static std::string CreateFromCString(const char* cstr); + /*! + * \brief Check if a keyword string is contained on another string. + * \param str The string in which to search for the keyword + * \param keyword The string to search for + * \return True if the keyword if found. + */ + static bool Contains(std::string_view str, + std::string_view keyword, + bool isCaseInsensitive = true); + private: /*! * Wrapper for CLangInfo::GetOriginalLocale() which allows us to From 15fe4ccdadf4b4998a680e7bfc8d03b5ebd02a42 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Fri, 21 Jul 2023 17:55:05 +0200 Subject: [PATCH 013/811] [DVDDemux][cleanup] applied clang format --- .../VideoPlayer/DVDDemuxers/DVDDemux.cpp | 99 ++++++++++--------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp index c2ac33f496507..d901d42aa4705 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp @@ -13,59 +13,59 @@ std::string CDemuxStreamAudio::GetStreamType() std::string strInfo; switch (codec) { - case AV_CODEC_ID_AC3: - strInfo = "AC3 "; - break; - case AV_CODEC_ID_EAC3: - strInfo = "DD+ "; - break; - case AV_CODEC_ID_DTS: - { - switch (profile) + case AV_CODEC_ID_AC3: + strInfo = "AC3 "; + break; + case AV_CODEC_ID_EAC3: + strInfo = "DD+ "; + break; + case AV_CODEC_ID_DTS: { - case FF_PROFILE_DTS_HD_MA: - strInfo = "DTS-HD MA "; + switch (profile) + { + case FF_PROFILE_DTS_HD_MA: + strInfo = "DTS-HD MA "; + break; + case FF_PROFILE_DTS_HD_HRA: + strInfo = "DTS-HD HRA "; + break; + default: + strInfo = "DTS "; + break; + } + break; + } + case AV_CODEC_ID_MP2: + strInfo = "MP2 "; + break; + case AV_CODEC_ID_MP3: + strInfo = "MP3 "; + break; + case AV_CODEC_ID_TRUEHD: + strInfo = "TrueHD "; break; - case FF_PROFILE_DTS_HD_HRA: - strInfo = "DTS-HD HRA "; + case AV_CODEC_ID_AAC: + strInfo = "AAC "; + break; + case AV_CODEC_ID_ALAC: + strInfo = "ALAC "; + break; + case AV_CODEC_ID_FLAC: + strInfo = "FLAC "; + break; + case AV_CODEC_ID_OPUS: + strInfo = "Opus "; + break; + case AV_CODEC_ID_VORBIS: + strInfo = "Vorbis "; + break; + case AV_CODEC_ID_PCM_BLURAY: + case AV_CODEC_ID_PCM_DVD: + strInfo = "PCM "; break; default: - strInfo = "DTS "; + strInfo = ""; break; - } - break; - } - case AV_CODEC_ID_MP2: - strInfo = "MP2 "; - break; - case AV_CODEC_ID_MP3: - strInfo = "MP3 "; - break; - case AV_CODEC_ID_TRUEHD: - strInfo = "TrueHD "; - break; - case AV_CODEC_ID_AAC: - strInfo = "AAC "; - break; - case AV_CODEC_ID_ALAC: - strInfo = "ALAC "; - break; - case AV_CODEC_ID_FLAC: - strInfo = "FLAC "; - break; - case AV_CODEC_ID_OPUS: - strInfo = "Opus "; - break; - case AV_CODEC_ID_VORBIS: - strInfo = "Vorbis "; - break; - case AV_CODEC_ID_PCM_BLURAY: - case AV_CODEC_ID_PCM_DVD: - strInfo = "PCM "; - break; - default: - strInfo = ""; - break; } strInfo += m_channelLayoutName; @@ -79,7 +79,8 @@ int CDVDDemux::GetNrOfStreams(StreamType streamType) for (auto pStream : GetStreams()) { - if (pStream && pStream->type == streamType) iCounter++; + if (pStream && pStream->type == streamType) + iCounter++; } return iCounter; From e8dc309944ee64c941d605a7d221ca65eb5c151d Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Tue, 8 Aug 2023 07:21:54 -0400 Subject: [PATCH 014/811] [Android] MediaCodec: Add support for AV1 Dolby Vision codec selection For AV1 files with Dolby Vision stream side data, the profile must be specified as the mime type is not enough to query the correct MediaCodec codec. Also, some devices support AV1 Dolby Vision on API version < 30, so we should attempt to use the profile constant regardless of the API version. --- .../Video/DVDVideoCodecAndroidMediaCodec.cpp | 37 ++++++++++++++----- xbmc/windowing/android/AndroidUtils.cpp | 11 ++++++ xbmc/windowing/android/AndroidUtils.h | 1 + 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 0470092aac1fc..3f60d691afc3c 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -531,20 +531,17 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio if (isDvhe || isDvh1) { - bool displaySupportsDovi = CAndroidUtils::GetDisplayHDRCapabilities().SupportsDolbyVision(); - bool mediaCodecSupportsDovi = - CAndroidUtils::SupportsMediaCodecMimeType("video/dolby-vision"); - - CLog::Log(LOGDEBUG, - "CDVDVideoCodecAndroidMediaCodec::Open Dolby Vision playback support: " - "Display: {}, MediaCodec: {}", - displaySupportsDovi, mediaCodecSupportsDovi); + bool displaySupportsDovi{false}; + bool mediaCodecSupportsDovi{false}; + std::tie(displaySupportsDovi, mediaCodecSupportsDovi) = + CAndroidUtils::GetDolbyVisionCapabilities(); // For Dolby Vision profiles that don't have HDR10 fallback, always use // the dvhe decoder even if the display not supports Dolby Vision. // For profiles that has HDR10 fallback (7, 8) is better use HEVC decoder to // ensure HDR10 output if display is not DV capable. - bool notHasHDR10fallback = (m_hints.dovi.dv_profile == 4 || m_hints.dovi.dv_profile == 5); + const bool notHasHDR10fallback = + (m_hints.dovi.dv_profile == 4 || m_hints.dovi.dv_profile == 5); if (mediaCodecSupportsDovi && (displaySupportsDovi || notHasHDR10fallback)) { @@ -634,6 +631,28 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio } m_mime = "video/av01"; m_formatname = "amc-av1"; + + if (m_hints.hdrType == StreamHdrType::HDR_TYPE_DOLBYVISION && m_hints.dovi.dv_profile == 10) + { + bool displaySupportsDovi{false}; + bool mediaCodecSupportsDovi{false}; + std::tie(displaySupportsDovi, mediaCodecSupportsDovi) = + CAndroidUtils::GetDolbyVisionCapabilities(); + + const bool notHasHDRfallback = (m_hints.dovi.dv_bl_signal_compatibility_id == 0 || + m_hints.dovi.dv_bl_signal_compatibility_id == 2 || + m_hints.dovi.dv_bl_signal_compatibility_id == 3); + + if (mediaCodecSupportsDovi && (displaySupportsDovi || notHasHDRfallback)) + { + m_mime = "video/dolby-vision"; + m_formatname = "amc-dav1"; + profile = CJNIBase::GetSDKVersion() >= 30 + ? CJNIMediaCodecInfoCodecProfileLevel::DolbyVisionProfileDvav110 + : 1024; + } + } + m_hints.extradata = {}; break; } diff --git a/xbmc/windowing/android/AndroidUtils.cpp b/xbmc/windowing/android/AndroidUtils.cpp index 8c61faeedb0d5..43f3c9e7251cf 100644 --- a/xbmc/windowing/android/AndroidUtils.cpp +++ b/xbmc/windowing/android/AndroidUtils.cpp @@ -438,3 +438,14 @@ bool CAndroidUtils::SupportsMediaCodecMimeType(const std::string& mimeType) return false; } + +std::pair CAndroidUtils::GetDolbyVisionCapabilities() +{ + const bool displaySupportsDovi = GetDisplayHDRCapabilities().SupportsDolbyVision(); + const bool mediaCodecSupportsDovi = SupportsMediaCodecMimeType("video/dolby-vision"); + + CLog::Log(LOGDEBUG, "CAndroidUtils::GetDolbyVisionCapabilities Display: {}, MediaCodec: {}", + displaySupportsDovi, mediaCodecSupportsDovi); + + return std::make_pair(displaySupportsDovi, mediaCodecSupportsDovi); +} diff --git a/xbmc/windowing/android/AndroidUtils.h b/xbmc/windowing/android/AndroidUtils.h index 426baee801a04..ee110654ff144 100644 --- a/xbmc/windowing/android/AndroidUtils.h +++ b/xbmc/windowing/android/AndroidUtils.h @@ -46,6 +46,7 @@ class CAndroidUtils : public ISettingCallback static std::vector GetDisplaySupportedHdrTypes(); static CHDRCapabilities GetDisplayHDRCapabilities(); + static std::pair GetDolbyVisionCapabilities(); protected: mutable int m_width; From dc9c36febf75d79a0cd956bb6062a69ffe656ae7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:27:44 +0200 Subject: [PATCH 015/811] [video] Add context menu item 'Information' for movie sets. --- xbmc/ContextMenuManager.cpp | 1 + xbmc/video/ContextMenus.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index 0efff747320e9..aab18f99af5d5 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -77,6 +77,7 @@ void CContextMenuManager::Init() std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), diff --git a/xbmc/video/ContextMenus.h b/xbmc/video/ContextMenus.h index af77209c37a72..d602b28b3e1d2 100644 --- a/xbmc/video/ContextMenus.h +++ b/xbmc/video/ContextMenus.h @@ -48,6 +48,11 @@ struct CMovieInfo : CVideoInfo CMovieInfo() : CVideoInfo(MediaTypeMovie) {} }; +struct CMovieSetInfo : CVideoInfo +{ + CMovieSetInfo() : CVideoInfo(MediaTypeVideoCollection) {} +}; + struct CVideoRemoveResumePoint : CStaticContextMenuAction { CVideoRemoveResumePoint() : CStaticContextMenuAction(38209) {} From 042b81ef26635441857039c09a21ef4d802c2fe1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:31:11 +0200 Subject: [PATCH 016/811] [listproviders] Add support for action 'Information' for movie sets. --- xbmc/listproviders/DirectoryProvider.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index b71c740da9a75..9c937e76423fc 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -491,11 +491,9 @@ bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) else if (fileItem->HasVideoInfoTag()) { auto mediaType = fileItem->GetVideoInfoTag()->m_type; - if (mediaType == MediaTypeMovie || - mediaType == MediaTypeTvShow || - mediaType == MediaTypeEpisode || - mediaType == MediaTypeVideo || - mediaType == MediaTypeMusicVideo) + if (mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || + mediaType == MediaTypeEpisode || mediaType == MediaTypeVideo || + mediaType == MediaTypeVideoCollection || mediaType == MediaTypeMusicVideo) { CGUIDialogVideoInfo::ShowFor(*fileItem); return true; From af86fae49bd66f232cad1664b81ad7001e6d3339 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:43:56 +0200 Subject: [PATCH 017/811] [video] Add context menu item 'Information' for TV show seasons. --- xbmc/ContextMenuManager.cpp | 1 + xbmc/video/ContextMenus.h | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index aab18f99af5d5..75fa2bb5140b9 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -80,6 +80,7 @@ void CContextMenuManager::Init() std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), diff --git a/xbmc/video/ContextMenus.h b/xbmc/video/ContextMenus.h index d602b28b3e1d2..a53c7c2217fe7 100644 --- a/xbmc/video/ContextMenus.h +++ b/xbmc/video/ContextMenus.h @@ -33,6 +33,11 @@ struct CTVShowInfo : CVideoInfo CTVShowInfo() : CVideoInfo(MediaTypeTvShow) {} }; +struct CSeasonInfo : CVideoInfo +{ + CSeasonInfo() : CVideoInfo(MediaTypeSeason) {} +}; + struct CEpisodeInfo : CVideoInfo { CEpisodeInfo() : CVideoInfo(MediaTypeEpisode) {} From 28d09bb5c6c4726fa179436ffea5ad4ba3dc0dca Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:44:46 +0200 Subject: [PATCH 018/811] [listproviders] Add support for action 'Information' for TV show seasons. --- xbmc/listproviders/DirectoryProvider.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 9c937e76423fc..0d9872e823e6a 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -492,8 +492,9 @@ bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) { auto mediaType = fileItem->GetVideoInfoTag()->m_type; if (mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || - mediaType == MediaTypeEpisode || mediaType == MediaTypeVideo || - mediaType == MediaTypeVideoCollection || mediaType == MediaTypeMusicVideo) + mediaType == MediaTypeSeason || mediaType == MediaTypeEpisode || + mediaType == MediaTypeVideo || mediaType == MediaTypeVideoCollection || + mediaType == MediaTypeMusicVideo) { CGUIDialogVideoInfo::ShowFor(*fileItem); return true; From 2855a6b9c8d660cf4a449d1bcba39ed3e67f727a Mon Sep 17 00:00:00 2001 From: CrystalP Date: Sat, 12 Aug 2023 17:38:02 -0400 Subject: [PATCH 019/811] [Windows] Change high precision setting to use 10 bit or better for all streams 8 bit YUV converted to RGB cannot be represented exactly with 8 bits so use 10 bits or float when the setting is enabled. Keep VSR exception. --- .../VideoRenderers/windows/RendererDXVA.cpp | 28 +++++-------- .../VideoRenderers/windows/RendererDXVA.h | 8 +--- .../windows/RendererShaders.cpp | 39 +++++++++---------- .../VideoRenderers/windows/RendererShaders.h | 2 +- 4 files changed, 30 insertions(+), 47 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp index c9fed0778aa8b..d95d56f1cc162 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp @@ -134,8 +134,7 @@ bool CRendererDXVA::Configure(const VideoPicture& picture, float fps, unsigned o m_enumerator->SupportedConversions(m_conversionsArgs); if (!conversions.empty()) { - const ProcessorConversion chosenConversion = - ChooseConversion(conversions, picture.colorBits, picture.color_transfer); + const ProcessorConversion chosenConversion = ChooseConversion(conversions); m_intermediateTargetFormat = chosenConversion.m_outputFormat; m_conversion = chosenConversion; @@ -189,8 +188,7 @@ void CRendererDXVA::CheckVideoParameters() // TODO case no supported conversion: add support in WinRenderer to fallback to a render method with support // For now, keep using the current conversion. Results won't be ideal but a black screen is avoided const ProcessorConversion conversion = - conversions.empty() ? m_conversion - : ChooseConversion(conversions, buf->bits, buf->color_transfer); + conversions.empty() ? m_conversion : ChooseConversion(conversions); if (m_conversion != conversion) { @@ -476,24 +474,15 @@ bool CRendererDXVA::CRenderBufferImpl::UploadToTexture() return m_bLoaded; } -ProcessorConversion CRendererDXVA::ChooseConversion( - const ProcessorConversions& conversions, - unsigned int sourceBits, - AVColorTransferCharacteristic colorTransfer) const +ProcessorConversion CRendererDXVA::ChooseConversion(const ProcessorConversions& conversions) const { assert(conversions.size() > 0); - bool tryHQ{false}; - if (!m_tryVSR) - { - // Try high quality when: backbuffer is 10 bits or High precision processing is on and the source is HDR - tryHQ = (DX::Windowing()->GetBackBuffer().GetFormat() == DXGI_FORMAT_R10G10B10A2_UNORM) || - (DX::Windowing()->IsHighPrecisionProcessingSettingEnabled() && sourceBits > 8 && - (colorTransfer == AVCOL_TRC_SMPTE2084 || colorTransfer == AVCOL_TRC_ARIB_STD_B67)); - } - - // RGB8 processor output format is required for VSR - if (!m_tryVSR && tryHQ) + // Try HQ except when: + // - trying VSR scaling, which requires RGB8 processor output format + // - the user opted out of high quality and the swap chain is 8 bits. + if (!m_tryVSR && (DX::Windowing()->IsHighPrecisionProcessingSettingEnabled() || + DX::Windowing()->GetBackBuffer().GetFormat() == DXGI_FORMAT_R10G10B10A2_UNORM)) { const auto it = std::find_if(conversions.cbegin(), conversions.cend(), [](const ProcessorConversion& c) { @@ -515,5 +504,6 @@ ProcessorConversion CRendererDXVA::ChooseConversion( return *it; // bad situation, nothing matching our needs found, return the first conversion available + CLog::LogF(LOGWARNING, "no conversion to wanted formats found, defaulting to first conversion."); return conversions.front(); } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h index 517278aa01321..cb435ad2c3343 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h @@ -51,13 +51,9 @@ class CRendererDXVA : public CRendererHQ /*! * \brief Choose the best available conversion for the given source and output constraints * \param conversions list of supported conversions - * \param picture information about the source - * \param tryVSR yes/no favor a conversion that enables Video Super Resolution scaling - * \return + * \return best match */ - DXVA::ProcessorConversion ChooseConversion(const DXVA::ProcessorConversions& conversions, - unsigned int sourceBits, - AVColorTransferCharacteristic colorTransfer) const; + DXVA::ProcessorConversion ChooseConversion(const DXVA::ProcessorConversions& conversions) const; std::unique_ptr m_processor; std::shared_ptr m_enumerator; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp index e16f7c49185ed..6f000855d801c 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.cpp @@ -99,8 +99,7 @@ bool CRendererShaders::Configure(const VideoPicture& picture, float fps, unsigne m_format = GetAVFormat(dxgi_format); } - CreateIntermediateTarget(m_sourceWidth, m_sourceHeight, false, - CalcIntermediateTargetFormat(picture)); + CreateIntermediateTarget(m_sourceWidth, m_sourceHeight, false, CalcIntermediateTargetFormat()); return true; } return false; @@ -203,35 +202,33 @@ AVColorPrimaries CRendererShaders::GetSrcPrimaries(AVColorPrimaries srcPrimaries return ret; } -DXGI_FORMAT CRendererShaders::CalcIntermediateTargetFormat(const VideoPicture& picture) const +DXGI_FORMAT CRendererShaders::CalcIntermediateTargetFormat() const { // Default value: same as the back buffer DXGI_FORMAT format{DX::Windowing()->GetBackBuffer().GetFormat()}; + // High precision setting not enabled: use back buffer format, 8 or 10 bits + // enabled: look for higher quality format if (!DX::Windowing()->IsHighPrecisionProcessingSettingEnabled()) return format; - // Preserve HDR precision - if (picture.colorBits > 8 && (picture.color_transfer == AVCOL_TRC_SMPTE2084 || - picture.color_transfer == AVCOL_TRC_ARIB_STD_B67)) - { - UINT reqSupport{D3D11_FORMAT_SUPPORT_SHADER_SAMPLE | D3D11_FORMAT_SUPPORT_RENDER_TARGET}; + UINT reqSupport{D3D11_FORMAT_SUPPORT_SHADER_SAMPLE | D3D11_FORMAT_SUPPORT_RENDER_TARGET}; - // Preferred: float16 as the yuv-rgb conversion takes place in float - // => avoids a conversion / quantization round-trip, but uses more bandwidth - const std::array hdrformats{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R10G10B10A2_UNORM, - DXGI_FORMAT_R32G32B32A32_FLOAT}; + // Preferred: float16 as the yuv-rgb conversion takes place in float + // => avoids a conversion / quantization round-trip, but uses more memory / bandwidth + const std::array hdrformats{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R10G10B10A2_UNORM, + DXGI_FORMAT_R32G32B32A32_FLOAT}; - const auto it = - std::find_if(hdrformats.cbegin(), hdrformats.cend(), [&](DXGI_FORMAT outputFormat) { - return DX::Windowing()->IsFormatSupport(outputFormat, reqSupport); - }); + const auto it = + std::find_if(hdrformats.cbegin(), hdrformats.cend(), [&](DXGI_FORMAT outputFormat) { + return DX::Windowing()->IsFormatSupport(outputFormat, reqSupport); + }); + + if (it != hdrformats.cend()) + format = *it; + else + CLog::LogF(LOGDEBUG, "no compatible high precision format found."); - if (it != hdrformats.cend()) - format = *it; - else - CLog::LogF(LOGDEBUG, "no compatible high precision format found for HDR."); - } return format; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h index d6d6ada908998..a987ac1332b7e 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererShaders.h @@ -48,7 +48,7 @@ class CRendererShaders : public CRendererHQ private: static AVColorPrimaries GetSrcPrimaries(AVColorPrimaries srcPrimaries, unsigned int width, unsigned int height); - DXGI_FORMAT CalcIntermediateTargetFormat(const VideoPicture& picture) const; + DXGI_FORMAT CalcIntermediateTargetFormat() const; AVColorPrimaries m_srcPrimaries = AVCOL_PRI_BT709; std::unique_ptr m_colorShader; From 2da7159fd5091b45d848b4af07e649febb0225bd Mon Sep 17 00:00:00 2001 From: CrystalP Date: Sun, 13 Aug 2023 12:09:09 -0400 Subject: [PATCH 020/811] [Windows] Cleanup unnecessary m_intermediateTargetFormat member. Thanks thexai for noticing. --- .../VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp | 8 ++------ .../VideoPlayer/VideoRenderers/windows/RendererDXVA.h | 1 - 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp index d95d56f1cc162..6a2d0d955aafd 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.cpp @@ -134,9 +134,7 @@ bool CRendererDXVA::Configure(const VideoPicture& picture, float fps, unsigned o m_enumerator->SupportedConversions(m_conversionsArgs); if (!conversions.empty()) { - const ProcessorConversion chosenConversion = ChooseConversion(conversions); - m_intermediateTargetFormat = chosenConversion.m_outputFormat; - m_conversion = chosenConversion; + m_conversion = ChooseConversion(conversions); CLog::LogF(LOGINFO, "chosen conversion: {}", m_conversion.ToString()); @@ -195,8 +193,6 @@ void CRendererDXVA::CheckVideoParameters() CLog::LogF(LOGINFO, "new conversion: {}", conversion.ToString()); m_processor->SetConversion(conversion); - m_intermediateTargetFormat = conversion.m_outputFormat; - m_conversion = conversion; } m_conversionsArgs = args; @@ -205,7 +201,7 @@ void CRendererDXVA::CheckVideoParameters() CreateIntermediateTarget(HasHQScaler() ? m_sourceWidth : m_viewWidth, HasHQScaler() ? m_sourceHeight : m_viewHeight, false, - m_intermediateTargetFormat); + m_conversion.m_outputFormat); } void CRendererDXVA::RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h index cb435ad2c3343..46fe5adef0378 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/windows/RendererDXVA.h @@ -57,7 +57,6 @@ class CRendererDXVA : public CRendererHQ std::unique_ptr m_processor; std::shared_ptr m_enumerator; - DXGI_FORMAT m_intermediateTargetFormat{DXGI_FORMAT_UNKNOWN}; DXVA::ProcessorConversion m_conversion; DXVA::SupportedConversionsArgs m_conversionsArgs; bool m_tryVSR{false}; From 6fe5068cbcecb1f1a5cd5b4863131246c681daa9 Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 29 Jul 2023 12:27:15 -0600 Subject: [PATCH 021/811] don't hash special images --- xbmc/TextureCacheJob.cpp | 56 ++++++++++++++++++++++++++++++---------- xbmc/TextureCacheJob.h | 9 ------- 2 files changed, 43 insertions(+), 22 deletions(-) diff --git a/xbmc/TextureCacheJob.cpp b/xbmc/TextureCacheJob.cpp index 7a7c555532f6b..d5cc7a3c00763 100644 --- a/xbmc/TextureCacheJob.cpp +++ b/xbmc/TextureCacheJob.cpp @@ -68,6 +68,38 @@ bool CTextureCacheJob::DoWork() return false; } +namespace +{ +// Most PVR images use "additional_info" to signify 'ownership' of basic images for easy +// cache cleaning, rather than special generated images +bool IsPVROwnedImage(const std::string& additional_info) +{ + return additional_info == "pvrchannel_radio" || additional_info == "pvrchannel_tv" || + additional_info == "pvrprovider" || additional_info == "pvrrecording" || + StringUtils::StartsWith(additional_info, "epgtag_"); +} + +// DecodeImageURL can also set "additional_info" to 'flipped' for mirror images selected in +// the GUI, so is not a special generated image +bool IsControl(const std::string& additional_info) +{ + return additional_info == "flipped"; +} + +// special generated images and images served via HTTP should not be regularly checked for changes +bool ShouldCheckForChanges(const std::string& additional_info, const std::string& url) +{ + const bool isSpecialImage = + !additional_info.empty() && !IsControl(additional_info) && !IsPVROwnedImage(additional_info); + if (isSpecialImage) + return false; + + const bool isHTTP = + StringUtils::StartsWith(url, "http://") || StringUtils::StartsWith(url, "https://"); + return !isHTTP; +} +} // namespace + bool CTextureCacheJob::CacheTexture(std::unique_ptr* out_texture) { // unwrap the URL as required @@ -76,14 +108,18 @@ bool CTextureCacheJob::CacheTexture(std::unique_ptr* out_texture) CPictureScalingAlgorithm::Algorithm scalingAlgorithm; std::string image = DecodeImageURL(m_url, width, height, scalingAlgorithm, additional_info); - m_details.updateable = additional_info != "music" && UpdateableURL(image); + m_details.updateable = ShouldCheckForChanges(additional_info, image); - // generate the hash - m_details.hash = GetImageHash(image); - if (m_details.hash.empty()) - return false; - else if (m_details.hash == m_oldHash) - return true; + if (m_details.updateable) + { + // generate the hash + m_details.hash = GetImageHash(image); + if (m_details.hash.empty()) + return false; + + if (m_details.hash == m_oldHash) + return true; + } std::unique_ptr texture = LoadImage(image, width, height, additional_info, true); if (texture) @@ -222,12 +258,6 @@ std::unique_ptr CTextureCacheJob::LoadImage(const std::string& image, return texture; } -bool CTextureCacheJob::UpdateableURL(const std::string &url) const -{ - // we don't constantly check online images - return !(StringUtils::StartsWith(url, "http://") || StringUtils::StartsWith(url, "https://")); -} - std::string CTextureCacheJob::GetImageHash(const std::string &url) { // silently ignore - we cannot stat these diff --git a/xbmc/TextureCacheJob.h b/xbmc/TextureCacheJob.h index 66775acf08fd8..8cbe65ef54e08 100644 --- a/xbmc/TextureCacheJob.h +++ b/xbmc/TextureCacheJob.h @@ -82,15 +82,6 @@ class CTextureCacheJob : public CJob */ static std::string GetImageHash(const std::string &url); - /*! \brief Check whether a given URL represents an image that can be updated - We currently don't check http:// and https:// URLs for updates, under the assumption that - a image URL is much more likely to be static and the actual image at the URL is unlikely - to change, so no point checking all the time. - \param url the url to check - \return true if the image given by the URL should be checked for updates, false otherwise - */ - bool UpdateableURL(const std::string &url) const; - /*! \brief Decode an image URL to the underlying image, width, height and orientation \param url wrapped URL of the image \param width width derived from URL From 0598a9b6768effccc66eea405be2e00bc93c7eec Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 29 Jul 2023 12:36:24 -0600 Subject: [PATCH 022/811] small update to class docs --- xbmc/imagefiles/SpecialImageFileLoader.h | 7 ++++--- xbmc/music/MusicEmbeddedImageFileLoader.h | 4 +++- xbmc/video/VideoEmbeddedImageFileLoader.h | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/xbmc/imagefiles/SpecialImageFileLoader.h b/xbmc/imagefiles/SpecialImageFileLoader.h index 18b3b03655295..1a0812cc06e72 100644 --- a/xbmc/imagefiles/SpecialImageFileLoader.h +++ b/xbmc/imagefiles/SpecialImageFileLoader.h @@ -17,10 +17,11 @@ namespace IMAGE_FILES { /*! - * @brief An interface to load special image files into a texture for the cache. + * @brief An interface to load special image files into a texture for display. * - * Special image files are generated or embedded images, like album covers embedded - * in music files or thumbnails generated from video files. + * Special image files are images that are more than just a link or path to an + * image file, such as generated or embedded images - like album covers + * embedded in music files or thumbnails generated from video files. */ class ISpecialImageFileLoader { diff --git a/xbmc/music/MusicEmbeddedImageFileLoader.h b/xbmc/music/MusicEmbeddedImageFileLoader.h index 95a2db7e126e5..42e332f1c01c3 100644 --- a/xbmc/music/MusicEmbeddedImageFileLoader.h +++ b/xbmc/music/MusicEmbeddedImageFileLoader.h @@ -12,7 +12,9 @@ namespace MUSIC_INFO { - +/*! + * @brief Generates a texture for an image embedded in a music file. +*/ class CMusicEmbeddedImageFileLoader : public IMAGE_FILES::ISpecialImageFileLoader { public: diff --git a/xbmc/video/VideoEmbeddedImageFileLoader.h b/xbmc/video/VideoEmbeddedImageFileLoader.h index c73ba5964a617..81dcdb6b6420f 100644 --- a/xbmc/video/VideoEmbeddedImageFileLoader.h +++ b/xbmc/video/VideoEmbeddedImageFileLoader.h @@ -12,7 +12,9 @@ namespace VIDEO { - +/*! + * @brief Generates a texture for an image embedded in a video file. +*/ class CVideoEmbeddedImageFileLoader : public IMAGE_FILES::ISpecialImageFileLoader { public: From 0840ead9b560a32cd3c65cc50e47f9971d964b56 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Sun, 9 Jul 2023 11:43:16 -0700 Subject: [PATCH 023/811] Port Dialog: Guard ports with a mutex (second attempt) --- xbmc/games/addons/input/GameClientInput.cpp | 69 +++++++++++++------ xbmc/games/addons/input/GameClientInput.h | 15 +++- xbmc/games/agents/GameAgentManager.cpp | 6 +- .../ports/guicontrols/GUIActivePortList.cpp | 5 +- xbmc/games/ports/windows/GUIPortList.cpp | 12 ++-- 5 files changed, 75 insertions(+), 32 deletions(-) diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp index 55d86e0035b61..273f376fe34e2 100644 --- a/xbmc/games/addons/input/GameClientInput.cpp +++ b/xbmc/games/addons/input/GameClientInput.cpp @@ -61,6 +61,8 @@ void CGameClientInput::Initialize() // Send controller layouts to game client SetControllerLayouts(m_topology->GetControllerTree().GetControllers()); + std::lock_guard lock(m_portMutex); + // Reset ports to default state (first accepted controller is connected) ActivateControllers(m_topology->GetControllerTree()); @@ -72,23 +74,27 @@ void CGameClientInput::Initialize() void CGameClientInput::Start(IGameInputCallback* input) { - m_inputCallback = input; - - // Connect/disconnect active controllers - for (const CPortNode& port : GetActiveControllerTree().GetPorts()) { - if (port.IsConnected()) + std::lock_guard lock(m_portMutex); + + m_inputCallback = input; + + // Connect/disconnect active controllers + for (const CPortNode& port : m_portManager->GetControllerTree().GetPorts()) { - const ControllerPtr& activeController = port.GetActiveController().GetController(); - if (activeController) - ConnectController(port.GetAddress(), activeController); + if (port.IsConnected()) + { + const ControllerPtr& activeController = port.GetActiveController().GetController(); + if (activeController) + ConnectController(port.GetAddress(), activeController); + } + else + DisconnectController(port.GetAddress()); } - else - DisconnectController(port.GetAddress()); - } - // Ensure hardware is open to receive events - m_hardware.reset(new CGameClientHardware(m_gameClient)); + // Ensure hardware is open to receive events + m_hardware.reset(new CGameClientHardware(m_gameClient)); + } // Notify observers of the initial port configuration NotifyObservers(ObservableMessageGamePortsChanged); @@ -105,6 +111,8 @@ void CGameClientInput::Deinitialize() void CGameClientInput::Stop() { + std::lock_guard lock(m_portMutex); + m_hardware.reset(); CloseMouse(); @@ -170,9 +178,14 @@ float CGameClientInput::GetPortActivation(const std::string& portAddress) { float activation = 0.0f; - auto it = m_joysticks.find(portAddress); - if (it != m_joysticks.end()) - activation = it->second->GetActivation(); + std::unique_lock lock(m_portMutex, std::defer_lock); + + if (lock.try_lock()) + { + auto it = m_joysticks.find(portAddress); + if (it != m_joysticks.end()) + activation = it->second->GetActivation(); + } return activation; } @@ -273,8 +286,10 @@ const CControllerTree& CGameClientInput::GetDefaultControllerTree() const return m_topology->GetControllerTree(); } -const CControllerTree& CGameClientInput::GetActiveControllerTree() const +CControllerTree CGameClientInput::GetActiveControllerTree() const { + std::lock_guard lock(m_portMutex); + return m_portManager->GetControllerTree(); } @@ -323,7 +338,9 @@ bool CGameClientInput::ConnectController(const std::string& portAddress, return false; } - const CPortNode& currentPort = GetActiveControllerTree().GetPort(portAddress); + std::lock_guard lock(m_portMutex); + + const CPortNode& currentPort = m_portManager->GetControllerTree().GetPort(portAddress); // Close current ports if any are open PERIPHERALS::EventLockHandlePtr inputHandlingLock; @@ -362,7 +379,7 @@ bool CGameClientInput::ConnectController(const std::string& portAddress, bool bSuccess = true; // If port is a multitap, we need to activate its children - const CPortNode& updatedPort = GetActiveControllerTree().GetPort(portAddress); + const CPortNode& updatedPort = m_portManager->GetControllerTree().GetPort(portAddress); const PortVec& childPorts = updatedPort.GetActiveController().GetHub().GetPorts(); for (const CPortNode& childPort : childPorts) { @@ -378,8 +395,10 @@ bool CGameClientInput::DisconnectController(const std::string& portAddress) { PERIPHERALS::EventLockHandlePtr inputHandlingLock; + std::lock_guard lock(m_portMutex); + // If port is a multitap, we need to deactivate its children - const CPortNode& currentPort = GetActiveControllerTree().GetPort(portAddress); + const CPortNode& currentPort = m_portManager->GetControllerTree().GetPort(portAddress); CloseJoysticks(currentPort, inputHandlingLock); // If a port was closed, then destroying the lock will block until all @@ -418,8 +437,12 @@ bool CGameClientInput::DisconnectController(const std::string& portAddress) void CGameClientInput::SavePorts() { - // Save port state - m_portManager->SaveXMLAsync(); + { + std::lock_guard lock(m_portMutex); + + // Save port state + m_portManager->SaveXMLAsync(); + } // Let the observers know that ports have changed NotifyObservers(ObservableMessageGamePortsChanged); @@ -427,6 +450,8 @@ void CGameClientInput::SavePorts() void CGameClientInput::ResetPorts() { + std::lock_guard lock(m_portMutex); + const CControllerTree& controllerTree = GetDefaultControllerTree(); for (const CPortNode& port : controllerTree.GetPorts()) ConnectController(port.GetAddress(), port.GetActiveController().GetController()); diff --git a/xbmc/games/addons/input/GameClientInput.h b/xbmc/games/addons/input/GameClientInput.h index 5dce0fb53b38d..d7d6598d90680 100644 --- a/xbmc/games/addons/input/GameClientInput.h +++ b/xbmc/games/addons/input/GameClientInput.h @@ -16,6 +16,7 @@ #include #include +#include #include class CCriticalSection; @@ -66,7 +67,7 @@ class CGameClientInput : protected CGameClientSubsystem, public Observable // Topology functions const CControllerTree& GetDefaultControllerTree() const; - const CControllerTree& GetActiveControllerTree() const; + CControllerTree GetActiveControllerTree() const; bool SupportsKeyboard() const; bool SupportsMouse() const; int GetPlayerLimit() const; @@ -132,9 +133,19 @@ class CGameClientInput : protected CGameClientSubsystem, public Observable */ JoystickMap m_joysticks; - // TODO: Guard with a mutex + /*! + * \brief Serializable port state + */ std::unique_ptr m_portManager; + /*! + * \brief Mutex for port state + * + * Mutex is recursive to allow for management of several ports within the + * function ResetPorts(). + */ + mutable std::recursive_mutex m_portMutex; + /*! * \brief Keyboard handler * diff --git a/xbmc/games/agents/GameAgentManager.cpp b/xbmc/games/agents/GameAgentManager.cpp index 512b389cc50fd..6657c4a878b10 100644 --- a/xbmc/games/agents/GameAgentManager.cpp +++ b/xbmc/games/agents/GameAgentManager.cpp @@ -156,7 +156,7 @@ std::vector CGameAgentManager::GetInputPorts() const if (m_gameClient) { - const CControllerTree& controllerTree = m_gameClient->Input().GetActiveControllerTree(); + CControllerTree controllerTree = m_gameClient->Input().GetActiveControllerTree(); controllerTree.GetInputPorts(inputPorts); } @@ -237,7 +237,7 @@ void CGameAgentManager::ProcessKeyboard() m_peripheralManager.GetPeripheralsWithFeature(keyboards, PERIPHERALS::FEATURE_KEYBOARD); if (!keyboards.empty()) { - const CControllerTree& controllers = m_gameClient->Input().GetActiveControllerTree(); + CControllerTree controllers = m_gameClient->Input().GetActiveControllerTree(); auto it = std::find_if( controllers.GetPorts().begin(), controllers.GetPorts().end(), @@ -259,7 +259,7 @@ void CGameAgentManager::ProcessMouse() m_peripheralManager.GetPeripheralsWithFeature(mice, PERIPHERALS::FEATURE_MOUSE); if (!mice.empty()) { - const CControllerTree& controllers = m_gameClient->Input().GetActiveControllerTree(); + CControllerTree controllers = m_gameClient->Input().GetActiveControllerTree(); auto it = std::find_if( controllers.GetPorts().begin(), controllers.GetPorts().end(), diff --git a/xbmc/games/ports/guicontrols/GUIActivePortList.cpp b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp index 1811193b4cc83..36769fdb44d1d 100644 --- a/xbmc/games/ports/guicontrols/GUIActivePortList.cpp +++ b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp @@ -86,7 +86,10 @@ void CGUIActivePortList::Refresh() // Add controllers of active ports if (m_gameClient) - AddItems(m_gameClient->Input().GetActiveControllerTree().GetPorts()); + { + CControllerTree controllerTree = m_gameClient->Input().GetActiveControllerTree(); + AddItems(controllerTree.GetPorts()); + } // Add padding if right-aligned if (m_alignment == XBFONT_RIGHT) diff --git a/xbmc/games/ports/windows/GUIPortList.cpp b/xbmc/games/ports/windows/GUIPortList.cpp index e85af7246a0ae..dc686b1a3a482 100644 --- a/xbmc/games/ports/windows/GUIPortList.cpp +++ b/xbmc/games/ports/windows/GUIPortList.cpp @@ -106,8 +106,10 @@ void CGUIPortList::Refresh() if (m_gameClient) { + CControllerTree controllerTree = m_gameClient->Input().GetActiveControllerTree(); + unsigned int itemIndex = 0; - for (const CPortNode& port : m_gameClient->Input().GetActiveControllerTree().GetPorts()) + for (const CPortNode& port : controllerTree.GetPorts()) AddItems(port, itemIndex, GetLabel(port)); m_viewControl->SetItems(*m_vecItems); @@ -254,7 +256,7 @@ void CGUIPortList::OnItemSelect(unsigned int itemIndex) if (portAddress.empty()) return; - const CPortNode& port = m_gameClient->Input().GetActiveControllerTree().GetPort(portAddress); + CPortNode port = m_gameClient->Input().GetActiveControllerTree().GetPort(portAddress); ControllerVector controllers; for (const CControllerNode& controllerNode : port.GetCompatibleControllers()) @@ -263,11 +265,13 @@ void CGUIPortList::OnItemSelect(unsigned int itemIndex) // Get current controller to give initial focus ControllerPtr controller = port.GetActiveController().GetController(); - auto callback = [this, &port](const ControllerPtr& controller) { + // Check if we should show a "disconnect" option + const bool showDisconnect = !port.IsForceConnected(); + + auto callback = [this, port = std::move(port)](const ControllerPtr& controller) { OnControllerSelected(port, controller); }; - const bool showDisconnect = !port.IsForceConnected(); m_controllerSelectDialog.Initialize(std::move(controllers), std::move(controller), showDisconnect, callback); } From 5939426e5bd4f92a9606ca24b52b73e58fbb1934 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Fri, 11 Aug 2023 15:25:35 -0700 Subject: [PATCH 024/811] Agent dialog: Fix agent list not updating correctly when changing ports --- xbmc/games/agents/windows/GUIAgentList.cpp | 1 + xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/games/agents/windows/GUIAgentList.cpp b/xbmc/games/agents/windows/GUIAgentList.cpp index 6d785915b2668..eaac3b1e77ef2 100644 --- a/xbmc/games/agents/windows/GUIAgentList.cpp +++ b/xbmc/games/agents/windows/GUIAgentList.cpp @@ -182,6 +182,7 @@ void CGUIAgentList::Notify(const Observable& obs, const ObservableMessage msg) switch (msg) { case ObservableMessageGameAgentsChanged: + case ObservableMessageGamePortsChanged: { CGUIMessage msg(GUI_MSG_REFRESH_LIST, m_guiWindow.GetID(), CONTROL_AGENT_LIST); CServiceBroker::GetAppMessenger()->SendGUIMessage(msg, m_guiWindow.GetID()); diff --git a/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp b/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp index ab9498fd4b87d..71974d729f3bf 100644 --- a/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp +++ b/xbmc/games/controllers/guicontrols/GUIGameControllerList.cpp @@ -171,7 +171,7 @@ void CGUIGameControllerList::UpdatePortIndex(const PERIPHERALS::PeripheralPtr& a static_cast(agentPeripheral.get()); // See if the input provider has a port address - const std::string& portAddress = agentManager.GetPortAddress(inputProvider); + std::string portAddress = agentManager.GetPortAddress(inputProvider); if (portAddress.empty()) return; From f7a620ea3f1946d4a31e0bb967d2d0ed6a913c1a Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 12:43:05 +0100 Subject: [PATCH 025/811] [skintimers] Decouple timer existance verification --- xbmc/addons/gui/skin/SkinTimerManager.cpp | 15 ++++++++++----- xbmc/addons/gui/skin/SkinTimerManager.h | 6 ++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index 43bd706324720..40ae29dbb98a2 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -55,7 +55,7 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) } std::string timerName = node->FirstChild("name")->FirstChild()->Value(); - if (m_timers.count(timerName) > 0) + if (TimerExists(timerName)) { CLog::LogF(LOGWARNING, "Ignoring timer with name {} - another timer with the same name already exists", @@ -136,7 +136,7 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) bool CSkinTimerManager::TimerIsRunning(const std::string& timer) const { - if (m_timers.count(timer) == 0) + if (!TimerExists(timer)) { CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); return false; @@ -146,7 +146,7 @@ bool CSkinTimerManager::TimerIsRunning(const std::string& timer) const float CSkinTimerManager::GetTimerElapsedSeconds(const std::string& timer) const { - if (m_timers.count(timer) == 0) + if (!TimerExists(timer)) { CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); return 0; @@ -156,7 +156,7 @@ float CSkinTimerManager::GetTimerElapsedSeconds(const std::string& timer) const void CSkinTimerManager::TimerStart(const std::string& timer) const { - if (m_timers.count(timer) == 0) + if (!TimerExists(timer)) { CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); return; @@ -166,7 +166,7 @@ void CSkinTimerManager::TimerStart(const std::string& timer) const void CSkinTimerManager::TimerStop(const std::string& timer) const { - if (m_timers.count(timer) == 0) + if (!TimerExists(timer)) { CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); return; @@ -174,6 +174,11 @@ void CSkinTimerManager::TimerStop(const std::string& timer) const m_timers.at(timer)->Stop(); } +bool CSkinTimerManager::TimerExists(const std::string& timer) const +{ + return m_timers.count(timer) != 0; +} + void CSkinTimerManager::Stop() { // skintimers, as infomanager clients register info conditions/expressions in the infomanager. diff --git a/xbmc/addons/gui/skin/SkinTimerManager.h b/xbmc/addons/gui/skin/SkinTimerManager.h index fdf44d1c26074..9aec7965c8017 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.h +++ b/xbmc/addons/gui/skin/SkinTimerManager.h @@ -38,6 +38,12 @@ class CSkinTimerManager /*! \brief Stops the manager */ void Stop(); + /*! \brief Checks if the timer with name `timer` exists + * \param timer the name of the skin timer + * \return true if the given timer exists, false otherwise + */ + bool TimerExists(const std::string& timer) const; + /*! \brief Checks if the timer with name `timer` is running \param timer the name of the skin timer \return true if the given timer exists and is running, false otherwise From 6814daca24dfa8a672874000af9d1978a0fa2a39 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 13:01:25 +0100 Subject: [PATCH 026/811] [SkinTimer] Add method to verify the total number of registered timers --- xbmc/addons/gui/skin/SkinTimerManager.cpp | 5 +++++ xbmc/addons/gui/skin/SkinTimerManager.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index 40ae29dbb98a2..0e4ff78cf66af 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -174,6 +174,11 @@ void CSkinTimerManager::TimerStop(const std::string& timer) const m_timers.at(timer)->Stop(); } +size_t CSkinTimerManager::GetTimerCount() const +{ + return m_timers.size(); +} + bool CSkinTimerManager::TimerExists(const std::string& timer) const { return m_timers.count(timer) != 0; diff --git a/xbmc/addons/gui/skin/SkinTimerManager.h b/xbmc/addons/gui/skin/SkinTimerManager.h index 9aec7965c8017..3e95c52089120 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.h +++ b/xbmc/addons/gui/skin/SkinTimerManager.h @@ -38,6 +38,11 @@ class CSkinTimerManager /*! \brief Stops the manager */ void Stop(); + /*! \brief Gets the total number of timers registered in the manager + * \return the timer count + */ + size_t GetTimerCount() const; + /*! \brief Checks if the timer with name `timer` exists * \param timer the name of the skin timer * \return true if the given timer exists, false otherwise From bcff5c4fcf8e87090890c913117bfdb126c6d599 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 13:25:14 +0100 Subject: [PATCH 027/811] [skintimers] Add method to grab(move) a given timer --- xbmc/addons/gui/skin/SkinTimerManager.cpp | 11 +++++++++++ xbmc/addons/gui/skin/SkinTimerManager.h | 6 ++++++ 2 files changed, 17 insertions(+) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index 0e4ff78cf66af..e3c41da8294c2 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -184,6 +184,17 @@ bool CSkinTimerManager::TimerExists(const std::string& timer) const return m_timers.count(timer) != 0; } +std::unique_ptr CSkinTimerManager::GrabTimer(const std::string& timer) +{ + if (auto iter = m_timers.find(timer); iter != m_timers.end()) + { + auto timerInstance = std::move(iter->second); + m_timers.erase(iter); + return timerInstance; + } + return {}; +} + void CSkinTimerManager::Stop() { // skintimers, as infomanager clients register info conditions/expressions in the infomanager. diff --git a/xbmc/addons/gui/skin/SkinTimerManager.h b/xbmc/addons/gui/skin/SkinTimerManager.h index 3e95c52089120..803e111178d83 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.h +++ b/xbmc/addons/gui/skin/SkinTimerManager.h @@ -49,6 +49,12 @@ class CSkinTimerManager */ bool TimerExists(const std::string& timer) const; + /*! \brief Move a given timer, removing it from the manager + * \param timer the name of the skin timer + * \return the timer (moved), nullptr if it doesn't exist + */ + std::unique_ptr GrabTimer(const std::string& timer); + /*! \brief Checks if the timer with name `timer` is running \param timer the name of the skin timer \return true if the given timer exists and is running, false otherwise From ad3198595e798129301633ffe2c4e02d17391eb4 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 13:31:02 +0100 Subject: [PATCH 028/811] [Skintimers] Add method to check if the timer is configured to reset on start --- xbmc/addons/gui/skin/SkinTimer.cpp | 5 +++++ xbmc/addons/gui/skin/SkinTimer.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/xbmc/addons/gui/skin/SkinTimer.cpp b/xbmc/addons/gui/skin/SkinTimer.cpp index c4e88b772f7e1..3bb9538626edd 100644 --- a/xbmc/addons/gui/skin/SkinTimer.cpp +++ b/xbmc/addons/gui/skin/SkinTimer.cpp @@ -80,6 +80,11 @@ INFO::InfoPtr CSkinTimer::GetStopCondition() const return m_stopCondition; } +bool CSkinTimer::ResetsOnStart() const +{ + return m_resetOnStart; +} + void CSkinTimer::OnStart() { if (m_startActions.HasAnyActions()) diff --git a/xbmc/addons/gui/skin/SkinTimer.h b/xbmc/addons/gui/skin/SkinTimer.h index d838ef0ab6733..c130d39e80f20 100644 --- a/xbmc/addons/gui/skin/SkinTimer.h +++ b/xbmc/addons/gui/skin/SkinTimer.h @@ -68,6 +68,11 @@ class CSkinTimer : public CStopWatch */ INFO::InfoPtr GetStopCondition() const; + /*! \brief Checks if a given timer is configured to reset every time it starts + * \return true if the timer is configured to reset on start, false otherwise + */ + bool ResetsOnStart() const; + /*! \brief Evaluates the timer start boolean info expression returning the respective result. * \details Called from the skin timer manager to check if the timer should be started * \return true if the condition is true, false otherwise From 372eaa1e9ef18fe280298e6d619ee97ca0365375 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 13:34:40 +0100 Subject: [PATCH 029/811] [SkinTimers] Add method to get the timer name --- xbmc/addons/gui/skin/SkinTimer.cpp | 5 +++++ xbmc/addons/gui/skin/SkinTimer.h | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/xbmc/addons/gui/skin/SkinTimer.cpp b/xbmc/addons/gui/skin/SkinTimer.cpp index 3bb9538626edd..6a1e073db2ce4 100644 --- a/xbmc/addons/gui/skin/SkinTimer.cpp +++ b/xbmc/addons/gui/skin/SkinTimer.cpp @@ -65,6 +65,11 @@ bool CSkinTimer::VerifyStopCondition() const return m_stopCondition && m_stopCondition->Get(INFO::DEFAULT_CONTEXT); } +const std::string& CSkinTimer::GetName() const +{ + return m_name; +} + INFO::InfoPtr CSkinTimer::GetStartCondition() const { return m_startCondition; diff --git a/xbmc/addons/gui/skin/SkinTimer.h b/xbmc/addons/gui/skin/SkinTimer.h index c130d39e80f20..8886d5fe87a37 100644 --- a/xbmc/addons/gui/skin/SkinTimer.h +++ b/xbmc/addons/gui/skin/SkinTimer.h @@ -53,6 +53,11 @@ class CSkinTimer : public CStopWatch /*! \brief stops the skin timer */ void Stop(); + /*! \brief Getter for the timer name + * \return the timer name + */ + const std::string& GetName() const; + /*! \brief Getter for the timer start boolean condition/expression * \return the start boolean condition/expression (may be null) */ From f936dca9d40e733c0c157a08cc434f22e0320458 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 23:43:36 +0100 Subject: [PATCH 030/811] [SkinTimers] Use dependency injection for infomanager --- xbmc/addons/Skin.cpp | 16 +++++++++------- xbmc/addons/Skin.h | 2 +- xbmc/addons/gui/skin/SkinTimerManager.cpp | 20 ++++++++++---------- xbmc/addons/gui/skin/SkinTimerManager.h | 10 ++++++++-- 4 files changed, 28 insertions(+), 20 deletions(-) diff --git a/xbmc/addons/Skin.cpp b/xbmc/addons/Skin.cpp index 220e77383d4ae..9e63a3f04783a 100644 --- a/xbmc/addons/Skin.cpp +++ b/xbmc/addons/Skin.cpp @@ -285,15 +285,17 @@ void CSkinInfo::LoadIncludes() void CSkinInfo::LoadTimers() { + m_skinTimerManager = + std::make_unique(CServiceBroker::GetGUI()->GetInfoManager()); const std::string timersPath = CSpecialProtocol::TranslatePathConvertCase(GetSkinPath("Timers.xml")); CLog::LogF(LOGINFO, "Trying to load skin timers from {}", timersPath); - m_skinTimerManager.LoadTimers(timersPath); + m_skinTimerManager->LoadTimers(timersPath); } void CSkinInfo::ProcessTimers() { - m_skinTimerManager.Process(); + m_skinTimerManager->Process(); } void CSkinInfo::ResolveIncludes(TiXmlElement* node, std::map* xmlIncludeConditions /* = nullptr */) @@ -418,27 +420,27 @@ void CSkinInfo::OnPostInstall(bool update, bool modal) void CSkinInfo::Unload() { - m_skinTimerManager.Stop(); + m_skinTimerManager->Stop(); } bool CSkinInfo::TimerIsRunning(const std::string& timer) const { - return m_skinTimerManager.TimerIsRunning(timer); + return m_skinTimerManager->TimerIsRunning(timer); } float CSkinInfo::GetTimerElapsedSeconds(const std::string& timer) const { - return m_skinTimerManager.GetTimerElapsedSeconds(timer); + return m_skinTimerManager->GetTimerElapsedSeconds(timer); } void CSkinInfo::TimerStart(const std::string& timer) const { - m_skinTimerManager.TimerStart(timer); + m_skinTimerManager->TimerStart(timer); } void CSkinInfo::TimerStop(const std::string& timer) const { - m_skinTimerManager.TimerStop(timer); + m_skinTimerManager->TimerStop(timer); } void CSkinInfo::SettingOptionsSkinColorsFiller(const SettingConstPtr& setting, diff --git a/xbmc/addons/Skin.h b/xbmc/addons/Skin.h index 41261678cf011..5ee51002c00f4 100644 --- a/xbmc/addons/Skin.h +++ b/xbmc/addons/Skin.h @@ -283,7 +283,7 @@ class CSkinInfo : public CAddon bool m_debugging; /*! Manager/Owner of skin timers */ - CSkinTimerManager m_skinTimerManager; + std::unique_ptr m_skinTimerManager; private: std::map m_strings; diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index e3c41da8294c2..fe247f1d1a0bd 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -9,7 +9,6 @@ #include "SkinTimerManager.h" #include "GUIInfoManager.h" -#include "ServiceBroker.h" #include "guilib/GUIAction.h" #include "guilib/GUIComponent.h" #include "utils/StringUtils.h" @@ -20,6 +19,10 @@ using namespace std::chrono_literals; +CSkinTimerManager::CSkinTimerManager(CGUIInfoManager& infoMgr) : m_infoMgr{infoMgr} +{ +} + void CSkinTimerManager::LoadTimers(const std::string& path) { CXBMCTinyXML doc; @@ -69,8 +72,7 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) if (node->FirstChild("start") && node->FirstChild("start")->FirstChild() && !node->FirstChild("start")->FirstChild()->ValueStr().empty()) { - startInfo = CServiceBroker::GetGUI()->GetInfoManager().Register( - node->FirstChild("start")->FirstChild()->ValueStr()); + startInfo = m_infoMgr.Register(node->FirstChild("start")->FirstChild()->ValueStr()); // check if timer needs to be reset after start if (node->FirstChildElement("start")->Attribute("reset") && StringUtils::EqualsNoCase(node->FirstChildElement("start")->Attribute("reset"), "true")) @@ -84,16 +86,14 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) if (node->FirstChild("reset") && node->FirstChild("reset")->FirstChild() && !node->FirstChild("reset")->FirstChild()->ValueStr().empty()) { - resetInfo = CServiceBroker::GetGUI()->GetInfoManager().Register( - node->FirstChild("reset")->FirstChild()->ValueStr()); + resetInfo = m_infoMgr.Register(node->FirstChild("reset")->FirstChild()->ValueStr()); } // timer stop INFO::InfoPtr stopInfo{nullptr}; if (node->FirstChild("stop") && node->FirstChild("stop")->FirstChild() && !node->FirstChild("stop")->FirstChild()->ValueStr().empty()) { - stopInfo = CServiceBroker::GetGUI()->GetInfoManager().Register( - node->FirstChild("stop")->FirstChild()->ValueStr()); + stopInfo = m_infoMgr.Register(node->FirstChild("stop")->FirstChild()->ValueStr()); } // process onstart actions @@ -207,15 +207,15 @@ void CSkinTimerManager::Stop() const std::unique_ptr::pointer timer = val.get(); if (timer->GetStartCondition()) { - CServiceBroker::GetGUI()->GetInfoManager().UnRegister(timer->GetStartCondition()); + m_infoMgr.UnRegister(timer->GetStartCondition()); } if (timer->GetStopCondition()) { - CServiceBroker::GetGUI()->GetInfoManager().UnRegister(timer->GetStopCondition()); + m_infoMgr.UnRegister(timer->GetStopCondition()); } if (timer->GetResetCondition()) { - CServiceBroker::GetGUI()->GetInfoManager().UnRegister(timer->GetResetCondition()); + m_infoMgr.UnRegister(timer->GetResetCondition()); } } m_timers.clear(); diff --git a/xbmc/addons/gui/skin/SkinTimerManager.h b/xbmc/addons/gui/skin/SkinTimerManager.h index 803e111178d83..75a5695566eff 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.h +++ b/xbmc/addons/gui/skin/SkinTimerManager.h @@ -14,6 +14,8 @@ #include #include +class CGUIInfoManager; + /*! \brief CSkinTimerManager is the container and manager for Skin timers. Its role is that of * checking if the timer boolean conditions are valid, start or stop timers and execute the respective * builtin actions linked to the timer lifecycle @@ -24,8 +26,11 @@ class CSkinTimerManager { public: - /*! \brief Skin timer manager constructor */ - CSkinTimerManager() = default; + /*! \brief Skin timer manager constructor + * \param infoMgr reference to the infomanager + */ + CSkinTimerManager(CGUIInfoManager& infoMgr); + CSkinTimerManager() = delete; /*! \brief Default skin timer manager destructor */ ~CSkinTimerManager() = default; @@ -91,4 +96,5 @@ class CSkinTimerManager /*! Container for the skin timers */ std::map> m_timers; + CGUIInfoManager& m_infoMgr; }; From c58121ccd9ab685cb390a1be6eed685dfed92b8e Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 13 Aug 2023 00:40:14 +0100 Subject: [PATCH 031/811] [Info] Use injection and remove dependency on service broker --- xbmc/GUIInfoManager.cpp | 2 +- xbmc/interfaces/info/InfoBool.h | 4 +++- xbmc/interfaces/info/InfoExpression.cpp | 21 +++++++++------------ xbmc/interfaces/info/InfoExpression.h | 4 ++-- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index dc4b7a809fc46..f344b50a8122e 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -10706,7 +10706,7 @@ INFO::InfoPtr CGUIInfoManager::Register(const std::string &expression, int conte res = m_bools.insert(std::make_shared(condition, context, m_refreshCounter)); if (res.second) - res.first->get()->Initialize(); + res.first->get()->Initialize(this); return *(res.first); } diff --git a/xbmc/interfaces/info/InfoBool.h b/xbmc/interfaces/info/InfoBool.h index 9a22269416e82..a4bf0b4a84fe3 100644 --- a/xbmc/interfaces/info/InfoBool.h +++ b/xbmc/interfaces/info/InfoBool.h @@ -12,6 +12,7 @@ #include class CGUIListItem; +class CGUIInfoManager; namespace INFO { @@ -25,7 +26,7 @@ class InfoBool InfoBool(const std::string &expression, int context, unsigned int &refreshCounter); virtual ~InfoBool() = default; - virtual void Initialize() {} + virtual void Initialize(CGUIInfoManager* infoMgr) { m_infoMgr = infoMgr; } /*! \brief Get the value of this info bool This is called to update (if dirty) and fetch the value of the info bool @@ -72,6 +73,7 @@ class InfoBool int m_context; ///< contextual information to go with the condition bool m_listItemDependent = false; ///< do not cache if a listitem pointer is given std::string m_expression; ///< original expression + CGUIInfoManager* m_infoMgr; private: unsigned int m_refreshCounter = 0; diff --git a/xbmc/interfaces/info/InfoExpression.cpp b/xbmc/interfaces/info/InfoExpression.cpp index dc9aa63e1e505..b99794f4b6496 100644 --- a/xbmc/interfaces/info/InfoExpression.cpp +++ b/xbmc/interfaces/info/InfoExpression.cpp @@ -9,8 +9,6 @@ #include "InfoExpression.h" #include "GUIInfoManager.h" -#include "ServiceBroker.h" -#include "guilib/GUIComponent.h" #include "utils/log.h" #include @@ -19,9 +17,10 @@ using namespace INFO; -void InfoSingle::Initialize() +void InfoSingle::Initialize(CGUIInfoManager* infoMgr) { - m_condition = CServiceBroker::GetGUI()->GetInfoManager().TranslateSingleString(m_expression, m_listItemDependent); + InfoBool::Initialize(infoMgr); + m_condition = m_infoMgr->TranslateSingleString(m_expression, m_listItemDependent); } void InfoSingle::Update(int contextWindow, const CGUIListItem* item) @@ -29,15 +28,16 @@ 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); + m_value = m_infoMgr->GetBool(m_condition, context, item); } -void InfoExpression::Initialize() +void InfoExpression::Initialize(CGUIInfoManager* infoMgr) { + InfoBool::Initialize(infoMgr); if (!Parse(m_expression)) { CLog::Log(LOGERROR, "Error parsing boolean expression {}", m_expression); - m_expression_tree = std::make_shared(CServiceBroker::GetGUI()->GetInfoManager().Register("false", 0), false); + m_expression_tree = std::make_shared(m_infoMgr->Register("false", 0), false); } } @@ -213,9 +213,6 @@ bool InfoExpression::Parse(const std::string &expression) // 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))) @@ -242,7 +239,7 @@ bool InfoExpression::Parse(const std::string &expression) } if (!operand.empty()) { - InfoPtr info = infoMgr.Register(operand, m_context); + InfoPtr info = m_infoMgr->Register(operand, m_context); if (!info) { CLog::Log(LOGERROR, "Bad operand '{}'", operand); @@ -293,7 +290,7 @@ bool InfoExpression::Parse(const std::string &expression) } if (!operand.empty()) { - InfoPtr info = infoMgr.Register(operand, m_context); + InfoPtr info = m_infoMgr->Register(operand, m_context); if (!info) { CLog::Log(LOGERROR, "Bad operand '{}'", operand); diff --git a/xbmc/interfaces/info/InfoExpression.h b/xbmc/interfaces/info/InfoExpression.h index 1a0877c2fe8e6..20503d150a9e0 100644 --- a/xbmc/interfaces/info/InfoExpression.h +++ b/xbmc/interfaces/info/InfoExpression.h @@ -28,7 +28,7 @@ class InfoSingle : public InfoBool : InfoBool(expression, context, refreshCounter) { } - void Initialize() override; + void Initialize(CGUIInfoManager* infoMgr) override; void Update(int contextWindow, const CGUIListItem* item) override; @@ -47,7 +47,7 @@ class InfoExpression : public InfoBool } ~InfoExpression() override = default; - void Initialize() override; + void Initialize(CGUIInfoManager* infoMgr) override; void Update(int contextWindow, const CGUIListItem* item) override; From e978cfada851041c17afd7ddb81c016f11630ba5 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 13 Aug 2023 13:53:17 +0100 Subject: [PATCH 032/811] [SkinTimers] Add getters for stop and start actions --- xbmc/addons/gui/skin/SkinTimer.cpp | 10 ++++++++++ xbmc/addons/gui/skin/SkinTimer.h | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/xbmc/addons/gui/skin/SkinTimer.cpp b/xbmc/addons/gui/skin/SkinTimer.cpp index 6a1e073db2ce4..ca75fa62187de 100644 --- a/xbmc/addons/gui/skin/SkinTimer.cpp +++ b/xbmc/addons/gui/skin/SkinTimer.cpp @@ -85,6 +85,16 @@ INFO::InfoPtr CSkinTimer::GetStopCondition() const return m_stopCondition; } +const CGUIAction& CSkinTimer::GetStartActions() const +{ + return m_startActions; +} + +const CGUIAction& CSkinTimer::GetStopActions() const +{ + return m_stopActions; +} + bool CSkinTimer::ResetsOnStart() const { return m_resetOnStart; diff --git a/xbmc/addons/gui/skin/SkinTimer.h b/xbmc/addons/gui/skin/SkinTimer.h index 8886d5fe87a37..334a816dd3165 100644 --- a/xbmc/addons/gui/skin/SkinTimer.h +++ b/xbmc/addons/gui/skin/SkinTimer.h @@ -78,6 +78,16 @@ class CSkinTimer : public CStopWatch */ bool ResetsOnStart() const; + /*! \brief Gets the start actions of this skin timer + * \return the actions configured to start when the timer is started + */ + const CGUIAction& GetStartActions() const; + + /*! \brief Gets the stop actions of this skin timer + * \return the actions configured to start when the timer is stopped + */ + const CGUIAction& GetStopActions() const; + /*! \brief Evaluates the timer start boolean info expression returning the respective result. * \details Called from the skin timer manager to check if the timer should be started * \return true if the condition is true, false otherwise From 006f7af5a7dab7610c25666a61bcea7750dfa246 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 13 Aug 2023 14:02:05 +0100 Subject: [PATCH 033/811] [CGUIAction] Provide methods for getting the action count and conditional actions --- xbmc/guilib/GUIAction.cpp | 15 +++++++++++++++ xbmc/guilib/GUIAction.h | 8 ++++++++ 2 files changed, 23 insertions(+) diff --git a/xbmc/guilib/GUIAction.cpp b/xbmc/guilib/GUIAction.cpp index 030fdc34c9474..8b86338557554 100644 --- a/xbmc/guilib/GUIAction.cpp +++ b/xbmc/guilib/GUIAction.cpp @@ -121,6 +121,16 @@ void CGUIAction::SetNavigation(int id) m_actions.emplace_back(std::move(strId)); } +bool CGUIAction::HasConditionalActions() const +{ + for (const auto& i : m_actions) + { + if (i.HasCondition()) + return true; + } + return false; +} + bool CGUIAction::HasActionsMeetingCondition() const { CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager(); @@ -137,6 +147,11 @@ bool CGUIAction::HasAnyActions() const return m_actions.size() > 0; } +size_t CGUIAction::GetActionCount() const +{ + return m_actions.size(); +} + void CGUIAction::Append(const CExecutableAction& action) { m_actions.emplace_back(action); diff --git a/xbmc/guilib/GUIAction.h b/xbmc/guilib/GUIAction.h index 1c881c5984d22..0502a2e1e92bf 100644 --- a/xbmc/guilib/GUIAction.h +++ b/xbmc/guilib/GUIAction.h @@ -84,6 +84,10 @@ class CGUIAction * Execute actions (no navigation paths); if action is paired with condition - evaluate condition first */ bool ExecuteActions(int controlID, int parentID, const CGUIListItemPtr& item = nullptr) const; + /** + * Check if there are any conditional actions + */ + bool HasConditionalActions() const; /** * Check if there is any action that meet its condition */ @@ -92,6 +96,10 @@ class CGUIAction * Check if there is any action */ bool HasAnyActions() const; + /** + * Get the total number of actions + */ + size_t GetActionCount() const; /** * Get navigation route that meet its conditions first */ From caf2ce3b3b285c737bef1ddfc47055f8bcfb9cff Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 12 Aug 2023 23:44:22 +0100 Subject: [PATCH 034/811] [skintimers] Add unit tests for skintimer parsing --- cmake/installdata/test-reference-data.txt | 1 + cmake/treedata/common/tests.txt | 1 + xbmc/addons/gui/skin/test/CMakeLists.txt | 3 + xbmc/addons/gui/skin/test/TestSkinTimers.cpp | 88 +++++++++++++++++++ xbmc/addons/gui/skin/test/testdata/Timers.xml | 50 +++++++++++ 5 files changed, 143 insertions(+) create mode 100644 xbmc/addons/gui/skin/test/CMakeLists.txt create mode 100644 xbmc/addons/gui/skin/test/TestSkinTimers.cpp create mode 100644 xbmc/addons/gui/skin/test/testdata/Timers.xml diff --git a/cmake/installdata/test-reference-data.txt b/cmake/installdata/test-reference-data.txt index cde76da29d16d..504dce7c277a0 100644 --- a/cmake/installdata/test-reference-data.txt +++ b/cmake/installdata/test-reference-data.txt @@ -1,3 +1,4 @@ +xbmc/addons/gui/skin/test/testdata/Timers.xml xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion1.txt xbmc/cores/VideoPlayer/test/edl/testdata/comskipversion2.txt xbmc/cores/VideoPlayer/test/edl/testdata/edlautowindautowait.edl diff --git a/cmake/treedata/common/tests.txt b/cmake/treedata/common/tests.txt index 468aca1b2c827..b185a57fd2561 100644 --- a/cmake/treedata/common/tests.txt +++ b/cmake/treedata/common/tests.txt @@ -1,4 +1,5 @@ xbmc/addons/test test/addons +xbmc/addons/gui/skin/test test/skin xbmc/cores/AudioEngine/Sinks/test test/audioengine_sinks xbmc/cores/VideoPlayer/test/edl test/edl xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/test test/videoshaders diff --git a/xbmc/addons/gui/skin/test/CMakeLists.txt b/xbmc/addons/gui/skin/test/CMakeLists.txt new file mode 100644 index 0000000000000..ee97ac66a9669 --- /dev/null +++ b/xbmc/addons/gui/skin/test/CMakeLists.txt @@ -0,0 +1,3 @@ +set(SOURCES TestSkinTimers.cpp) + +core_add_test_library(skin_test) diff --git a/xbmc/addons/gui/skin/test/TestSkinTimers.cpp b/xbmc/addons/gui/skin/test/TestSkinTimers.cpp new file mode 100644 index 0000000000000..b03d9abe0f578 --- /dev/null +++ b/xbmc/addons/gui/skin/test/TestSkinTimers.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2023- Team Kodi + * 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 "GUIInfoManager.h" +#include "addons/gui/skin/SkinTimerManager.h" +#include "test/TestUtils.h" + +#include + +class TestSkinTimers : public ::testing::Test +{ +protected: + TestSkinTimers() = default; +}; + +TEST_F(TestSkinTimers, TestSkinTimerParsing) +{ + auto timersFile = XBMC_REF_FILE_PATH("xbmc/addons/gui/skin/test/testdata/Timers.xml"); + CGUIInfoManager infoMgr; + CSkinTimerManager skinTimerManager{infoMgr}; + skinTimerManager.LoadTimers(timersFile); + // ensure only 5 timers are loaded (there are two invalid timers in the test file) + EXPECT_EQ(skinTimerManager.GetTimerCount(), 5); + // timer1 + EXPECT_EQ(skinTimerManager.TimerExists("timer1"), true); + auto timer1 = skinTimerManager.GrabTimer("timer1"); + EXPECT_NE(timer1, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 4); + EXPECT_EQ(timer1->GetName(), "timer1"); + EXPECT_EQ(timer1->ResetsOnStart(), true); + EXPECT_EQ(timer1->GetStartCondition()->GetExpression(), "true"); + EXPECT_EQ(timer1->GetStopCondition()->GetExpression(), "true"); + EXPECT_EQ(timer1->GetResetCondition()->GetExpression(), "true"); + EXPECT_EQ(timer1->GetStartActions().GetActionCount(), 2); + EXPECT_EQ(timer1->GetStopActions().GetActionCount(), 2); + EXPECT_EQ(timer1->GetStartActions().HasConditionalActions(), false); + EXPECT_EQ(timer1->GetStopActions().HasConditionalActions(), false); + EXPECT_EQ(timer1->GetStartActions().HasAnyActions(), true); + EXPECT_EQ(timer1->GetStopActions().HasAnyActions(), true); + // timer2 + auto timer2 = skinTimerManager.GrabTimer("timer2"); + EXPECT_NE(timer2, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 3); + EXPECT_EQ(timer2->ResetsOnStart(), false); + EXPECT_EQ(timer2->GetStartActions().GetActionCount(), 1); + EXPECT_EQ(timer2->GetStopActions().GetActionCount(), 1); + EXPECT_EQ(timer2->GetStartActions().HasConditionalActions(), false); + EXPECT_EQ(timer2->GetStopActions().HasConditionalActions(), false); + EXPECT_EQ(timer2->GetStartActions().HasAnyActions(), true); + EXPECT_EQ(timer2->GetStopActions().HasAnyActions(), true); + // timer3 + auto timer3 = skinTimerManager.GrabTimer("timer3"); + EXPECT_NE(timer3, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 2); + EXPECT_EQ(timer3->GetName(), "timer3"); + EXPECT_EQ(timer3->GetStartActions().HasConditionalActions(), false); + EXPECT_EQ(timer3->GetStopActions().HasConditionalActions(), false); + EXPECT_EQ(timer3->GetStartActions().HasAnyActions(), false); + EXPECT_EQ(timer3->GetStopActions().HasAnyActions(), false); + EXPECT_EQ(timer3->GetStartCondition(), nullptr); + EXPECT_EQ(timer3->GetStopCondition(), nullptr); + EXPECT_EQ(timer3->GetResetCondition(), nullptr); + // timer4 + auto timer4 = skinTimerManager.GrabTimer("timer4"); + EXPECT_NE(timer4, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 1); + EXPECT_EQ(timer4->GetName(), "timer4"); + EXPECT_EQ(timer4->GetStartCondition(), nullptr); + EXPECT_EQ(timer4->GetStopCondition(), nullptr); + EXPECT_EQ(timer4->GetResetCondition(), nullptr); + EXPECT_EQ(timer4->GetStartActions().HasAnyActions(), true); + EXPECT_EQ(timer4->GetStopActions().HasAnyActions(), true); + EXPECT_EQ(timer4->GetStartActions().HasConditionalActions(), false); + EXPECT_EQ(timer4->GetStopActions().HasConditionalActions(), false); + // timer5 + auto timer5 = skinTimerManager.GrabTimer("timer5"); + EXPECT_NE(timer5, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 0); + EXPECT_EQ(timer5->GetName(), "timer5"); + EXPECT_EQ(timer5->GetStartActions().HasAnyActions(), true); + EXPECT_EQ(timer5->GetStopActions().HasAnyActions(), true); + EXPECT_EQ(timer5->GetStartActions().HasConditionalActions(), true); + EXPECT_EQ(timer5->GetStopActions().HasConditionalActions(), true); +} diff --git a/xbmc/addons/gui/skin/test/testdata/Timers.xml b/xbmc/addons/gui/skin/test/testdata/Timers.xml new file mode 100644 index 0000000000000..f20827a73565e --- /dev/null +++ b/xbmc/addons/gui/skin/test/testdata/Timers.xml @@ -0,0 +1,50 @@ + + + + timer1 + This is a valid timer with multiple actions and resets everytime it is started + true + true + true + Notification(timer1, notification 1, 1000) + Notification(timer1, notification 2, 1000) + Notification(timer1, notification 3, 1000) + Notification(timer1, notification 4, 1000) + + + timer2 + This is a valid timer without reset on start + true + true + true + Notification(timer1, notification 1, 1000) + Notification(timer1, notification 2, 1000) + + + timer3 + This is a valid timer fully managed from builtins + + + timer4 + This is a valid timer fully managed from builtins (and executes a few bultins) + Notification(timer1, notification 1, 1000) + Notification(timer1, notification 2, 1000) + + + timer5 + This is a timer with conditional execution on builtins + Notification(timer1, notification 1, 1000) + Notification(timer1, notification 2, 1000) + + + + This is an invalid timer (name empty) + true + true + + + This is an invalid timer (no name element) + true + true + + From a41d6baadc02125340c981ef21ea843d4d27f8ac Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 15 Aug 2023 12:59:46 +0100 Subject: [PATCH 035/811] [skintimers] Avoid double lookups on timer map --- xbmc/addons/gui/skin/SkinTimerManager.cpp | 30 +++++++++++------------ 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index fe247f1d1a0bd..d0a9ce4e8cf03 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -136,42 +136,40 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) bool CSkinTimerManager::TimerIsRunning(const std::string& timer) const { - if (!TimerExists(timer)) + if (auto iter = m_timers.find(timer); iter != m_timers.end()) { - CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); - return false; + return iter->second->IsRunning(); } - return m_timers.at(timer)->IsRunning(); + CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); + return false; } float CSkinTimerManager::GetTimerElapsedSeconds(const std::string& timer) const { - if (!TimerExists(timer)) + if (auto iter = m_timers.find(timer); iter != m_timers.end()) { - CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); - return 0; + return iter->second->GetElapsedSeconds(); } - return m_timers.at(timer)->GetElapsedSeconds(); + CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); + return 0; } void CSkinTimerManager::TimerStart(const std::string& timer) const { - if (!TimerExists(timer)) + if (auto iter = m_timers.find(timer); iter != m_timers.end()) { - CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); - return; + return iter->second->Start(); } - m_timers.at(timer)->Start(); + CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); } void CSkinTimerManager::TimerStop(const std::string& timer) const { - if (!TimerExists(timer)) + if (auto iter = m_timers.find(timer); iter != m_timers.end()) { - CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); - return; + return iter->second->Stop(); } - m_timers.at(timer)->Stop(); + CLog::LogF(LOGERROR, "Couldn't find Skin Timer with name: {}", timer); } size_t CSkinTimerManager::GetTimerCount() const From 74c8b8b2109d08335fc5b60df4deebf241326526 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 16 Aug 2023 17:01:47 +0200 Subject: [PATCH 036/811] [Android] Replaces some value strings used directly in the code --- xbmc/platform/android/activity/XBMCApp.cpp | 74 ++++++++++--------- .../storage/AndroidStorageProvider.cpp | 4 +- xbmc/utils/SystemInfo.cpp | 6 +- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 6609775b487c0..02f52d8e90838 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -237,15 +237,15 @@ void CXBMCApp::onStart() // Some intent filters MUST be registered in code rather than through the manifest CJNIIntentFilter intentFilter; - intentFilter.addAction("android.intent.action.BATTERY_CHANGED"); - intentFilter.addAction("android.intent.action.SCREEN_ON"); - intentFilter.addAction("android.intent.action.HEADSET_PLUG"); + intentFilter.addAction(CJNIIntent::ACTION_BATTERY_CHANGED); + intentFilter.addAction(CJNIIntent::ACTION_SCREEN_ON); + intentFilter.addAction(CJNIIntent::ACTION_HEADSET_PLUG); // We currently use HDMI_AUDIO_PLUG for mode switch, don't use it on TV's (device_type = "0" if (m_hdmiSource) - intentFilter.addAction("android.media.action.HDMI_AUDIO_PLUG"); + intentFilter.addAction(CJNIAudioManager::ACTION_HDMI_AUDIO_PLUG); - intentFilter.addAction("android.intent.action.SCREEN_OFF"); - intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE"); + intentFilter.addAction(CJNIIntent::ACTION_SCREEN_OFF); + intentFilter.addAction(CJNIConnectivityManager::CONNECTIVITY_ACTION); registerReceiver(*this, intentFilter); m_mediaSession = std::make_unique(); m_activityManager = @@ -259,7 +259,7 @@ namespace { bool isHeadsetPlugged() { - CJNIAudioManager audioManager(CXBMCApp::getSystemService("audio")); + CJNIAudioManager audioManager(CXBMCApp::getSystemService(CJNIContext::AUDIO_SERVICE)); if (CJNIBuild::SDK_INT >= 26) { @@ -442,7 +442,7 @@ void CXBMCApp::onLostFocus() void CXBMCApp::RegisterDisplayListenerCallback(void*) { - CJNIDisplayManager displayManager(getSystemService("display")); + CJNIDisplayManager displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE)); if (displayManager) { android_printf("CXBMCApp: installing DisplayManager::DisplayListener"); @@ -452,7 +452,7 @@ void CXBMCApp::RegisterDisplayListenerCallback(void*) void CXBMCApp::UnregisterDisplayListener() { - CJNIDisplayManager displayManager(getSystemService("display")); + CJNIDisplayManager displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE)); if (displayManager) { android_printf("CXBMCApp: removing DisplayManager::DisplayListener"); @@ -546,7 +546,7 @@ void CXBMCApp::KeepScreenOn(bool on) bool CXBMCApp::AcquireAudioFocus() { - CJNIAudioManager audioManager(getSystemService("audio")); + CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE)); int result; @@ -585,7 +585,7 @@ bool CXBMCApp::AcquireAudioFocus() bool CXBMCApp::ReleaseAudioFocus() { - CJNIAudioManager audioManager(getSystemService("audio")); + CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE)); int result; if (CJNIBuild::SDK_INT >= 26) @@ -891,12 +891,15 @@ void CXBMCApp::UpdateSessionState() m_playback_state |= PLAYBACK_STATE_VIDEO; else m_playback_state &= ~PLAYBACK_STATE_VIDEO; + if (appPlayer->HasAudio()) m_playback_state |= PLAYBACK_STATE_AUDIO; else m_playback_state &= ~PLAYBACK_STATE_AUDIO; + pos = appPlayer->GetTime(); speed = appPlayer->GetPlaySpeed(); + if (m_playback_state & PLAYBACK_STATE_PLAYING) state = CJNIPlaybackState::STATE_PLAYING; else @@ -904,10 +907,9 @@ void CXBMCApp::UpdateSessionState() } else state = CJNIPlaybackState::STATE_STOPPED; - builder - .setState(state, pos, speed, CJNISystemClock::elapsedRealtime()) - .setActions(0xffffffffffffffff) - ; + + builder.setState(state, pos, speed, CJNISystemClock::elapsedRealtime()) + .setActions(CJNIPlaybackState::PLAYBACK_POSITION_UNKNOWN); m_mediaSession->updatePlaybackState(builder.build()); } @@ -963,13 +965,13 @@ void CXBMCApp::OnPlayBackStopped() const CJNIViewInputDevice CXBMCApp::GetInputDevice(int deviceId) { - CJNIInputManager inputManager(getSystemService("input")); + CJNIInputManager inputManager(getSystemService(CJNIContext::INPUT_SERVICE)); return inputManager.getInputDevice(deviceId); } std::vector CXBMCApp::GetInputDeviceIds() { - CJNIInputManager inputManager(getSystemService("input")); + CJNIInputManager inputManager(getSystemService(CJNIContext::INPUT_SERVICE)); return inputManager.getInputDeviceIds(); } @@ -1030,7 +1032,7 @@ bool CXBMCApp::StartActivity(const std::string& package, GetPackageManager().getLaunchIntentForPackage(package) : CJNIIntent(intent); - if (intent.empty() && GetPackageManager().hasSystemFeature("android.software.leanback")) + if (intent.empty() && GetPackageManager().hasSystemFeature(CJNIPackageManager::FEATURE_LEANBACK)) { CJNIIntent leanbackIntent = GetPackageManager().getLeanbackLaunchIntentForPackage(package); if (leanbackIntent) @@ -1235,7 +1237,7 @@ int CXBMCApp::GetMaxSystemVolume() int CXBMCApp::GetMaxSystemVolume(JNIEnv *env) { - CJNIAudioManager audioManager(getSystemService("audio")); + CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE)); if (audioManager) return audioManager.getStreamMaxVolume(); android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager"); @@ -1244,7 +1246,7 @@ int CXBMCApp::GetMaxSystemVolume(JNIEnv *env) float CXBMCApp::GetSystemVolume() { - CJNIAudioManager audioManager(getSystemService("audio")); + CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE)); if (audioManager) return (float)audioManager.getStreamVolume() / GetMaxSystemVolume(); else @@ -1256,7 +1258,7 @@ float CXBMCApp::GetSystemVolume() void CXBMCApp::SetSystemVolume(float percent) { - CJNIAudioManager audioManager(getSystemService("audio")); + CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE)); int maxVolume = (int)(GetMaxSystemVolume() * percent); if (audioManager) audioManager.setStreamVolume(maxVolume); @@ -1271,9 +1273,9 @@ void CXBMCApp::onReceive(CJNIIntent intent) std::string action = intent.getAction(); CLog::Log(LOGDEBUG, "CXBMCApp::onReceive - Got intent. Action: {}", action); - if (action == "android.intent.action.BATTERY_CHANGED") - m_batteryLevel = intent.getIntExtra("level",-1); - else if (action == "android.intent.action.DREAMING_STOPPED") + if (action == CJNIIntent::ACTION_BATTERY_CHANGED) + m_batteryLevel = intent.getIntExtra("level", -1); + else if (action == CJNIIntent::ACTION_DREAMING_STOPPED) { if (HasFocus()) { @@ -1282,11 +1284,11 @@ void CXBMCApp::onReceive(CJNIIntent intent) appPower->WakeUpScreenSaverAndDPMS(); } } - else if (action == "android.intent.action.HEADSET_PLUG" || - action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") + else if (action == CJNIIntent::ACTION_HEADSET_PLUG || + action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED") { bool newstate = m_headsetPlugged; - if (action == "android.intent.action.HEADSET_PLUG") + if (action == CJNIIntent::ACTION_HEADSET_PLUG) { newstate = (intent.getIntExtra("state", 0) != 0); @@ -1318,9 +1320,9 @@ void CXBMCApp::onReceive(CJNIIntent intent) iae->DeviceChange(); } } - else if (action == "android.media.action.HDMI_AUDIO_PLUG") + else if (action == CJNIAudioManager::ACTION_HDMI_AUDIO_PLUG) { - const bool hdmiPlugged = (intent.getIntExtra("android.media.extra.AUDIO_PLUG_STATE", 0) != 0); + const bool hdmiPlugged = (intent.getIntExtra(CJNIAudioManager::EXTRA_AUDIO_PLUG_STATE, 0) != 0); CLog::Log(LOGDEBUG, "-- HDMI is plugged in: {}", hdmiPlugged); if (m_hdmiSource && g_application.IsInitialized()) { @@ -1329,7 +1331,7 @@ void CXBMCApp::onReceive(CJNIIntent intent) dynamic_cast(winSystem)->SetHdmiState(hdmiPlugged); } } - else if (action == "android.intent.action.SCREEN_ON") + else if (action == CJNIIntent::ACTION_SCREEN_ON) { // Sent when the device wakes up and becomes interactive. // @@ -1350,7 +1352,7 @@ void CXBMCApp::onReceive(CJNIIntent intent) appPower->WakeUpScreenSaverAndDPMS(); } } - else if (action == "android.intent.action.SCREEN_OFF") + else if (action == CJNIIntent::ACTION_SCREEN_OFF) { // Sent when the device goes to sleep and becomes non-interactive. // @@ -1364,7 +1366,7 @@ void CXBMCApp::onReceive(CJNIIntent intent) static_cast(syscall)->SetSuspended(); } } - else if (action == "android.intent.action.MEDIA_BUTTON") + else if (action == CJNIIntent::ACTION_MEDIA_BUTTON) { if (m_playback_state == PLAYBACK_STATE_STOPPED) { @@ -1398,7 +1400,7 @@ void CXBMCApp::onReceive(CJNIIntent intent) else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_STOP) CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_STOP, 0, 0, up); } - else if (action == "android.net.conn.CONNECTIVITY_CHANGE") + else if (action == CJNIConnectivityManager::CONNECTIVITY_ACTION) { if (g_application.IsInitialized()) { @@ -1423,13 +1425,15 @@ void CXBMCApp::onNewIntent(CJNIIntent intent) std::string action = intent.getAction(); CLog::Log(LOGDEBUG, "CXBMCApp::onNewIntent - Got intent. Action: {}", action); std::string targetFile = GetFilenameFromIntent(intent); - if (!targetFile.empty() && (action == "android.intent.action.VIEW" || action == "android.intent.action.GET_CONTENT")) + if (!targetFile.empty() && + (action == CJNIIntent::ACTION_VIEW || action == CJNIIntent::ACTION_GET_CONTENT)) { CLog::Log(LOGDEBUG, "-- targetFile: {}", targetFile); CURL targeturl(targetFile); std::string value; - if (action == "android.intent.action.GET_CONTENT" || (targeturl.GetOption("showinfo", value) && value == "true")) + if (action == CJNIIntent::ACTION_GET_CONTENT || + (targeturl.GetOption("showinfo", value) && value == "true")) { if (targeturl.IsProtocol("videodb") || (targeturl.IsProtocol("special") && targetFile.find("playlists/video") != std::string::npos) diff --git a/xbmc/platform/android/storage/AndroidStorageProvider.cpp b/xbmc/platform/android/storage/AndroidStorageProvider.cpp index bb2fd01e83233..db9b71f1e4b67 100644 --- a/xbmc/platform/android/storage/AndroidStorageProvider.cpp +++ b/xbmc/platform/android/storage/AndroidStorageProvider.cpp @@ -143,13 +143,13 @@ void CAndroidStorageProvider::GetLocalDrives(VECSOURCES &localDrives) localDrives.push_back(share); } -void CAndroidStorageProvider::GetRemovableDrives(VECSOURCES &removableDrives) +void CAndroidStorageProvider::GetRemovableDrives(VECSOURCES& removableDrives) { if (CJNIBase::GetSDKVersion() >= 24) { bool inError = false; - CJNIStorageManager manager(CJNIContext::getSystemService("storage")); + CJNIStorageManager manager(CJNIContext::getSystemService(CJNIContext::STORAGE_SERVICE)); if (xbmc_jnienv()->ExceptionCheck()) { xbmc_jnienv()->ExceptionDescribe(); diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp index e3bda61db4f43..b842fcd8ee6af 100644 --- a/xbmc/utils/SystemInfo.cpp +++ b/xbmc/utils/SystemInfo.cpp @@ -597,7 +597,7 @@ std::string CSysInfo::GetOsName(bool emptyIfUnknown /* = false*/) static std::string osName; if (osName.empty()) { -#if defined (TARGET_WINDOWS) +#if defined(TARGET_WINDOWS) osName = GetKernelName() + "-based OS"; #elif defined(TARGET_FREEBSD) osName = GetKernelName(true); // FIXME: for FreeBSD OS name is a kernel name @@ -607,8 +607,8 @@ std::string CSysInfo::GetOsName(bool emptyIfUnknown /* = false*/) osName = "tvOS"; #elif defined(TARGET_DARWIN_OSX) osName = "macOS"; -#elif defined (TARGET_ANDROID) - if (CJNIContext::GetPackageManager().hasSystemFeature("android.software.leanback")) +#elif defined(TARGET_ANDROID) + if (CJNIContext::GetPackageManager().hasSystemFeature(CJNIPackageManager::FEATURE_LEANBACK)) osName = "Android TV"; else osName = "Android"; From e558e1ec5ba7b606791e68dfe4cb782c28298c4b Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Thu, 17 Aug 2023 10:04:18 +0200 Subject: [PATCH 037/811] [Android] CXBMCApp: remove unnecessary GetSDKVersion method --- xbmc/platform/android/activity/XBMCApp.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/xbmc/platform/android/activity/XBMCApp.h b/xbmc/platform/android/activity/XBMCApp.h index fbe49f57c504d..6ab6959f2fd6e 100644 --- a/xbmc/platform/android/activity/XBMCApp.h +++ b/xbmc/platform/android/activity/XBMCApp.h @@ -125,8 +125,6 @@ class CXBMCApp : public IActivityHandler, bool isValid() { return m_activity != NULL; } - int32_t GetSDKVersion() const { return m_activity->sdkVersion; } - void onStart() override; void onResume() override; void onPause() override; From ea625655cbb84873e5741ef267a10749908ca131 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 17 Aug 2023 12:15:26 +0100 Subject: [PATCH 038/811] [docs] Fix for-the-badge badges --- README.md | 10 +++++----- docs/CONTRIBUTING.md | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ab43f9563661a..5ab9086e06a75 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ Kodi couldn't exist without ## License Kodi is **[GPLv2 licensed](LICENSE.md)**. You may use, distribute and copy it under the license terms. - - - - - + + + + + diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index df7d2ffef506f..8277a6f8fdd86 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -45,6 +45,6 @@ The *pull request* description should only contain information relevant to the c ## Updating your PR Making a *pull request* adhere to the standards above can be difficult. If the maintainers notice anything that they'd like changed, they'll ask you to edit your *pull request* before it gets merged. **There's no need to open a new *pull request*, just edit the existing one**. If you're not sure how to do that, our **[git guide](GIT-FU.md)** provides a step by step guide. If you're still not sure, ask us for guidance. We're all fairly proficient with *git* and happy to be of assistance. - - + + From cfc89de8534b88d24d502436a3822a124fda69d8 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Thu, 17 Aug 2023 17:04:40 +0200 Subject: [PATCH 039/811] [DVDDemux] Add new audio codec descriptions --- .../VideoPlayer/DVDDemuxers/DVDDemux.cpp | 62 ++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp index d901d42aa4705..63fb9264a8348 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemux.cpp @@ -8,21 +8,41 @@ #include "DVDDemux.h" +#include "utils/StringUtils.h" + std::string CDemuxStreamAudio::GetStreamType() { std::string strInfo; switch (codec) { + //! @todo: With ffmpeg >= 6.1 add new AC4 codec case AV_CODEC_ID_AC3: strInfo = "AC3 "; break; case AV_CODEC_ID_EAC3: - strInfo = "DD+ "; + { + //! @todo: With ffmpeg >= 6.1 add new atmos profile case + // "JOC" its EAC3 Atmos underlying profile, there is no standard codec name string + if (StringUtils::Contains(codecName, "JOC")) + strInfo = "DD+ ATMOS "; + else + strInfo = "DD+ "; break; + } case AV_CODEC_ID_DTS: { + //! @todo: With ffmpeg >= 6.1 add new DTSX profile cases switch (profile) { + case FF_PROFILE_DTS_96_24: + strInfo = "DTS 96/24 "; + break; + case FF_PROFILE_DTS_ES: + strInfo = "DTS ES "; + break; + case FF_PROFILE_DTS_EXPRESS: + strInfo = "DTS EXPRESS "; + break; case FF_PROFILE_DTS_HD_MA: strInfo = "DTS-HD MA "; break; @@ -45,8 +65,46 @@ std::string CDemuxStreamAudio::GetStreamType() strInfo = "TrueHD "; break; case AV_CODEC_ID_AAC: - strInfo = "AAC "; + { + switch (profile) + { + case FF_PROFILE_AAC_LOW: + case FF_PROFILE_MPEG2_AAC_LOW: + strInfo = "AAC-LC "; + break; + case FF_PROFILE_AAC_HE: + case FF_PROFILE_MPEG2_AAC_HE: + strInfo = "HE-AAC "; + break; + case FF_PROFILE_AAC_HE_V2: + strInfo = "HE-AACv2 "; + break; + case FF_PROFILE_AAC_SSR: + strInfo = "AAC-SSR "; + break; + case FF_PROFILE_AAC_LTP: + strInfo = "AAC-LTP "; + break; + default: + { + // Try check by codec full string according to RFC 6381 + if (codecName == "mp4a.40.2" || codecName == "mp4a.40.17") + strInfo = "AAC-LC "; + else if (codecName == "mp4a.40.3") + strInfo = "AAC-SSR "; + else if (codecName == "mp4a.40.4" || codecName == "mp4a.40.19") + strInfo = "AAC-LTP "; + else if (codecName == "mp4a.40.5") + strInfo = "HE-AAC "; + else if (codecName == "mp4a.40.29") + strInfo = "HE-AACv2 "; + else + strInfo = "AAC "; + break; + } + } break; + } case AV_CODEC_ID_ALAC: strInfo = "ALAC "; break; From 37f64e5feafe4e6cc7ffb8b191736ae5b6907753 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 17 Aug 2023 18:35:40 +0200 Subject: [PATCH 040/811] [Windows] fix 'FileTimeToLocalFileTime' to account for daylight saving --- xbmc/platform/win32/XTimeUtils.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/xbmc/platform/win32/XTimeUtils.cpp b/xbmc/platform/win32/XTimeUtils.cpp index 46da4ea0adecb..79a680c077fae 100644 --- a/xbmc/platform/win32/XTimeUtils.cpp +++ b/xbmc/platform/win32/XTimeUtils.cpp @@ -84,12 +84,20 @@ void GetLocalTime(SystemTime* systemTime) int FileTimeToLocalFileTime(const FileTime* fileTime, FileTime* localFileTime) { - FILETIME file; + FILETIME file{}; file.dwLowDateTime = fileTime->lowDateTime; file.dwHighDateTime = fileTime->highDateTime; - FILETIME localFile; - int ret = ::FileTimeToLocalFileTime(&file, &localFile); + SYSTEMTIME systemTime{}; + if (FALSE == ::FileTimeToSystemTime(&file, &systemTime)) + return FALSE; + + SYSTEMTIME localSystemTime{}; + if (FALSE == ::SystemTimeToTzSpecificLocalTime(nullptr, &systemTime, &localSystemTime)) + return FALSE; + + FILETIME localFile{}; + int ret = ::SystemTimeToFileTime(&localSystemTime, &localFile); localFileTime->lowDateTime = localFile.dwLowDateTime; localFileTime->highDateTime = localFile.dwHighDateTime; From e61a3d00b24b4fafcd84cc30d1d01213f34a750c Mon Sep 17 00:00:00 2001 From: amazingfate Date: Fri, 18 Aug 2023 01:21:22 +0800 Subject: [PATCH 041/811] Install gbm session entry --- cmake/scripts/linux/Install.cmake | 10 ++++++++++ tools/Linux/kodi-gbm-session.desktop.in | 8 ++++++++ 2 files changed, 18 insertions(+) create mode 100644 tools/Linux/kodi-gbm-session.desktop.in diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 4a388a1b21246..af06021b9f19e 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -39,6 +39,10 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi-standalone.sh.in configure_file(${CMAKE_SOURCE_DIR}/cmake/KodiConfig.cmake.in ${CORE_BUILD_DIR}/scripts/${APP_NAME}Config.cmake @ONLY) +# Configure gbm session entry +configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi-gbm-session.desktop.in + ${CORE_BUILD_DIR}/${APP_NAME_LC}-gbm-session.desktop @ONLY) + # Configure xsession entry configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi-xsession.desktop.in ${CORE_BUILD_DIR}/${APP_NAME_LC}-xsession.desktop @ONLY) @@ -85,6 +89,12 @@ foreach(file ${install_data}) COMPONENT kodi) endforeach() +# Install gbm session entry +install(FILES ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${APP_NAME_LC}-gbm-session.desktop + RENAME ${APP_NAME_LC}-gbm.desktop + DESTINATION ${datarootdir}/wayland-sessions + COMPONENT kodi) + # Install xsession entry install(FILES ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${APP_NAME_LC}-xsession.desktop RENAME ${APP_NAME_LC}.desktop diff --git a/tools/Linux/kodi-gbm-session.desktop.in b/tools/Linux/kodi-gbm-session.desktop.in new file mode 100644 index 0000000000000..83f82f4d2b5b6 --- /dev/null +++ b/tools/Linux/kodi-gbm-session.desktop.in @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=@APP_NAME@ on GBM +Comment=This session will start @APP_NAME@ media center +Exec=@APP_NAME_LC@-standalone --windowing=gbm +TryExec=@APP_NAME_LC@-standalone +Type=Application +Keywords=audio;video;media;center;tv;movies;series;songs;remote; +Icon=@APP_NAME_LC@ From 242449fb4cec135f54e5768b6a18ebbca24276f2 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 17 Aug 2023 20:04:04 +0200 Subject: [PATCH 042/811] AdvancedSettings: exclude '@eaDir' from scan, auto generated by DSM (Synology) Have sub-folders prevents of use GetFastHash() on simple media sources --- xbmc/settings/AdvancedSettings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/settings/AdvancedSettings.cpp b/xbmc/settings/AdvancedSettings.cpp index 15136f1f31e2f..dc75c315866c7 100644 --- a/xbmc/settings/AdvancedSettings.cpp +++ b/xbmc/settings/AdvancedSettings.cpp @@ -218,6 +218,7 @@ void CAdvancedSettings::Initialize() m_allExcludeFromScanRegExps.emplace_back("[\\/]\\.\\_"); m_allExcludeFromScanRegExps.emplace_back("\\.DS_Store"); m_allExcludeFromScanRegExps.emplace_back("\\.AppleDouble"); + m_allExcludeFromScanRegExps.emplace_back("\\@eaDir"); // auto generated by DSM (Synology) m_moviesExcludeFromScanRegExps.clear(); m_moviesExcludeFromScanRegExps.emplace_back("-trailer"); From fc2fb379baadeafa7f5a213ca5a6f0932ecad389 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 18 Aug 2023 09:10:44 +0200 Subject: [PATCH 043/811] Unnecessary check of the API level currently running on the device to be greater than or equal to 21 as this is the minimum level required by the app --- xbmc/platform/android/activity/XBMCApp.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index 6609775b487c0..cf8c3d64424fb 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -984,19 +984,23 @@ std::vector CXBMCApp::GetApplications() const std::unique_lock lock(m_applicationsMutex); if (m_applications.empty()) { - CJNIList packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES); + CJNIList packageList = + GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES); int numPackages = packageList.size(); for (int i = 0; i < numPackages; i++) { - CJNIIntent intent = GetPackageManager().getLaunchIntentForPackage(packageList.get(i).packageName); - if (!intent && CJNIBuild::SDK_INT >= 21) - intent = GetPackageManager().getLeanbackLaunchIntentForPackage(packageList.get(i).packageName); + CJNIIntent intent = + GetPackageManager().getLaunchIntentForPackage(packageList.get(i).packageName); + if (!intent) + intent = + GetPackageManager().getLeanbackLaunchIntentForPackage(packageList.get(i).packageName); if (!intent) continue; androidPackage newPackage; newPackage.packageName = packageList.get(i).packageName; - newPackage.packageLabel = GetPackageManager().getApplicationLabel(packageList.get(i)).toString(); + newPackage.packageLabel = + GetPackageManager().getApplicationLabel(packageList.get(i)).toString(); newPackage.icon = packageList.get(i).icon; m_applications.emplace_back(newPackage); } From de9ca3d42167eea15be3b1bc81c8e6aff4738d2e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 29 Jul 2023 10:58:19 +0200 Subject: [PATCH 044/811] [video][Estuary] Video info dialog: Improve support for TV show seasons. --- .../resources/strings.po | 1 + addons/skin.estuary/xml/DialogVideoInfo.xml | 14 ++++-- addons/skin.estuary/xml/Variables.xml | 1 + xbmc/video/VideoDatabase.cpp | 27 +++++++++++ xbmc/video/VideoDatabase.h | 1 + xbmc/video/VideoInfoScanner.cpp | 14 +++++- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 47 ++++++++++++------- xbmc/video/jobs/VideoLibraryRefreshingJob.cpp | 19 +++++++- xbmc/video/windows/GUIWindowVideoBase.cpp | 24 ++++++++-- 9 files changed, 118 insertions(+), 30 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 47a8ac08f721f..5298264734441 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -13503,6 +13503,7 @@ msgstr "" #: xbmc/dialogs/GUIDialogSmartPlaylistRule.cpp #: xbmc/media/MediaTypes.cpp #: xbmc/playlists/SmartPlaylist.cpp +#: xbmc/video/dialogs/GUIDialogVideoInfo.cpp #: addons/skin.estuary/xml/View_51_Poster.xml #: addons/skin.estuary/xml/Variables.xml #: xbmc/view/GUIViewState.cpp diff --git a/addons/skin.estuary/xml/DialogVideoInfo.xml b/addons/skin.estuary/xml/DialogVideoInfo.xml index 81002fd778ffb..cfbc08d6213a3 100644 --- a/addons/skin.estuary/xml/DialogVideoInfo.xml +++ b/addons/skin.estuary/xml/DialogVideoInfo.xml @@ -207,28 +207,34 @@ - + + + + + + + - + - + - + diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 6567d6a59f8aa..6090d86e48620 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -372,6 +372,7 @@ $INFO[ListItem.Season]$INFO[ListItem.Episode,[COLOR grey]x[/COLOR],: ]$INFO[ListItem.Title] + $INFO[ListItem.Label] $INFO[ListItem.Tagline,[I],[/I]] $INFO[ListItem.Genre] diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index cc4979dadd37b..a87e6f962514b 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -2136,6 +2136,33 @@ bool CVideoDatabase::GetTvShowInfo(const std::string& strPath, CVideoInfoTag& de return false; } +bool CVideoDatabase::GetSeasonInfo(const std::string& path, + int season, + CVideoInfoTag& details, + CFileItem* item) +{ + try + { + const std::string sql = PrepareSQL("strPath='%s' AND season=%i", path.c_str(), season); + const std::string id = GetSingleValue("season_view", "idSeason", sql); + if (id.empty()) + { + CLog::LogF(LOGERROR, "Failed to obtain seasonId for path={}, season={}", path, season); + } + else + { + const int idSeason = static_cast(std::strtol(id.c_str(), nullptr, 10)); + return GetSeasonInfo(idSeason, details, item); + } + } + catch (...) + { + CLog::LogF(LOGERROR, "Exception while trying to to obtain seasonId for path={}, season={}", + path, season); + } + return false; +} + bool CVideoDatabase::GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails /* = true */) { return GetSeasonInfo(idSeason, details, allDetails, nullptr); diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index af04052cf13c6..8e9facdbab947 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -513,6 +513,7 @@ class CVideoDatabase : public CDatabase bool LoadVideoInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int getDetails = VideoDbDetailsAll); bool GetMovieInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idMovie = -1, int getDetails = VideoDbDetailsAll); bool GetTvShowInfo(const std::string& strPath, CVideoInfoTag& details, int idTvShow = -1, CFileItem* item = NULL, int getDetails = VideoDbDetailsAll); + bool GetSeasonInfo(const std::string& path, int season, CVideoInfoTag& details, CFileItem* item); bool GetSeasonInfo(int idSeason, CVideoInfoTag& details, CFileItem* item); bool GetSeasonInfo(int idSeason, CVideoInfoTag& details, bool allDetails = true); bool GetEpisodeBasicInfo(const std::string& strFilenameAndPath, CVideoInfoTag& details, int idEpisode = -1); diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 5b2059de60f23..629e76762414d 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -540,10 +540,18 @@ namespace VIDEO bool fetchEpisodes, CGUIDialogProgress* pDlgProgress) { - long idTvShow = -1; + const bool isSeason = + pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_type == MediaTypeSeason; + + int idTvShow = -1; + int idSeason = -1; std::string strPath = pItem->GetPath(); if (pItem->m_bIsFolder) + { idTvShow = m_database.GetTvShowId(strPath); + if (isSeason && idTvShow > -1) + idSeason = m_database.GetSeasonId(idTvShow, pItem->GetVideoInfoTag()->m_iSeason); + } else if (pItem->IsPlugin() && pItem->HasVideoInfoTag() && pItem->GetVideoInfoTag()->m_iIdShow >= 0) { // for plugin source we cannot get idTvShow from episode path with URIUtils::GetDirectory() in all cases @@ -557,8 +565,10 @@ namespace VIDEO { strPath = URIUtils::GetDirectory(strPath); idTvShow = m_database.GetTvShowId(strPath); + if (isSeason && idTvShow > -1) + idSeason = m_database.GetSeasonId(idTvShow, pItem->GetVideoInfoTag()->m_iSeason); } - if (idTvShow > -1 && (fetchEpisodes || !pItem->m_bIsFolder)) + if (idTvShow > -1 && (!isSeason || idSeason > -1) && (fetchEpisodes || !pItem->m_bIsFolder)) { INFO_RET ret = RetrieveInfoForEpisodes(pItem, idTvShow, info2, useLocal, pDlgProgress); if (ret == INFO_ADDED) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 5b3adb5a0b25d..46122ff34bcd0 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -386,7 +386,7 @@ void CGUIDialogVideoInfo::SetMovie(const CFileItem *item) loader.LoadItem(item.get()); } else - { // movie/show/episode + { // movie/show/season/episode for (CVideoInfoTag::iCast it = m_movieItem->GetVideoInfoTag()->m_cast.begin(); it != m_movieItem->GetVideoInfoTag()->m_cast.end(); ++it) { CFileItemPtr item(new CFileItem(it->strName)); @@ -479,6 +479,10 @@ void CGUIDialogVideoInfo::Update() { SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 20342); } + else if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeSeason) + { + SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 20360); // Episodes + } else { SET_CONTROL_LABEL(CONTROL_BTN_TRACKS, 206); @@ -662,7 +666,14 @@ void CGUIDialogVideoInfo::OnSearchItemFound(const CFileItem* pItem) if (type == VideoDbContentType::EPISODES) db.GetEpisodeInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); if (type == VideoDbContentType::TVSHOWS) - db.GetTvShowInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); + { + bool hasInfo = false; + const CVideoInfoTag* videoTag = pItem->GetVideoInfoTag(); + if (videoTag->m_type == MediaTypeSeason && videoTag->m_iSeason != -1) + hasInfo = db.GetSeasonInfo(videoTag->m_iIdSeason, movieDetails); + if (!hasInfo) + db.GetTvShowInfo(pItem->GetPath(), movieDetails, videoTag->m_iDbId); + } if (type == VideoDbContentType::MUSICVIDEOS) db.GetMusicVideoInfo(pItem->GetPath(), movieDetails, pItem->GetVideoInfoTag()->m_iDbId); db.Close(); @@ -690,9 +701,11 @@ void CGUIDialogVideoInfo::ClearCastList() void CGUIDialogVideoInfo::Play(bool resume) { - if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeTvShow) + std::string strPath; + + const CVideoInfoTag* videoTag = m_movieItem->GetVideoInfoTag(); + if (videoTag->m_type == MediaTypeTvShow || videoTag->m_type == MediaTypeSeason) { - std::string strPath; if (m_movieItem->IsPlugin()) { strPath = m_movieItem->GetPath(); @@ -703,25 +716,23 @@ void CGUIDialogVideoInfo::Play(bool resume) GetWindowManager().GetActiveWindow(), 0, GUI_MSG_UPDATE, 0); message.SetStringParam(strPath); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + return; } - else - CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath); } - else - { - strPath = StringUtils::Format("videodb://tvshows/titles/{}/", - m_movieItem->GetVideoInfoTag()->m_iDbId); - Close(); - CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV,strPath); - } - return; + else if (videoTag->m_type == MediaTypeTvShow) + strPath = StringUtils::Format("videodb://tvshows/titles/{}/", videoTag->m_iDbId); + else // season + strPath = StringUtils::Format("videodb://tvshows/titles/{}/{}/", videoTag->m_iIdShow, + videoTag->m_iSeason); + } + else if (videoTag->m_type == MediaTypeVideoCollection) + { + strPath = StringUtils::Format("videodb://movies/sets/{}/?setid={}", videoTag->m_iDbId, + videoTag->m_iDbId); } - if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + if (!strPath.empty()) { - std::string strPath = StringUtils::Format("videodb://movies/sets/{}/?setid={}", - m_movieItem->GetVideoInfoTag()->m_iDbId, - m_movieItem->GetVideoInfoTag()->m_iDbId); Close(); CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_VIDEO_NAV, strPath); return; diff --git a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp index dbac5ed486be0..fc8c9b68cbd65 100644 --- a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp +++ b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp @@ -320,6 +320,8 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) { if (!m_item->m_bIsFolder) db.DeleteEpisode(dbId); + else if (m_item->GetVideoInfoTag()->m_type == MediaTypeSeason) + db.DeleteSeason(dbId); else if (m_refreshAll) db.DeleteTvShow(dbId); else @@ -328,6 +330,7 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) } if (pluginTag || pluginArt) + { // set video info and art from plugin source with metadata.local scraper to items for (auto &i: items) { @@ -336,6 +339,7 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) if (pluginArt) i->SetArt(*pluginArt); } + } // finally download the information for the item CVideoInfoScanner scanner; @@ -361,9 +365,20 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) db.GetMusicVideoInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); else if (scraper->Content() == CONTENT_TVSHOWS) { - // update tvshow info to get updated episode numbers + // update tvshow/season info to get updated episode numbers if (m_item->m_bIsFolder) - db.GetTvShowInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + { + // Note: don't use any database ids (m_iDbId, m_idSeason, m_IdShow) of m_item's video + // info tag here. The db information might have been deleted and recreated afterwards, + // invalidating the old db ids and m_item is not (yet) updated at this point. + bool hasInfo = false; + const CVideoInfoTag* videoTag = m_item->GetVideoInfoTag(); + if (videoTag && videoTag->m_type == MediaTypeSeason && videoTag->m_iSeason != -1) + hasInfo = db.GetSeasonInfo(m_item->GetPath(), videoTag->m_iSeason, + *m_item->GetVideoInfoTag(), m_item.get()); + if (!hasInfo) + db.GetTvShowInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); + } else db.GetEpisodeInfo(m_item->GetPath(), *m_item->GetVideoInfoTag()); } diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 9d7762dcba2d7..2eead26fa5107 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -339,7 +339,11 @@ bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, b { if (item->m_bIsFolder) { - bHasInfo = m_database.GetTvShowInfo(item->GetPath(), movieDetails, dbId); + const CVideoInfoTag* videoTag = item->GetVideoInfoTag(); + if (videoTag && videoTag->m_type == MediaTypeSeason && videoTag->m_iSeason != -1) + bHasInfo = m_database.GetSeasonInfo(videoTag->m_iIdSeason, movieDetails); + if (!bHasInfo) + bHasInfo = m_database.GetTvShowInfo(item->GetPath(), movieDetails, dbId); } else { @@ -420,18 +424,30 @@ bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, b // 3. Run a loop so that if we Refresh we re-run this block do { - if (!CVideoLibraryQueue::GetInstance().RefreshItemModal(item, needsRefresh, pDlgInfo->RefreshAll())) - return listNeedsUpdating; + // reload images + //! !todo we need this to update images in video info dialog immediatly after refresh, but why? + CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); + OnMessage(reload); + + if (!CVideoLibraryQueue::GetInstance().RefreshItemModal(item, needsRefresh, + pDlgInfo->RefreshAll())) + break; // remove directory caches and reload images CUtil::DeleteVideoDatabaseDirectoryCache(); - CGUIMessage reload(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); OnMessage(reload); pDlgInfo->SetMovie(item.get()); pDlgInfo->Open(); item->SetArt("thumb", pDlgInfo->GetThumbnail()); needsRefresh = pDlgInfo->NeedRefresh(); + if (needsRefresh && pDlgInfo->GetCurrentListItem() != nullptr) + { + item = pDlgInfo->GetCurrentListItem(); + + if (item->IsVideoDb() && item->HasVideoInfoTag()) + item->SetPath(item->GetVideoInfoTag()->GetPath()); + } listNeedsUpdating = true; } while (needsRefresh); From 3c018cf4d063fa04c9c75c086562cc3fbafdd194 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Fri, 18 Aug 2023 19:05:29 +0100 Subject: [PATCH 045/811] [macos][nativewindowing] Kill FULLSCREEN_UPDATE event --- .../osx/OpenGL/WindowControllerMacOS.mm | 44 ++++++------------- 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm b/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm index 3259cc108cec9..06eea773e3702 100644 --- a/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm +++ b/xbmc/windowing/osx/OpenGL/WindowControllerMacOS.mm @@ -54,41 +54,25 @@ - (nullable instancetype)initWithTitle:(NSString*)title defaultSize:(NSSize)size - (void)windowDidResize:(NSNotification*)aNotification { - NSRect rect = [self.window.contentView - convertRectToBacking:[self.window contentRectForFrameRect:self.window.frame]]; - int width = static_cast(rect.size.width); - int height = static_cast(rect.size.height); - - XBMC_Event newEvent = {}; - if ((self.window.styleMask & NSWindowStyleMaskFullScreen) != NSWindowStyleMaskFullScreen) { - RESOLUTION res_index = RES_DESKTOP; - if ((width == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iWidth) && - (height == CDisplaySettings::GetInstance().GetResolutionInfo(res_index).iHeight)) - return; + NSRect rect = [self.window.contentView + convertRectToBacking:[self.window contentRectForFrameRect:self.window.frame]]; + XBMC_Event newEvent = {}; newEvent.type = XBMC_VIDEORESIZE; - } - else - { - // macos may trigger a resize/rescale event after (or in) the fullscreen state. Use a different event - // so that window coordinates are properly set (XBMC_VIDEORESIZE is only supposed to be used when running - // windowed) - newEvent.type = XBMC_FULLSCREEN_UPDATE; - } + newEvent.resize.w = static_cast(rect.size.width); + newEvent.resize.h = static_cast(rect.size.height); - newEvent.resize.w = width; - newEvent.resize.h = height; - - // check for valid sizes cause in some cases - // we are hit during fullscreen transition from macos - // and might be technically "zero" sized - if (newEvent.resize.w != 0 && newEvent.resize.h != 0) - { - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - appPort->OnEvent(newEvent); + // check for valid sizes cause in some cases + // we are hit during fullscreen transition from macos + // and might be technically "zero" sized + if (newEvent.resize.w != 0 && newEvent.resize.h != 0) + { + std::shared_ptr appPort = CServiceBroker::GetAppPort(); + if (appPort) + appPort->OnEvent(newEvent); + } } } From b587485c7a9f8286a62b5f3cbbce16ef6b631e7e Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 19 Aug 2023 19:04:08 +0100 Subject: [PATCH 046/811] [GUI] Remove XBMC_FULLSCREEN_UPDATE event --- xbmc/application/Application.cpp | 10 ---------- xbmc/windowing/XBMC_events.h | 1 - 2 files changed, 11 deletions(-) diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index 7d930b7145337..5bcf125e9d773 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -296,22 +296,12 @@ void CApplication::HandlePortEvents() //auto& gfxContext = CServiceBroker::GetWinSystem()->GetGfxContext(); //gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true); // try to resize window back to it's full screen size - //! TODO: DX windowing should emit XBMC_FULLSCREEN_UPDATE instead with the proper dimensions - //! and position to avoid the ifdef in common code auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP); CServiceBroker::GetWinSystem()->ResizeWindow(res_info.iScreenWidth, res_info.iScreenHeight, 0, 0); } #endif } break; - case XBMC_FULLSCREEN_UPDATE: - { - if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_fullScreen) - { - CServiceBroker::GetWinSystem()->ResizeWindow(newEvent.resize.w, newEvent.resize.h, 0, 0); - } - break; - } case XBMC_VIDEOMOVE: { CServiceBroker::GetWinSystem()->OnMove(newEvent.move.x, newEvent.move.y); diff --git a/xbmc/windowing/XBMC_events.h b/xbmc/windowing/XBMC_events.h index 97948fcca02e6..744cbff3f3c4f 100644 --- a/xbmc/windowing/XBMC_events.h +++ b/xbmc/windowing/XBMC_events.h @@ -22,7 +22,6 @@ typedef enum XBMC_MOUSEBUTTONUP, /* Mouse button released */ XBMC_QUIT, /* User-requested quit */ XBMC_VIDEORESIZE, /* User resized video mode */ - XBMC_FULLSCREEN_UPDATE, /* Triggered by an OS event when Kodi is running in fullscreen, rescale and repositioning is required */ XBMC_SCREENCHANGE, /* Window moved to a different screen */ XBMC_VIDEOMOVE, /* User moved the window */ XBMC_MODECHANGE, /* Video mode must be changed */ From 2356ed7f23d9d195e58c27f3d905066823cf4f12 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Thu, 17 Aug 2023 11:03:56 +0100 Subject: [PATCH 047/811] DVDDemuxFFmpeg: hasAudio/hasVideo does not need a packet hasAudio/hasVideo should be set based on streams seen. We don't want to wait for a packet to be seen otherwise we go through the hasAudio path even if video packets are coming later --- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 8bc376a976470..c53d05f910f35 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -2323,9 +2323,9 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState() if (idx == m_seekStream) return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[idx]; - if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && idx == m_pkt.pkt.stream_index) + if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - if (m_pkt.pkt.dts != AV_NOPTS_VALUE) + if (idx == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE) { if (!m_startTime) { @@ -2347,10 +2347,9 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState() if (static_cast(i) == m_seekStream) return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[i]; - if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO && - static_cast(i) == m_pkt.pkt.stream_index) + if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { - if (m_pkt.pkt.dts != AV_NOPTS_VALUE) + if (static_cast(i) == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE) { if (!m_startTime) { @@ -2385,9 +2384,10 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() if (idx == m_seekStream) return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[idx]; - if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && idx == m_pkt.pkt.stream_index) + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - if (m_pkt.pkt.dts != AV_NOPTS_VALUE && st->codecpar->extradata) + if (idx == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE && + st->codecpar->extradata) { if (!m_startTime) { @@ -2409,10 +2409,10 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() if (static_cast(i) == m_seekStream) return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[i]; - if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO && - static_cast(i) == m_pkt.pkt.stream_index) + if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { - if (m_pkt.pkt.dts != AV_NOPTS_VALUE && st->codecpar->extradata) + if (static_cast(i) == m_pkt.pkt.stream_index && m_pkt.pkt.dts != AV_NOPTS_VALUE && + st->codecpar->extradata) { if (!m_startTime) { From 945a0dfc72cde660f2234b659d5b3b0664bc4527 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Thu, 17 Aug 2023 13:52:34 +0100 Subject: [PATCH 048/811] DVDDemuxFFmpeg: Replace early return for ready streams The m_seekStream check was added to avoid IsTransportStreamReady returning false after a stream has started but most recent packet has no valid dts. But this prevents detection of stream changes (e.g. video stream appearing later than audio). Instead we can use the fact m_startTime is set to avoid issue with missing dts in most recent packet --- .../VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index c53d05f910f35..9cf6f155a3380 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -2319,9 +2319,6 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState() for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++) { int idx = m_pFormatContext->programs[m_program]->stream_index[i]; - // if we match m_seekStream then we are ready - if (idx == m_seekStream) - return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[idx]; if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2343,9 +2340,6 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState() { for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) { - // if we match m_seekStream then we are ready - if (static_cast(i) == m_seekStream) - return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[i]; if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { @@ -2363,6 +2357,8 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamAudioState() } } } + if (hasAudio && m_startTime) + return TRANSPORT_STREAM_STATE::READY; return (hasAudio) ? TRANSPORT_STREAM_STATE::NOTREADY : TRANSPORT_STREAM_STATE::NONE; } @@ -2380,9 +2376,6 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() for (unsigned int i = 0; i < m_pFormatContext->programs[m_program]->nb_stream_indexes; i++) { int idx = m_pFormatContext->programs[m_program]->stream_index[i]; - // if we match m_seekStream then we are ready - if (idx == m_seekStream) - return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[idx]; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -2405,9 +2398,6 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() { for (unsigned int i = 0; i < m_pFormatContext->nb_streams; i++) { - // if we match m_seekStream then we are ready - if (static_cast(i) == m_seekStream) - return TRANSPORT_STREAM_STATE::READY; st = m_pFormatContext->streams[i]; if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) { @@ -2426,6 +2416,8 @@ TRANSPORT_STREAM_STATE CDVDDemuxFFmpeg::TransportStreamVideoState() } } } + if (hasVideo && m_startTime) + return TRANSPORT_STREAM_STATE::READY; return (hasVideo) ? TRANSPORT_STREAM_STATE::NOTREADY : TRANSPORT_STREAM_STATE::NONE; } From 535b0ea85125aba9e18e25f1d3da56fbc5ed6428 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Thu, 17 Aug 2023 16:36:08 +0100 Subject: [PATCH 049/811] CDVDDemuxFFmpeg: After seek ensure a valid timestamp is read to support chapters m_currentPts is not currently set after a seek until a suitable packet is read. That may cause issues with chapters, so make sure m_currentPts is valid immediately after a seek. --- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 9cf6f155a3380..03388f761b1f3 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1350,8 +1350,29 @@ bool CDVDDemuxFFmpeg::SeekTime(double time, bool backwards, double* startpts) } } - CLog::Log(LOGDEBUG, "{} - seek to time {:.2f}s ret:{} hitEnd:{}", __FUNCTION__, time, ret, - hitEnd); + if (ret >= 0) + { + XbmcThreads::EndTime<> timer(1000ms); + while (m_currentPts == DVD_NOPTS_VALUE && !timer.IsTimePast()) + { + m_pkt.result = -1; + av_packet_unref(&m_pkt.pkt); + + DemuxPacket* pkt = Read(); + if (!pkt) + { + KODI::TIME::Sleep(10ms); + continue; + } + CDVDDemuxUtils::FreeDemuxPacket(pkt); + } + } + + if (m_currentPts == DVD_NOPTS_VALUE) + CLog::Log(LOGDEBUG, "{} - unknown position after seek", __FUNCTION__); + else + CLog::Log(LOGDEBUG, "{} - seek ended up on time {}", __FUNCTION__, + (int)(m_currentPts / DVD_TIME_BASE * 1000)); // in this case the start time is requested time if (startpts) From 44e72a4dd734f89a4ffe60faaa3f89bf5477a4f3 Mon Sep 17 00:00:00 2001 From: Frank Howie Date: Thu, 27 Jul 2023 22:19:58 +0200 Subject: [PATCH 050/811] [cleanup] change deprecated 'CAddonMgr::IsCompatible(const IAddon& addon)' this member takes a 'const std::shared_ptr&' now Signed-off-by: Frank Howie --- xbmc/addons/AddonManager.cpp | 14 +++++++------- xbmc/addons/AddonManager.h | 8 +++++--- xbmc/addons/AddonRepos.cpp | 4 ++-- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/xbmc/addons/AddonManager.cpp b/xbmc/addons/AddonManager.cpp index c4c5427df3550..7acc39107f8fa 100644 --- a/xbmc/addons/AddonManager.cpp +++ b/xbmc/addons/AddonManager.cpp @@ -901,7 +901,7 @@ bool CAddonMgr::EnableSingle(const std::string& id) auto eventLog = CServiceBroker::GetEventLog(); - if (!IsCompatible(*addon)) + if (!IsCompatible(addon)) { CLog::Log(LOGERROR, "Add-on '{}' is not compatible with Kodi", addon->ID()); if (eventLog) @@ -1111,9 +1111,9 @@ void CAddonMgr::PublishInstanceRemoved(const std::string& addonId, AddonInstance m_events.Publish(AddonEvents::InstanceRemoved(addonId, instanceId)); } -bool CAddonMgr::IsCompatible(const IAddon& addon) const +bool CAddonMgr::IsCompatible(const std::shared_ptr& addon) const { - for (const auto& dependency : addon.GetDependencies()) + for (const auto& dependency : addon->GetDependencies()) { if (!dependency.optional) { @@ -1122,10 +1122,10 @@ bool CAddonMgr::IsCompatible(const IAddon& addon) const if (StringUtils::StartsWith(dependency.id, "xbmc.") || StringUtils::StartsWith(dependency.id, "kodi.")) { - AddonPtr addon; - bool haveAddon = - GetAddon(dependency.id, addon, AddonType::UNKNOWN, OnlyEnabled::CHOICE_YES); - if (!haveAddon || !addon->MeetsVersion(dependency.versionMin, dependency.version)) + std::shared_ptr dep; + const bool haveDependency = + GetAddon(dependency.id, dep, AddonType::UNKNOWN, OnlyEnabled::CHOICE_YES); + if (!haveDependency || !dep->MeetsVersion(dependency.versionMin, dependency.version)) return false; } } diff --git a/xbmc/addons/AddonManager.h b/xbmc/addons/AddonManager.h index cdce156e0a326..10668125c042d 100644 --- a/xbmc/addons/AddonManager.h +++ b/xbmc/addons/AddonManager.h @@ -426,10 +426,12 @@ class CAddonMgr bool ServicesHasStarted() const; /*! - * @deprecated This addon function should no more used and becomes replaced - * in future with the other below by his callers. + * @brief Check if given addon is compatible with Kodi. + * + * @param[in] addon Addon to check + * @return true if compatible, false if not */ - bool IsCompatible(const IAddon& addon) const; + bool IsCompatible(const std::shared_ptr& addon) const; /*! * @brief Check given addon information is compatible with Kodi. diff --git a/xbmc/addons/AddonRepos.cpp b/xbmc/addons/AddonRepos.cpp index 80bfc63989971..5455ca4487d2c 100644 --- a/xbmc/addons/AddonRepos.cpp +++ b/xbmc/addons/AddonRepos.cpp @@ -120,7 +120,7 @@ bool CAddonRepos::LoadAddonsFromDatabase(const std::string& addonId, for (const auto& addon : m_allAddons) { - if (m_addonMgr.IsCompatible(*addon)) + if (m_addonMgr.IsCompatible(addon)) { m_addonsByRepoMap[addon->Origin()].insert({addon->ID(), addon}); } @@ -487,7 +487,7 @@ void CAddonRepos::BuildCompatibleVersionsList( for (const auto& addon : m_allAddons) { - if (m_addonMgr.IsCompatible(*addon)) + if (m_addonMgr.IsCompatible(addon)) { if (IsFromOfficialRepo(addon, CheckAddonPath::CHOICE_YES)) { From 093bc0457891cdec1ac08d10a64b7aa82e129472 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 17 Aug 2023 10:45:01 +0100 Subject: [PATCH 051/811] [Skintimers] Add test for a timer with all info/actions empty --- xbmc/addons/gui/skin/test/TestSkinTimers.cpp | 26 ++++++++++++++----- xbmc/addons/gui/skin/test/testdata/Timers.xml | 9 +++++++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/xbmc/addons/gui/skin/test/TestSkinTimers.cpp b/xbmc/addons/gui/skin/test/TestSkinTimers.cpp index b03d9abe0f578..1c173c527c00a 100644 --- a/xbmc/addons/gui/skin/test/TestSkinTimers.cpp +++ b/xbmc/addons/gui/skin/test/TestSkinTimers.cpp @@ -23,13 +23,13 @@ TEST_F(TestSkinTimers, TestSkinTimerParsing) CGUIInfoManager infoMgr; CSkinTimerManager skinTimerManager{infoMgr}; skinTimerManager.LoadTimers(timersFile); - // ensure only 5 timers are loaded (there are two invalid timers in the test file) - EXPECT_EQ(skinTimerManager.GetTimerCount(), 5); + // ensure only 6 timers are loaded (there are two invalid timers in the test file) + EXPECT_EQ(skinTimerManager.GetTimerCount(), 6); // timer1 EXPECT_EQ(skinTimerManager.TimerExists("timer1"), true); auto timer1 = skinTimerManager.GrabTimer("timer1"); EXPECT_NE(timer1, nullptr); - EXPECT_EQ(skinTimerManager.GetTimerCount(), 4); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 5); EXPECT_EQ(timer1->GetName(), "timer1"); EXPECT_EQ(timer1->ResetsOnStart(), true); EXPECT_EQ(timer1->GetStartCondition()->GetExpression(), "true"); @@ -44,7 +44,7 @@ TEST_F(TestSkinTimers, TestSkinTimerParsing) // timer2 auto timer2 = skinTimerManager.GrabTimer("timer2"); EXPECT_NE(timer2, nullptr); - EXPECT_EQ(skinTimerManager.GetTimerCount(), 3); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 4); EXPECT_EQ(timer2->ResetsOnStart(), false); EXPECT_EQ(timer2->GetStartActions().GetActionCount(), 1); EXPECT_EQ(timer2->GetStopActions().GetActionCount(), 1); @@ -55,7 +55,7 @@ TEST_F(TestSkinTimers, TestSkinTimerParsing) // timer3 auto timer3 = skinTimerManager.GrabTimer("timer3"); EXPECT_NE(timer3, nullptr); - EXPECT_EQ(skinTimerManager.GetTimerCount(), 2); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 3); EXPECT_EQ(timer3->GetName(), "timer3"); EXPECT_EQ(timer3->GetStartActions().HasConditionalActions(), false); EXPECT_EQ(timer3->GetStopActions().HasConditionalActions(), false); @@ -67,7 +67,7 @@ TEST_F(TestSkinTimers, TestSkinTimerParsing) // timer4 auto timer4 = skinTimerManager.GrabTimer("timer4"); EXPECT_NE(timer4, nullptr); - EXPECT_EQ(skinTimerManager.GetTimerCount(), 1); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 2); EXPECT_EQ(timer4->GetName(), "timer4"); EXPECT_EQ(timer4->GetStartCondition(), nullptr); EXPECT_EQ(timer4->GetStopCondition(), nullptr); @@ -79,10 +79,22 @@ TEST_F(TestSkinTimers, TestSkinTimerParsing) // timer5 auto timer5 = skinTimerManager.GrabTimer("timer5"); EXPECT_NE(timer5, nullptr); - EXPECT_EQ(skinTimerManager.GetTimerCount(), 0); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 1); EXPECT_EQ(timer5->GetName(), "timer5"); EXPECT_EQ(timer5->GetStartActions().HasAnyActions(), true); EXPECT_EQ(timer5->GetStopActions().HasAnyActions(), true); EXPECT_EQ(timer5->GetStartActions().HasConditionalActions(), true); EXPECT_EQ(timer5->GetStopActions().HasConditionalActions(), true); + // timer6 + auto timer6 = skinTimerManager.GrabTimer("timer6"); + EXPECT_NE(timer6, nullptr); + EXPECT_EQ(skinTimerManager.GetTimerCount(), 0); + EXPECT_EQ(timer6->GetName(), "timer6"); + EXPECT_EQ(timer6->GetStartCondition(), nullptr); + EXPECT_EQ(timer6->GetStopCondition(), nullptr); + EXPECT_EQ(timer6->GetResetCondition(), nullptr); + EXPECT_EQ(timer6->GetStartActions().HasAnyActions(), false); + EXPECT_EQ(timer6->GetStopActions().HasAnyActions(), false); + EXPECT_EQ(timer6->GetStartActions().HasConditionalActions(), false); + EXPECT_EQ(timer6->GetStopActions().HasConditionalActions(), false); } diff --git a/xbmc/addons/gui/skin/test/testdata/Timers.xml b/xbmc/addons/gui/skin/test/testdata/Timers.xml index f20827a73565e..1d833db408f9b 100644 --- a/xbmc/addons/gui/skin/test/testdata/Timers.xml +++ b/xbmc/addons/gui/skin/test/testdata/Timers.xml @@ -36,6 +36,15 @@ Notification(timer1, notification 1, 1000) Notification(timer1, notification 2, 1000) + + timer6 + This is a timer only with the name defined, all other props empty + + + + + + This is an invalid timer (name empty) From 56adf11743f022bb74aa10eb9eb8b5e6a934f96b Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Tue, 4 Jul 2023 13:06:12 +0100 Subject: [PATCH 052/811] [skintimers] Migrate to tinyxml2 --- xbmc/addons/gui/skin/SkinTimerManager.cpp | 38 ++++++++++------------- xbmc/addons/gui/skin/SkinTimerManager.h | 6 +++- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.cpp b/xbmc/addons/gui/skin/SkinTimerManager.cpp index d0a9ce4e8cf03..5d3a81e2893ec 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.cpp +++ b/xbmc/addons/gui/skin/SkinTimerManager.cpp @@ -12,7 +12,7 @@ #include "guilib/GUIAction.h" #include "guilib/GUIComponent.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include @@ -25,22 +25,22 @@ CSkinTimerManager::CSkinTimerManager(CGUIInfoManager& infoMgr) : m_infoMgr{infoM void CSkinTimerManager::LoadTimers(const std::string& path) { - CXBMCTinyXML doc; + CXBMCTinyXML2 doc; if (!doc.LoadFile(path)) { - CLog::LogF(LOGWARNING, "Could not load timers file {}: {} (row: {}, col: {})", path, - doc.ErrorDesc(), doc.ErrorRow(), doc.ErrorCol()); + CLog::LogF(LOGWARNING, "Could not load timers file {}: {} (Line: {})", path, doc.ErrorStr(), + doc.ErrorLineNum()); return; } - TiXmlElement* root = doc.RootElement(); + auto* root = doc.RootElement(); if (!root || !StringUtils::EqualsNoCase(root->Value(), "timers")) { CLog::LogF(LOGERROR, "Error loading timers file {}: Root element required.", path); return; } - const TiXmlElement* timerNode = root->FirstChildElement("timer"); + const auto* timerNode = root->FirstChildElement("timer"); while (timerNode) { LoadTimerInternal(timerNode); @@ -48,16 +48,15 @@ void CSkinTimerManager::LoadTimers(const std::string& path) } } -void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) +void CSkinTimerManager::LoadTimerInternal(const tinyxml2::XMLNode* node) { - if ((!node->FirstChild("name") || !node->FirstChild("name")->FirstChild() || - node->FirstChild("name")->FirstChild()->ValueStr().empty())) + if ((!node->FirstChildElement("name") || !node->FirstChildElement("name")->FirstChild())) { CLog::LogF(LOGERROR, "Missing required field 'name' for valid skin timer. Ignoring timer."); return; } - std::string timerName = node->FirstChild("name")->FirstChild()->Value(); + std::string timerName = node->FirstChildElement("name")->FirstChild()->Value(); if (TimerExists(timerName)) { CLog::LogF(LOGWARNING, @@ -69,10 +68,9 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) // timer start INFO::InfoPtr startInfo{nullptr}; bool resetOnStart{false}; - if (node->FirstChild("start") && node->FirstChild("start")->FirstChild() && - !node->FirstChild("start")->FirstChild()->ValueStr().empty()) + if (node->FirstChildElement("start") && node->FirstChildElement("start")->FirstChild()) { - startInfo = m_infoMgr.Register(node->FirstChild("start")->FirstChild()->ValueStr()); + startInfo = m_infoMgr.Register(node->FirstChildElement("start")->FirstChild()->Value()); // check if timer needs to be reset after start if (node->FirstChildElement("start")->Attribute("reset") && StringUtils::EqualsNoCase(node->FirstChildElement("start")->Attribute("reset"), "true")) @@ -83,23 +81,21 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) // timer reset INFO::InfoPtr resetInfo{nullptr}; - if (node->FirstChild("reset") && node->FirstChild("reset")->FirstChild() && - !node->FirstChild("reset")->FirstChild()->ValueStr().empty()) + if (node->FirstChildElement("reset") && node->FirstChildElement("reset")->FirstChild()) { - resetInfo = m_infoMgr.Register(node->FirstChild("reset")->FirstChild()->ValueStr()); + resetInfo = m_infoMgr.Register(node->FirstChildElement("reset")->FirstChild()->Value()); } // timer stop INFO::InfoPtr stopInfo{nullptr}; - if (node->FirstChild("stop") && node->FirstChild("stop")->FirstChild() && - !node->FirstChild("stop")->FirstChild()->ValueStr().empty()) + if (node->FirstChildElement("stop") && node->FirstChildElement("stop")->FirstChild()) { - stopInfo = m_infoMgr.Register(node->FirstChild("stop")->FirstChild()->ValueStr()); + stopInfo = m_infoMgr.Register(node->FirstChildElement("stop")->FirstChild()->Value()); } // process onstart actions CGUIAction startActions; startActions.EnableSendThreadMessageMode(); - const TiXmlElement* onStartElement = node->FirstChildElement("onstart"); + const auto* onStartElement = node->FirstChildElement("onstart"); while (onStartElement) { if (onStartElement->FirstChild()) @@ -116,7 +112,7 @@ void CSkinTimerManager::LoadTimerInternal(const TiXmlElement* node) // process onstop actions CGUIAction stopActions; stopActions.EnableSendThreadMessageMode(); - const TiXmlElement* onStopElement = node->FirstChildElement("onstop"); + const auto* onStopElement = node->FirstChildElement("onstop"); while (onStopElement) { if (onStopElement->FirstChild()) diff --git a/xbmc/addons/gui/skin/SkinTimerManager.h b/xbmc/addons/gui/skin/SkinTimerManager.h index 75a5695566eff..fff27cb046716 100644 --- a/xbmc/addons/gui/skin/SkinTimerManager.h +++ b/xbmc/addons/gui/skin/SkinTimerManager.h @@ -15,6 +15,10 @@ #include class CGUIInfoManager; +namespace tinyxml2 +{ +class XMLNode; +} /*! \brief CSkinTimerManager is the container and manager for Skin timers. Its role is that of * checking if the timer boolean conditions are valid, start or stop timers and execute the respective @@ -92,7 +96,7 @@ class CSkinTimerManager * \note Called internally from LoadTimers * \param node - the XML representation of a skin timer object */ - void LoadTimerInternal(const TiXmlElement* node); + void LoadTimerInternal(const tinyxml2::XMLNode* node); /*! Container for the skin timers */ std::map> m_timers; From f6e615584446f09e0b297cc74301e474d99b3849 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Sun, 20 Aug 2023 21:19:44 +0200 Subject: [PATCH 053/811] CWebSocketFrame: Fix alignment issues UBSAN error: xbmc/network/websocket/WebSocket.cpp:107:14: runtime error: load of misaligned address 0x63100021c802 for type 'const uint32_t' (aka 'const unsigned int'), which requires 4 byte alignment 0x63100021c802: note: pointer points here 00 00 88 82 cf d3 5c c3 cc 3a 00 be be be be be be be be be be be be be be be be be be be be be ^ #0 0x56360048bf64 in CWebSocketFrame::CWebSocketFrame(char const*, unsigned long) xbmc/network/websocket/WebSocket.cpp:107:14 #1 0x5636004a6905 in CWebSocketV8::GetFrame(char const*, unsigned long) xbmc/network/websocket/WebSocketV8.cpp:145:14 #2 0x563600491ec9 in CWebSocket::Handle(char const*&, unsigned long&, bool&) xbmc/network/websocket/WebSocket.cpp:298:34 #3 0x5636005b05dd in JSONRPC::CTCPServer::CWebSocketClient::PushBuffer(JSONRPC::CTCPServer*, char const*, int) xbmc/network/TCPServer.cpp:716:29 #4 0x5636005a3760 in JSONRPC::CTCPServer::Process() xbmc/network/TCPServer.cpp:171:33 #5 0x5636005a6858 in non-virtual thunk to JSONRPC::CTCPServer::Process() xbmc/network/TCPServer.cpp #6 0x5635fca1fe32 in CThread::Action() xbmc/threads/Thread.cpp:283:5 #7 0x5635fca225f6 in CThread::Create(bool)::$_0::operator()(CThread*, std::promise) const xbmc/threads/Thread.cpp:152:18 #8 0x5635fca212d6 in void std::__invoke_impl>(std::__invoke_other, CThread::Create(bool)::$_0&&, CThread*&&, std::promise&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/invoke.h:61:14 #9 0x5635fca20f06 in std::__invoke_result>::type std::__invoke>(CThread::Create(bool)::$_0&&, CThread*&&, std::promise&&) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/invoke.h:96:14 #10 0x5635fca20e3f in void std::thread::_Invoker>>::_M_invoke<0ul, 1ul, 2ul>(std::_Index_tuple<0ul, 1ul, 2ul>) /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/std_thread.h:292:13 #11 0x5635fca20cb8 in std::thread::_Invoker>>::operator()() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/std_thread.h:299:11 #12 0x5635fca20888 in std::thread::_State_impl>>>::_M_run() /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/13.2.1/../../../../include/c++/13.2.1/bits/std_thread.h:244:13 #13 0x7f03890e1942 in execute_native_thread_routine /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++11/thread.cc:104:18 #14 0x7f038a88c9ea (/usr/lib/libc.so.6+0x8c9ea) (BuildId: 316d0d3666387f0e8fb98773f51aa1801027c5ab) #15 0x7f038a910dfb (/usr/lib/libc.so.6+0x110dfb) (BuildId: 316d0d3666387f0e8fb98773f51aa1801027c5ab) SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior xbmc/network/websocket/WebSocket.cpp:107:14 in --- xbmc/network/websocket/WebSocket.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/xbmc/network/websocket/WebSocket.cpp b/xbmc/network/websocket/WebSocket.cpp index bbf4a017f66aa..ecec4efeee9a5 100644 --- a/xbmc/network/websocket/WebSocket.cpp +++ b/xbmc/network/websocket/WebSocket.cpp @@ -13,6 +13,7 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include #include #include @@ -85,12 +86,15 @@ CWebSocketFrame::CWebSocketFrame(const char* data, uint64_t length) int offset = 0; if (m_length == 126) { - m_length = (uint64_t)Endian_SwapBE16(*(const uint16_t *)(m_data + 2)); + uint16_t length; + std::memcpy(&length, m_data + 2, 2); + m_length = Endian_SwapBE16(length); offset = 2; } else if (m_length == 127) { - m_length = Endian_SwapBE64(*(const uint64_t *)(m_data + 2)); + std::memcpy(&m_length, m_data + 2, 8); + m_length = Endian_SwapBE64(m_length); offset = 8; } @@ -104,7 +108,7 @@ CWebSocketFrame::CWebSocketFrame(const char* data, uint64_t length) // Get the mask if (m_masked) { - m_mask = *(const uint32_t *)(m_data + LENGTH_MIN + offset); + std::memcpy(&m_mask, m_data + LENGTH_MIN + offset, 4); offset += 4; } From 567960ee8e2ea5d0d0b4c3e442a0cf3de7b036ea Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 27 Jul 2023 10:49:59 +1000 Subject: [PATCH 054/811] [input] migrate to tinyxml2 usage --- xbmc/games/controllers/Controller.cpp | 14 +-- xbmc/games/controllers/ControllerLayout.cpp | 15 ++-- xbmc/games/controllers/ControllerLayout.h | 7 +- .../controllers/input/PhysicalFeature.cpp | 4 +- .../games/controllers/input/PhysicalFeature.h | 7 +- .../controllers/input/PhysicalTopology.cpp | 11 ++- .../controllers/input/PhysicalTopology.h | 7 +- xbmc/games/ports/input/PhysicalPort.cpp | 11 ++- xbmc/games/ports/input/PhysicalPort.h | 7 +- xbmc/games/ports/input/PortManager.cpp | 86 +++++++++++-------- xbmc/games/ports/input/PortManager.h | 25 ++++-- xbmc/input/ButtonTranslator.cpp | 30 +++---- xbmc/input/ButtonTranslator.h | 8 +- xbmc/input/CustomControllerTranslator.cpp | 14 +-- xbmc/input/CustomControllerTranslator.h | 7 +- xbmc/input/IButtonMapper.h | 7 +- xbmc/input/IRTranslator.cpp | 28 +++--- xbmc/input/IRTranslator.h | 7 +- xbmc/input/JoystickMapper.cpp | 33 ++++--- xbmc/input/JoystickMapper.h | 14 +-- xbmc/input/KeyboardLayout.cpp | 21 ++--- xbmc/input/KeyboardLayout.h | 7 +- xbmc/input/KeyboardLayoutManager.cpp | 15 ++-- xbmc/input/KeyboardTranslator.cpp | 15 ++-- xbmc/input/KeyboardTranslator.h | 7 +- xbmc/input/TouchTranslator.cpp | 11 +-- xbmc/input/TouchTranslator.h | 11 ++- xbmc/input/mouse/MouseTranslator.cpp | 9 +- xbmc/input/mouse/MouseTranslator.h | 7 +- xbmc/peripherals/Peripherals.cpp | 29 ++++--- xbmc/peripherals/Peripherals.h | 8 +- xbmc/peripherals/devices/Peripheral.cpp | 24 ++++-- xbmc/peripherals/devices/Peripheral.h | 1 - 33 files changed, 304 insertions(+), 203 deletions(-) diff --git a/xbmc/games/controllers/Controller.cpp b/xbmc/games/controllers/Controller.cpp index e51af4e3a7a68..c910d4b31cb65 100644 --- a/xbmc/games/controllers/Controller.cpp +++ b/xbmc/games/controllers/Controller.cpp @@ -13,11 +13,12 @@ #include "URL.h" #include "addons/addoninfo/AddonType.h" #include "games/controllers/input/PhysicalTopology.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" #include +#include using namespace KODI; using namespace GAME; @@ -122,16 +123,17 @@ bool CController::LoadLayout(void) CLog::Log(LOGINFO, "Loading controller layout: {}", CURL::GetRedacted(strLayoutXmlPath)); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(strLayoutXmlPath)) { - CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorDesc(), - xmlDoc.ErrorRow()); + CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorStr(), + xmlDoc.ErrorLineNum()); return false; } - TiXmlElement* pRootElement = xmlDoc.RootElement(); - if (!pRootElement || pRootElement->NoChildren() || pRootElement->ValueStr() != LAYOUT_XML_ROOT) + auto* pRootElement = xmlDoc.RootElement(); + if (pRootElement == nullptr || pRootElement->NoChildren() || + std::strcmp(pRootElement->Value(), LAYOUT_XML_ROOT) != 0) { CLog::Log(LOGERROR, "Can't find root <{}> tag", LAYOUT_XML_ROOT); return false; diff --git a/xbmc/games/controllers/ControllerLayout.cpp b/xbmc/games/controllers/ControllerLayout.cpp index 85816c1c71695..26098627cf263 100644 --- a/xbmc/games/controllers/ControllerLayout.cpp +++ b/xbmc/games/controllers/ControllerLayout.cpp @@ -17,8 +17,11 @@ #include "utils/XMLUtils.h" #include "utils/log.h" +#include #include +#include + using namespace KODI; using namespace GAME; @@ -87,7 +90,7 @@ std::string CControllerLayout::ImagePath(void) const return path; } -void CControllerLayout::Deserialize(const TiXmlElement* pElement, +void CControllerLayout::Deserialize(const tinyxml2::XMLElement* pElement, const CController* controller, std::vector& features) { @@ -116,10 +119,10 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, if (!image.empty()) m_strImage = image; - for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; + for (const auto* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement()) { - if (pChild->ValueStr() == LAYOUT_XML_ELM_CATEGORY) + if (std::strcmp(pChild->Value(), LAYOUT_XML_ELM_CATEGORY) == 0) { // Category std::string strCategory = XMLUtils::GetAttribute(pChild, LAYOUT_XML_ATTR_CATEGORY_NAME); @@ -135,7 +138,7 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, std::istringstream(strCategoryLabelId) >> categoryLabelId; // Features - for (const TiXmlElement* pFeature = pChild->FirstChildElement(); pFeature != nullptr; + for (const auto* pFeature = pChild->FirstChildElement(); pFeature != nullptr; pFeature = pFeature->NextSiblingElement()) { CPhysicalFeature feature; @@ -144,7 +147,7 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, features.push_back(feature); } } - else if (pChild->ValueStr() == LAYOUT_XML_ELM_TOPOLOGY) + else if (std::strcmp(pChild->Value(), LAYOUT_XML_ELM_TOPOLOGY) == 0) { // Topology CPhysicalTopology topology; @@ -153,7 +156,7 @@ void CControllerLayout::Deserialize(const TiXmlElement* pElement, } else { - CLog::Log(LOGDEBUG, "Ignoring <{}> tag", pChild->ValueStr()); + CLog::Log(LOGDEBUG, "Ignoring <{}> tag", pChild->Value()); } } } diff --git a/xbmc/games/controllers/ControllerLayout.h b/xbmc/games/controllers/ControllerLayout.h index 15898374831a7..529ebf94172aa 100644 --- a/xbmc/games/controllers/ControllerLayout.h +++ b/xbmc/games/controllers/ControllerLayout.h @@ -12,7 +12,10 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} namespace KODI { @@ -76,7 +79,7 @@ class CControllerLayout * \param controller The controller, used to obtain read-only properties * \param features The deserialized features, if any */ - void Deserialize(const TiXmlElement* pLayoutElement, + void Deserialize(const tinyxml2::XMLElement* pLayoutElement, const CController* controller, std::vector& features); diff --git a/xbmc/games/controllers/input/PhysicalFeature.cpp b/xbmc/games/controllers/input/PhysicalFeature.cpp index ec79b6de3a415..6dfb049cb43ba 100644 --- a/xbmc/games/controllers/input/PhysicalFeature.cpp +++ b/xbmc/games/controllers/input/PhysicalFeature.cpp @@ -17,6 +17,8 @@ #include +#include + using namespace KODI; using namespace GAME; using namespace JOYSTICK; @@ -74,7 +76,7 @@ std::string CPhysicalFeature::Label() const return label; } -bool CPhysicalFeature::Deserialize(const TiXmlElement* pElement, +bool CPhysicalFeature::Deserialize(const tinyxml2::XMLElement* pElement, const CController* controller, FEATURE_CATEGORY category, int categoryLabelId) diff --git a/xbmc/games/controllers/input/PhysicalFeature.h b/xbmc/games/controllers/input/PhysicalFeature.h index 6319a63a5ee00..a1c5c01c63dc9 100644 --- a/xbmc/games/controllers/input/PhysicalFeature.h +++ b/xbmc/games/controllers/input/PhysicalFeature.h @@ -14,7 +14,10 @@ #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} namespace KODI { @@ -45,7 +48,7 @@ class CPhysicalFeature JOYSTICK::INPUT_TYPE InputType(void) const { return m_inputType; } KEYBOARD::KeySymbol Keycode() const { return m_keycode; } - bool Deserialize(const TiXmlElement* pElement, + bool Deserialize(const tinyxml2::XMLElement* pElement, const CController* controller, JOYSTICK::FEATURE_CATEGORY category, int categoryLabelId); diff --git a/xbmc/games/controllers/input/PhysicalTopology.cpp b/xbmc/games/controllers/input/PhysicalTopology.cpp index 2fecb5ac11c37..e8ad1dd27022c 100644 --- a/xbmc/games/controllers/input/PhysicalTopology.cpp +++ b/xbmc/games/controllers/input/PhysicalTopology.cpp @@ -12,8 +12,11 @@ #include "utils/XMLUtils.h" #include "utils/log.h" +#include #include +#include + using namespace KODI; using namespace GAME; @@ -28,7 +31,7 @@ void CPhysicalTopology::Reset() *this = std::move(defaultTopology); } -bool CPhysicalTopology::Deserialize(const TiXmlElement* pElement) +bool CPhysicalTopology::Deserialize(const tinyxml2::XMLElement* pElement) { Reset(); @@ -37,10 +40,10 @@ bool CPhysicalTopology::Deserialize(const TiXmlElement* pElement) m_bProvidesInput = (XMLUtils::GetAttribute(pElement, LAYOUT_XML_ATTR_PROVIDES_INPUT) != "false"); - for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; + for (const auto* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement()) { - if (pChild->ValueStr() == LAYOUT_XML_ELM_PORT) + if (std::strcmp(pChild->Value(), LAYOUT_XML_ELM_PORT) == 0) { CPhysicalPort port; if (port.Deserialize(pChild)) @@ -48,7 +51,7 @@ bool CPhysicalTopology::Deserialize(const TiXmlElement* pElement) } else { - CLog::Log(LOGDEBUG, "Unknown physical topology tag: <{}>", pChild->ValueStr()); + CLog::Log(LOGDEBUG, "Unknown physical topology tag: <{}>", pChild->Value()); } } diff --git a/xbmc/games/controllers/input/PhysicalTopology.h b/xbmc/games/controllers/input/PhysicalTopology.h index c194de82f9fa8..2468312c16275 100644 --- a/xbmc/games/controllers/input/PhysicalTopology.h +++ b/xbmc/games/controllers/input/PhysicalTopology.h @@ -12,7 +12,10 @@ #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} namespace KODI { @@ -50,7 +53,7 @@ class CPhysicalTopology */ const std::vector& Ports() const { return m_ports; } - bool Deserialize(const TiXmlElement* pElement); + bool Deserialize(const tinyxml2::XMLElement* pElement); private: bool m_bProvidesInput = true; diff --git a/xbmc/games/ports/input/PhysicalPort.cpp b/xbmc/games/ports/input/PhysicalPort.cpp index 9c53c761999cd..d9f43f264a0ad 100644 --- a/xbmc/games/ports/input/PhysicalPort.cpp +++ b/xbmc/games/ports/input/PhysicalPort.cpp @@ -13,8 +13,11 @@ #include "utils/log.h" #include +#include #include +#include + using namespace KODI; using namespace GAME; @@ -34,7 +37,7 @@ bool CPhysicalPort::IsCompatible(const std::string& controllerId) const return std::find(m_accepts.begin(), m_accepts.end(), controllerId) != m_accepts.end(); } -bool CPhysicalPort::Deserialize(const TiXmlElement* pElement) +bool CPhysicalPort::Deserialize(const tinyxml2::XMLElement* pElement) { if (pElement == nullptr) return false; @@ -43,10 +46,10 @@ bool CPhysicalPort::Deserialize(const TiXmlElement* pElement) m_portId = XMLUtils::GetAttribute(pElement, LAYOUT_XML_ATTR_PORT_ID); - for (const TiXmlElement* pChild = pElement->FirstChildElement(); pChild != nullptr; + for (const auto* pChild = pElement->FirstChildElement(); pChild != nullptr; pChild = pChild->NextSiblingElement()) { - if (pChild->ValueStr() == LAYOUT_XML_ELM_ACCEPTS) + if (std::strcmp(pChild->Value(), LAYOUT_XML_ELM_ACCEPTS) == 0) { std::string controller = XMLUtils::GetAttribute(pChild, LAYOUT_XML_ATTR_CONTROLLER); @@ -58,7 +61,7 @@ bool CPhysicalPort::Deserialize(const TiXmlElement* pElement) } else { - CLog::Log(LOGDEBUG, "Unknown physical topology port tag: <{}>", pChild->ValueStr()); + CLog::Log(LOGDEBUG, "Unknown physical topology port tag: <{}>", pChild->Value()); } } diff --git a/xbmc/games/ports/input/PhysicalPort.h b/xbmc/games/ports/input/PhysicalPort.h index 83fe3a503c87e..51dba223c4e7a 100644 --- a/xbmc/games/ports/input/PhysicalPort.h +++ b/xbmc/games/ports/input/PhysicalPort.h @@ -11,7 +11,10 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} namespace KODI { @@ -54,7 +57,7 @@ class CPhysicalPort */ bool IsCompatible(const std::string& controllerId) const; - bool Deserialize(const TiXmlElement* pElement); + bool Deserialize(const tinyxml2::XMLElement* pElement); private: std::string m_portId; diff --git a/xbmc/games/ports/input/PortManager.cpp b/xbmc/games/ports/input/PortManager.cpp index a04177aac4599..b9c582a92e676 100644 --- a/xbmc/games/ports/input/PortManager.cpp +++ b/xbmc/games/ports/input/PortManager.cpp @@ -15,11 +15,12 @@ #include "games/ports/types/PortNode.h" #include "utils/FileUtils.h" #include "utils/URIUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" #include +#include using namespace KODI; using namespace GAME; @@ -72,17 +73,17 @@ void CPortManager::LoadXML() CLog::Log(LOGINFO, "Loading port layout: {}", CURL::GetRedacted(m_xmlPath)); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(m_xmlPath)) { - CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorDesc(), - xmlDoc.ErrorRow()); + CLog::Log(LOGDEBUG, "Unable to load file: {} at line {}", xmlDoc.ErrorStr(), + xmlDoc.ErrorLineNum()); return; } - const TiXmlElement* pRootElement = xmlDoc.RootElement(); + const auto* pRootElement = xmlDoc.RootElement(); if (pRootElement == nullptr || pRootElement->NoChildren() || - pRootElement->ValueStr() != XML_ROOT_PORTS) + std::strcmp(pRootElement->Value(), XML_ROOT_PORTS) != 0) { CLog::Log(LOGERROR, "Can't find root <{}> tag", XML_ROOT_PORTS); return; @@ -104,17 +105,21 @@ void CPortManager::SaveXMLAsync() m_saveFutures.end()); // Save async - std::future task = std::async(std::launch::async, [this, ports = std::move(ports)]() { - CXBMCTinyXML doc; - TiXmlElement node(XML_ROOT_PORTS); + std::future task = std::async(std::launch::async, + [this, ports = std::move(ports)]() + { + CXBMCTinyXML2 doc; + auto* node = doc.NewElement(XML_ROOT_PORTS); + if (node == nullptr) + return; - SerializePorts(node, ports); + SerializePorts(*node, ports); - doc.InsertEndChild(node); + doc.InsertEndChild(node); - std::lock_guard lock(m_saveMutex); - doc.SaveFile(m_xmlPath); - }); + std::lock_guard lock(m_saveMutex); + doc.SaveFile(m_xmlPath); + }); m_saveFutures.emplace_back(std::move(task)); } @@ -192,15 +197,14 @@ bool CPortManager::ConnectController(const std::string& portAddress, return false; } -void CPortManager::DeserializePorts(const TiXmlElement* pElement, PortVec& ports) +void CPortManager::DeserializePorts(const tinyxml2::XMLElement* pElement, PortVec& ports) { - for (const TiXmlElement* pPort = pElement->FirstChildElement(); pPort != nullptr; + for (const auto* pPort = pElement->FirstChildElement(); pPort != nullptr; pPort = pPort->NextSiblingElement()) { - if (pPort->ValueStr() != XML_ELM_PORT) + if (std::strcmp(pPort->Value(), XML_ELM_PORT) != 0) { - CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pElement->ValueStr(), - pPort->ValueStr()); + CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pElement->Value(), pPort->Value()); continue; } @@ -217,7 +221,7 @@ void CPortManager::DeserializePorts(const TiXmlElement* pElement, PortVec& ports } } -void CPortManager::DeserializePort(const TiXmlElement* pPort, CPortNode& port) +void CPortManager::DeserializePort(const tinyxml2::XMLElement* pPort, CPortNode& port) { // Connected bool connected = (XMLUtils::GetAttribute(pPort, XML_ATTR_PORT_CONNECTED) == "true"); @@ -231,15 +235,16 @@ void CPortManager::DeserializePort(const TiXmlElement* pPort, CPortNode& port) DeserializeControllers(pPort, port.GetCompatibleControllers()); } -void CPortManager::DeserializeControllers(const TiXmlElement* pPort, ControllerNodeVec& controllers) +void CPortManager::DeserializeControllers(const tinyxml2::XMLElement* pPort, + ControllerNodeVec& controllers) { - for (const TiXmlElement* pController = pPort->FirstChildElement(); pController != nullptr; + for (const auto* pController = pPort->FirstChildElement(); pController != nullptr; pController = pController->NextSiblingElement()) { - if (pController->ValueStr() != XML_ELM_CONTROLLER) + if (std::strcmp(pController->Value(), XML_ELM_CONTROLLER) != 0) { - CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pPort->ValueStr(), - pController->ValueStr()); + CLog::Log(LOGDEBUG, "Inside <{}> tag: Ignoring <{}> tag", pPort->Value(), + pController->Value()); continue; } @@ -258,32 +263,35 @@ void CPortManager::DeserializeControllers(const TiXmlElement* pPort, ControllerN } } -void CPortManager::DeserializeController(const TiXmlElement* pController, +void CPortManager::DeserializeController(const tinyxml2::XMLElement* pController, CControllerNode& controller) { // Child ports DeserializePorts(pController, controller.GetHub().GetPorts()); } -void CPortManager::SerializePorts(TiXmlElement& node, const PortVec& ports) +void CPortManager::SerializePorts(tinyxml2::XMLElement& node, const PortVec& ports) { + auto doc = node.GetDocument(); for (const CPortNode& port : ports) { - TiXmlElement portNode(XML_ELM_PORT); + auto portNode = doc->NewElement(XML_ELM_PORT); + if (portNode == nullptr) + continue; - SerializePort(portNode, port); + SerializePort(*portNode, port); node.InsertEndChild(portNode); } } -void CPortManager::SerializePort(TiXmlElement& portNode, const CPortNode& port) +void CPortManager::SerializePort(tinyxml2::XMLElement& portNode, const CPortNode& port) { // Port ID - portNode.SetAttribute(XML_ATTR_PORT_ID, port.GetPortID()); + portNode.SetAttribute(XML_ATTR_PORT_ID, port.GetPortID().c_str()); // Port address - portNode.SetAttribute(XML_ATTR_PORT_ADDRESS, port.GetAddress()); + portNode.SetAttribute(XML_ATTR_PORT_ADDRESS, port.GetAddress().c_str()); // Connected state portNode.SetAttribute(XML_ATTR_PORT_CONNECTED, port.IsConnected() ? "true" : "false"); @@ -292,14 +300,14 @@ void CPortManager::SerializePort(TiXmlElement& portNode, const CPortNode& port) if (port.GetActiveController().GetController()) { const std::string controllerId = port.GetActiveController().GetController()->ID(); - portNode.SetAttribute(XML_ATTR_PORT_CONTROLLER, controllerId); + portNode.SetAttribute(XML_ATTR_PORT_CONTROLLER, controllerId.c_str()); } // All compatible controllers SerializeControllers(portNode, port.GetCompatibleControllers()); } -void CPortManager::SerializeControllers(TiXmlElement& portNode, +void CPortManager::SerializeControllers(tinyxml2::XMLElement& portNode, const ControllerNodeVec& controllers) { for (const CControllerNode& controller : controllers) @@ -308,20 +316,22 @@ void CPortManager::SerializeControllers(TiXmlElement& portNode, if (!HasState(controller)) continue; - TiXmlElement controllerNode(XML_ELM_CONTROLLER); + auto controllerNode = portNode.GetDocument()->NewElement(XML_ELM_CONTROLLER); + if (controllerNode == nullptr) + continue; - SerializeController(controllerNode, controller); + SerializeController(*controllerNode, controller); portNode.InsertEndChild(controllerNode); } } -void CPortManager::SerializeController(TiXmlElement& controllerNode, +void CPortManager::SerializeController(tinyxml2::XMLElement& controllerNode, const CControllerNode& controller) { // Controller ID if (controller.GetController()) - controllerNode.SetAttribute(XML_ATTR_CONTROLLER_ID, controller.GetController()->ID()); + controllerNode.SetAttribute(XML_ATTR_CONTROLLER_ID, controller.GetController()->ID().c_str()); // Ports SerializePorts(controllerNode, controller.GetHub().GetPorts()); diff --git a/xbmc/games/ports/input/PortManager.h b/xbmc/games/ports/input/PortManager.h index a02ad35ce223e..85dc4255f3bda 100644 --- a/xbmc/games/ports/input/PortManager.h +++ b/xbmc/games/ports/input/PortManager.h @@ -14,7 +14,10 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} namespace KODI { @@ -41,15 +44,19 @@ class CPortManager const CControllerTree& GetControllerTree() const { return m_controllerTree; } private: - static void DeserializePorts(const TiXmlElement* pElement, PortVec& ports); - static void DeserializePort(const TiXmlElement* pElement, CPortNode& port); - static void DeserializeControllers(const TiXmlElement* pElement, ControllerNodeVec& controllers); - static void DeserializeController(const TiXmlElement* pElement, CControllerNode& controller); + static void DeserializePorts(const tinyxml2::XMLElement* pElement, PortVec& ports); + static void DeserializePort(const tinyxml2::XMLElement* pElement, CPortNode& port); + static void DeserializeControllers(const tinyxml2::XMLElement* pElement, + ControllerNodeVec& controllers); + static void DeserializeController(const tinyxml2::XMLElement* pElement, + CControllerNode& controller); - static void SerializePorts(TiXmlElement& node, const PortVec& ports); - static void SerializePort(TiXmlElement& portNode, const CPortNode& port); - static void SerializeControllers(TiXmlElement& portNode, const ControllerNodeVec& controllers); - static void SerializeController(TiXmlElement& controllerNode, const CControllerNode& controller); + static void SerializePorts(tinyxml2::XMLElement& node, const PortVec& ports); + static void SerializePort(tinyxml2::XMLElement& portNode, const CPortNode& port); + static void SerializeControllers(tinyxml2::XMLElement& portNode, + const ControllerNodeVec& controllers); + static void SerializeController(tinyxml2::XMLElement& controllerNode, + const CControllerNode& controller); static bool ConnectController(const std::string& portAddress, bool connected, diff --git a/xbmc/input/ButtonTranslator.cpp b/xbmc/input/ButtonTranslator.cpp index 193fc4488b1f1..15421cc128dc3 100644 --- a/xbmc/input/ButtonTranslator.cpp +++ b/xbmc/input/ButtonTranslator.cpp @@ -23,7 +23,7 @@ #include "input/actions/ActionTranslator.h" #include "input/mouse/MouseTranslator.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include @@ -125,17 +125,17 @@ bool CButtonTranslator::Load() bool CButtonTranslator::LoadKeymap(const std::string& keymapPath) { - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; CLog::Log(LOGINFO, "Loading {}", keymapPath); if (!xmlDoc.LoadFile(keymapPath)) { - CLog::Log(LOGERROR, "Error loading keymap: {}, Line {}\n{}", keymapPath, xmlDoc.ErrorRow(), - xmlDoc.ErrorDesc()); + CLog::Log(LOGERROR, "Error loading keymap: {}, Line {}\n{}", keymapPath, xmlDoc.ErrorLineNum(), + xmlDoc.ErrorStr()); return false; } - TiXmlElement* pRoot = xmlDoc.RootElement(); + auto* pRoot = xmlDoc.RootElement(); if (pRoot == nullptr) { CLog::Log(LOGERROR, "Error getting keymap root: {}", keymapPath); @@ -150,10 +150,10 @@ bool CButtonTranslator::LoadKeymap(const std::string& keymapPath) } // run through our window groups - TiXmlNode* pWindow = pRoot->FirstChild(); + auto* pWindow = pRoot->FirstChild(); while (pWindow != nullptr) { - if (pWindow->Type() == TiXmlNode::TINYXML_ELEMENT) + if (pWindow->ToElement()) { int windowID = WINDOW_INVALID; const char* szWindow = pWindow->Value(); @@ -306,20 +306,18 @@ void CButtonTranslator::MapAction(uint32_t buttonCode, const std::string& szActi } } -void CButtonTranslator::MapWindowActions(const TiXmlNode* pWindow, int windowID) +void CButtonTranslator::MapWindowActions(const tinyxml2::XMLNode* pWindow, int windowID) { if (pWindow == nullptr || windowID == WINDOW_INVALID) return; - const TiXmlNode* pDevice; - static const std::vector types = {"gamepad", "remote", "universalremote", "keyboard", "mouse", "appcommand"}; for (const auto& type : types) { - for (pDevice = pWindow->FirstChild(type); pDevice != nullptr; - pDevice = pDevice->NextSiblingElement(type)) + for (auto* pDevice = pWindow->FirstChildElement(type.c_str()); pDevice != nullptr; + pDevice = pDevice->NextSiblingElement(type.c_str())) { buttonMap map; std::map::iterator it = m_translatorMap.find(windowID); @@ -329,7 +327,7 @@ void CButtonTranslator::MapWindowActions(const TiXmlNode* pWindow, int windowID) m_translatorMap.erase(it); } - const TiXmlElement* pButton = pDevice->FirstChildElement(); + const auto* pButton = pDevice->FirstChildElement(); while (pButton != nullptr) { @@ -377,11 +375,11 @@ void CButtonTranslator::MapWindowActions(const TiXmlNode* pWindow, int windowID) IButtonMapper* mapper = it.second; // Map device actions - pDevice = pWindow->FirstChild(device); - while (pDevice != nullptr) + auto* pDevice = pWindow->FirstChildElement(device.c_str()); + while (pDevice) { mapper->MapActions(windowID, pDevice); - pDevice = pDevice->NextSibling(device); + pDevice = pDevice->NextSiblingElement(device.c_str()); } } } diff --git a/xbmc/input/ButtonTranslator.h b/xbmc/input/ButtonTranslator.h index 3f33c270480a8..0eb4ada2225f8 100644 --- a/xbmc/input/ButtonTranslator.h +++ b/xbmc/input/ButtonTranslator.h @@ -16,12 +16,16 @@ #include class CKey; -class TiXmlNode; class CCustomControllerTranslator; class CTouchTranslator; class IButtonMapper; class IWindowKeymap; +namespace tinyxml2 +{ +class XMLNode; +} + /// singleton class to map from buttons to actions /// Warning: _not_ threadsafe! class CButtonTranslator @@ -82,7 +86,7 @@ class CButtonTranslator unsigned int GetActionCode(int window, const CKey& key, std::string& strAction) const; - void MapWindowActions(const TiXmlNode* pWindow, int wWindowID); + void MapWindowActions(const tinyxml2::XMLNode* pWindow, int wWindowID); void MapAction(uint32_t buttonCode, const std::string& szAction, buttonMap& map); bool LoadKeymap(const std::string& keymapPath); diff --git a/xbmc/input/CustomControllerTranslator.cpp b/xbmc/input/CustomControllerTranslator.cpp index 9d25a6e75b28b..a9a7cf00411a2 100644 --- a/xbmc/input/CustomControllerTranslator.cpp +++ b/xbmc/input/CustomControllerTranslator.cpp @@ -11,15 +11,17 @@ #include "WindowTranslator.h" //! @todo #include "input/actions/ActionIDs.h" #include "input/actions/ActionTranslator.h" -#include "utils/XBMCTinyXML.h" #include "utils/log.h" -void CCustomControllerTranslator::MapActions(int windowID, const TiXmlNode* pCustomController) +#include + +void CCustomControllerTranslator::MapActions(int windowID, + const tinyxml2::XMLNode* pCustomController) { CustomControllerButtonMap buttonMap; std::string controllerName; - const TiXmlElement* pController = pCustomController->ToElement(); + const auto* pController = pCustomController->ToElement(); if (pController != nullptr) { // Transform loose name to new family, including altnames @@ -35,15 +37,15 @@ void CCustomControllerTranslator::MapActions(int windowID, const TiXmlNode* pCus } // Parse map - const TiXmlElement* pButton = pCustomController->FirstChildElement(); + const auto* pButton = pCustomController->FirstChildElement(); int id = 0; while (pButton != nullptr) { std::string action; if (!pButton->NoChildren()) - action = pButton->FirstChild()->ValueStr(); + action = pButton->FirstChild()->Value(); - if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS) && id >= 0) + if ((pButton->QueryIntAttribute("id", &id) == tinyxml2::XML_SUCCESS) && id >= 0) { buttonMap[id] = action; } diff --git a/xbmc/input/CustomControllerTranslator.h b/xbmc/input/CustomControllerTranslator.h index 5c06fe716627a..d6c1e2c285e4e 100644 --- a/xbmc/input/CustomControllerTranslator.h +++ b/xbmc/input/CustomControllerTranslator.h @@ -13,7 +13,10 @@ #include #include -class TiXmlNode; +namespace tinyxml2 +{ +class XMLNode; +} class CCustomControllerTranslator : public IButtonMapper { @@ -21,7 +24,7 @@ class CCustomControllerTranslator : public IButtonMapper CCustomControllerTranslator() = default; // implementation of IButtonMapper - void MapActions(int windowID, const TiXmlNode* pDevice) override; + void MapActions(int windowID, const tinyxml2::XMLNode* pDevice) override; void Clear() override; bool TranslateCustomControllerString(int windowId, diff --git a/xbmc/input/IButtonMapper.h b/xbmc/input/IButtonMapper.h index e535df1c90a93..aa604e69ee141 100644 --- a/xbmc/input/IButtonMapper.h +++ b/xbmc/input/IButtonMapper.h @@ -8,7 +8,10 @@ #pragma once -class TiXmlNode; +namespace tinyxml2 +{ +class XMLNode; +} /*! * \brief Interface for classes that can map buttons to Kodi actions @@ -18,7 +21,7 @@ class IButtonMapper public: virtual ~IButtonMapper() = default; - virtual void MapActions(int windowId, const TiXmlNode* pDevice) = 0; + virtual void MapActions(int windowId, const tinyxml2::XMLNode* pDevice) = 0; virtual void Clear() = 0; }; diff --git a/xbmc/input/IRTranslator.cpp b/xbmc/input/IRTranslator.cpp index 837014276af5b..e115352b7e2d5 100644 --- a/xbmc/input/IRTranslator.cpp +++ b/xbmc/input/IRTranslator.cpp @@ -15,9 +15,10 @@ #include "utils/FileUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" +#include #include #include @@ -56,17 +57,20 @@ bool CIRTranslator::LoadIRMap(const std::string& irMapPath) StringUtils::ToLower(remoteMapTag); // Load our xml file, and fill up our mapping tables - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; // Load the config file CLog::Log(LOGINFO, "Loading {}", irMapPath); if (!xmlDoc.LoadFile(irMapPath)) { - CLog::Log(LOGERROR, "{}, Line {}\n{}", irMapPath, xmlDoc.ErrorRow(), xmlDoc.ErrorDesc()); + CLog::Log(LOGERROR, "{}, Line {}\n{}", irMapPath, xmlDoc.ErrorLineNum(), xmlDoc.ErrorStr()); return false; } - TiXmlElement* pRoot = xmlDoc.RootElement(); + auto* pRoot = xmlDoc.RootElement(); + if (pRoot == nullptr) + return false; + std::string strValue = pRoot->Value(); if (strValue != remoteMapTag) { @@ -75,15 +79,15 @@ bool CIRTranslator::LoadIRMap(const std::string& irMapPath) } // Run through our window groups - TiXmlNode* pRemote = pRoot->FirstChild(); + auto* pRemote = pRoot->FirstChild(); while (pRemote != nullptr) { - if (pRemote->Type() == TiXmlNode::TINYXML_ELEMENT) + if (pRemote->ToElement()) { const char* szRemote = pRemote->Value(); if (szRemote != nullptr) { - TiXmlAttribute* pAttr = pRemote->ToElement()->FirstAttribute(); + auto* pAttr = pRemote->ToElement()->FirstAttribute(); if (pAttr != nullptr) MapRemote(pRemote, pAttr->Value()); } @@ -94,7 +98,7 @@ bool CIRTranslator::LoadIRMap(const std::string& irMapPath) return true; } -void CIRTranslator::MapRemote(TiXmlNode* pRemote, const std::string& szDevice) +void CIRTranslator::MapRemote(tinyxml2::XMLNode* pRemote, const std::string& szDevice) { CLog::Log(LOGINFO, "* Adding remote mapping for device '{}'", szDevice); @@ -106,15 +110,15 @@ void CIRTranslator::MapRemote(TiXmlNode* pRemote, const std::string& szDevice) const std::shared_ptr& buttons = m_irRemotesMap[szDevice]; - TiXmlElement* pButton = pRemote->FirstChildElement(); + auto* pButton = pRemote->FirstChildElement(); while (pButton != nullptr) { if (!pButton->NoChildren()) { - if (pButton->ValueStr() == "altname") - remoteNames.push_back(pButton->FirstChild()->ValueStr()); + if (std::strcmp(pButton->Value(), "altname") == 0) + remoteNames.push_back(pButton->FirstChild()->Value()); else - (*buttons)[pButton->FirstChild()->ValueStr()] = pButton->ValueStr(); + (*buttons)[pButton->FirstChild()->Value()] = pButton->Value(); } pButton = pButton->NextSiblingElement(); } diff --git a/xbmc/input/IRTranslator.h b/xbmc/input/IRTranslator.h index d32363e617e14..c767aba86f30f 100644 --- a/xbmc/input/IRTranslator.h +++ b/xbmc/input/IRTranslator.h @@ -12,7 +12,10 @@ #include #include -class TiXmlNode; +namespace tinyxml2 +{ +class XMLNode; +} class CIRTranslator { @@ -36,7 +39,7 @@ class CIRTranslator private: bool LoadIRMap(const std::string& irMapPath); - void MapRemote(TiXmlNode* pRemote, const std::string& szDevice); + void MapRemote(tinyxml2::XMLNode* pRemote, const std::string& szDevice); using IRButtonMap = std::map; diff --git a/xbmc/input/JoystickMapper.cpp b/xbmc/input/JoystickMapper.cpp index 233238dbd3de3..4e1337ab5f322 100644 --- a/xbmc/input/JoystickMapper.cpp +++ b/xbmc/input/JoystickMapper.cpp @@ -14,12 +14,13 @@ #include "input/joysticks/JoystickTranslator.h" #include "input/joysticks/JoystickUtils.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" #include #include #include +#include + using namespace KODI; #define JOYSTICK_XML_NODE_PROFILE "profile" @@ -27,7 +28,7 @@ using namespace KODI; #define JOYSTICK_XML_ATTR_HOLDTIME "holdtime" #define JOYSTICK_XML_ATTR_HOTKEY "hotkey" -void CJoystickMapper::MapActions(int windowID, const TiXmlNode* pDevice) +void CJoystickMapper::MapActions(int windowID, const tinyxml2::XMLNode* pDevice) { std::string controllerId; DeserializeJoystickNode(pDevice, controllerId); @@ -44,7 +45,7 @@ void CJoystickMapper::MapActions(int windowID, const TiXmlNode* pDevice) if (!keymap) keymap.reset(new CWindowKeymap(controllerId)); - const TiXmlElement* pButton = pDevice->FirstChildElement(); + const auto* pButton = pDevice->FirstChildElement(); while (pButton != nullptr) { std::string feature; @@ -92,14 +93,20 @@ std::vector> CJoystickMapper::GetJoystickKe return keymaps; } -void CJoystickMapper::DeserializeJoystickNode(const TiXmlNode* pDevice, std::string& controllerId) +void CJoystickMapper::DeserializeJoystickNode(const tinyxml2::XMLNode* pDevice, + std::string& controllerId) { - const TiXmlElement* deviceElem = pDevice->ToElement(); + const auto* deviceElem = pDevice->ToElement(); if (deviceElem != nullptr) - deviceElem->QueryValueAttribute(JOYSTICK_XML_NODE_PROFILE, &controllerId); + { + const char* controller; + if (deviceElem->QueryStringAttribute(JOYSTICK_XML_NODE_PROFILE, &controller) == + tinyxml2::XML_SUCCESS) + controllerId = controller; + } } -bool CJoystickMapper::DeserializeButton(const TiXmlElement* pButton, +bool CJoystickMapper::DeserializeButton(const tinyxml2::XMLElement* pButton, std::string& feature, JOYSTICK::ANALOG_STICK_DIRECTION& dir, unsigned int& holdtimeMs, @@ -111,7 +118,7 @@ bool CJoystickMapper::DeserializeButton(const TiXmlElement* pButton, { const char* szAction = nullptr; - const TiXmlNode* actionNode = pButton->FirstChild(); + const auto* actionNode = pButton->FirstChild(); if (actionNode != nullptr) szAction = actionNode->Value(); @@ -133,8 +140,9 @@ bool CJoystickMapper::DeserializeButton(const TiXmlElement* pButton, // Process holdtime parameter holdtimeMs = 0; - std::string strHoldTime; - if (pButton->QueryValueAttribute(JOYSTICK_XML_ATTR_HOLDTIME, &strHoldTime) == TIXML_SUCCESS) + const char* strHoldTime; + if (pButton->QueryStringAttribute(JOYSTICK_XML_ATTR_HOLDTIME, &strHoldTime) == + tinyxml2::XML_SUCCESS) { std::istringstream ss(strHoldTime); ss >> holdtimeMs; @@ -142,8 +150,9 @@ bool CJoystickMapper::DeserializeButton(const TiXmlElement* pButton, // Process hotkeys hotkeys.clear(); - std::string strHotkeys; - if (pButton->QueryValueAttribute(JOYSTICK_XML_ATTR_HOTKEY, &strHotkeys) == TIXML_SUCCESS) + const char* strHotkeys; + if (pButton->QueryStringAttribute(JOYSTICK_XML_ATTR_HOTKEY, &strHotkeys) == + tinyxml2::XML_SUCCESS) { std::vector vecHotkeys = StringUtils::Split(strHotkeys, ","); for (auto& hotkey : vecHotkeys) diff --git a/xbmc/input/JoystickMapper.h b/xbmc/input/JoystickMapper.h index ed5e61a9415ce..a0cb23b41de72 100644 --- a/xbmc/input/JoystickMapper.h +++ b/xbmc/input/JoystickMapper.h @@ -18,8 +18,12 @@ #include class IWindowKeymap; -class TiXmlElement; -class TiXmlNode; + +namespace tinyxml2 +{ +class XMLElement; +class XMLNode; +} // namespace tinyxml2 class CJoystickMapper : public IButtonMapper { @@ -28,14 +32,14 @@ class CJoystickMapper : public IButtonMapper ~CJoystickMapper() override = default; // implementation of IButtonMapper - void MapActions(int windowID, const TiXmlNode* pDevice) override; + void MapActions(int windowID, const tinyxml2::XMLNode* pDevice) override; void Clear() override; std::vector> GetJoystickKeymaps() const; private: - void DeserializeJoystickNode(const TiXmlNode* pDevice, std::string& controllerId); - bool DeserializeButton(const TiXmlElement* pButton, + void DeserializeJoystickNode(const tinyxml2::XMLNode* pDevice, std::string& controllerId); + bool DeserializeButton(const tinyxml2::XMLElement* pButton, std::string& feature, KODI::JOYSTICK::ANALOG_STICK_DIRECTION& dir, unsigned int& holdtimeMs, diff --git a/xbmc/input/KeyboardLayout.cpp b/xbmc/input/KeyboardLayout.cpp index 7e9c3e0a817e1..81d4933cb8187 100644 --- a/xbmc/input/KeyboardLayout.cpp +++ b/xbmc/input/KeyboardLayout.cpp @@ -12,18 +12,19 @@ #include "guilib/LocalizeStrings.h" #include "utils/CharsetConverter.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" #include "utils/log.h" #include #include +#include + CKeyboardLayout::~CKeyboardLayout() = default; -bool CKeyboardLayout::Load(const TiXmlElement* element) +bool CKeyboardLayout::Load(const tinyxml2::XMLElement* element) { const char* language = element->Attribute("language"); - if (language == NULL) + if (language == nullptr) { CLog::Log(LOGWARNING, "CKeyboardLayout: invalid \"language\" attribute"); return false; @@ -37,7 +38,7 @@ bool CKeyboardLayout::Load(const TiXmlElement* element) } const char* layout = element->Attribute("layout"); - if (layout == NULL) + if (layout == nullptr) { CLog::Log(LOGWARNING, "CKeyboardLayout: invalid \"layout\" attribute"); return false; @@ -50,13 +51,13 @@ bool CKeyboardLayout::Load(const TiXmlElement* element) return false; } - const TiXmlElement* keyboard = element->FirstChildElement("keyboard"); + const auto* keyboard = element->FirstChildElement("keyboard"); if (element->Attribute("codingtable")) m_codingtable = IInputCodingTablePtr( CInputCodingTableFactory::CreateCodingTable(element->Attribute("codingtable"))); else m_codingtable = NULL; - while (keyboard != NULL) + while (keyboard != nullptr) { // parse modifiers keys std::set modifierKeysSet; @@ -85,12 +86,12 @@ bool CKeyboardLayout::Load(const TiXmlElement* element) } // parse keyboard rows - const TiXmlNode* row = keyboard->FirstChild("row"); - while (row != NULL) + const auto* row = keyboard->FirstChildElement("row"); + while (row != nullptr) { if (!row->NoChildren()) { - std::string strRow = row->FirstChild()->ValueStr(); + std::string strRow = row->FirstChild()->Value(); std::vector chars = BreakCharacters(strRow); if (!modifierKeysSet.empty()) { @@ -101,7 +102,7 @@ bool CKeyboardLayout::Load(const TiXmlElement* element) m_keyboards[ModifierKeyNone].push_back(chars); } - row = row->NextSibling(); + row = row->NextSiblingElement(); } keyboard = keyboard->NextSiblingElement(); diff --git a/xbmc/input/KeyboardLayout.h b/xbmc/input/KeyboardLayout.h index 653b1146a6bf3..673476a39415a 100644 --- a/xbmc/input/KeyboardLayout.h +++ b/xbmc/input/KeyboardLayout.h @@ -14,7 +14,10 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} class CKeyboardLayout { @@ -23,7 +26,7 @@ class CKeyboardLayout virtual ~CKeyboardLayout(); IInputCodingTablePtr GetCodingTable() { return m_codingtable; } - bool Load(const TiXmlElement* element); + bool Load(const tinyxml2::XMLElement* element); std::string GetIdentifier() const; std::string GetName() const; diff --git a/xbmc/input/KeyboardLayoutManager.cpp b/xbmc/input/KeyboardLayoutManager.cpp index 66ed817cf18cb..f2d97c7ee9410 100644 --- a/xbmc/input/KeyboardLayoutManager.cpp +++ b/xbmc/input/KeyboardLayoutManager.cpp @@ -14,10 +14,11 @@ #include "filesystem/Directory.h" #include "settings/lib/Setting.h" #include "settings/lib/SettingDefinitions.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include +#include #define KEYBOARD_LAYOUTS_PATH "special://xbmc/system/keyboardlayouts" @@ -60,30 +61,30 @@ bool CKeyboardLayoutManager::Load(const std::string& path /* = "" */) if (layoutPath.empty()) continue; - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(layoutPath)) { CLog::Log(LOGWARNING, "CKeyboardLayoutManager: unable to open {}", layoutPath); continue; } - const TiXmlElement* rootElement = xmlDoc.RootElement(); - if (rootElement == NULL) + const auto* rootElement = xmlDoc.RootElement(); + if (rootElement == nullptr) { CLog::Log(LOGWARNING, "CKeyboardLayoutManager: missing or invalid XML root element in {}", layoutPath); continue; } - if (rootElement->ValueStr() != "keyboardlayouts") + if (std::strcmp(rootElement->Value(), "keyboardlayouts") != 0) { CLog::Log(LOGWARNING, "CKeyboardLayoutManager: unexpected XML root element \"{}\" in {}", rootElement->Value(), layoutPath); continue; } - const TiXmlElement* layoutElement = rootElement->FirstChildElement("layout"); - while (layoutElement != NULL) + const auto* layoutElement = rootElement->FirstChildElement("layout"); + while (layoutElement != nullptr) { CKeyboardLayout layout; if (!layout.Load(layoutElement)) diff --git a/xbmc/input/KeyboardTranslator.cpp b/xbmc/input/KeyboardTranslator.cpp index 1b50151c03522..cb1ea947b98ad 100644 --- a/xbmc/input/KeyboardTranslator.cpp +++ b/xbmc/input/KeyboardTranslator.cpp @@ -11,13 +11,14 @@ #include "Key.h" #include "XBMC_keytable.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" #include "utils/log.h" #include #include -uint32_t CKeyboardTranslator::TranslateButton(const TiXmlElement* pButton) +#include + +uint32_t CKeyboardTranslator::TranslateButton(const tinyxml2::XMLElement* pButton) { uint32_t button_id = 0; const char* szButton = pButton->Value(); @@ -28,10 +29,10 @@ uint32_t CKeyboardTranslator::TranslateButton(const TiXmlElement* pButton) const std::string strKey = szButton; if (strKey == "key") { - std::string strID; - if (pButton->QueryValueAttribute("id", &strID) == TIXML_SUCCESS) + const char* strID; + if (pButton->QueryStringAttribute("id", &strID) == tinyxml2::XML_SUCCESS) { - const char* str = strID.c_str(); + const char* str = strID; char* endptr; long int id = strtol(str, &endptr, 0); if (endptr - str != (int)strlen(str) || id <= 0 || id > 0x00FFFFFF) @@ -46,8 +47,8 @@ uint32_t CKeyboardTranslator::TranslateButton(const TiXmlElement* pButton) button_id = TranslateString(szButton); // Process the ctrl/shift/alt modifiers - std::string strMod; - if (pButton->QueryValueAttribute("mod", &strMod) == TIXML_SUCCESS) + const char* strMod; + if (pButton->QueryStringAttribute("mod", &strMod) == tinyxml2::XML_SUCCESS) { StringUtils::ToLower(strMod); diff --git a/xbmc/input/KeyboardTranslator.h b/xbmc/input/KeyboardTranslator.h index 7432037e88e0d..95f71dea7ae91 100644 --- a/xbmc/input/KeyboardTranslator.h +++ b/xbmc/input/KeyboardTranslator.h @@ -11,11 +11,14 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} class CKeyboardTranslator { public: - static uint32_t TranslateButton(const TiXmlElement* pButton); + static uint32_t TranslateButton(const tinyxml2::XMLElement* pButton); static uint32_t TranslateString(const std::string& szButton); }; diff --git a/xbmc/input/TouchTranslator.cpp b/xbmc/input/TouchTranslator.cpp index e1eb8432575e0..30b1b2acb26fc 100644 --- a/xbmc/input/TouchTranslator.cpp +++ b/xbmc/input/TouchTranslator.cpp @@ -12,11 +12,12 @@ #include "input/actions/ActionIDs.h" #include "input/actions/ActionTranslator.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" #include "utils/log.h" #include +#include + using ActionName = std::string; using TouchCommandID = unsigned int; @@ -33,7 +34,7 @@ static const std::map TouchCommands = { {"swipeup", ACTION_GESTURE_SWIPE_UP}, {"swipedown", ACTION_GESTURE_SWIPE_DOWN}}; -void CTouchTranslator::MapActions(int windowID, const TiXmlNode* pTouch) +void CTouchTranslator::MapActions(int windowID, const tinyxml2::XMLNode* pTouch) { if (pTouch == nullptr) return; @@ -50,11 +51,11 @@ void CTouchTranslator::MapActions(int windowID, const TiXmlNode* pTouch) m_touchMap.erase(it); } - const TiXmlElement* pTouchElem = pTouch->ToElement(); + const auto* pTouchElem = pTouch->ToElement(); if (pTouchElem == nullptr) return; - const TiXmlElement* pButton = pTouchElem->FirstChildElement(); + const auto* pButton = pTouchElem->FirstChildElement(); while (pButton != nullptr) { CTouchAction action; @@ -137,7 +138,7 @@ unsigned int CTouchTranslator::GetActionID(WindowID window, return touchIt->second.actionId; } -unsigned int CTouchTranslator::TranslateTouchCommand(const TiXmlElement* pButton, +unsigned int CTouchTranslator::TranslateTouchCommand(const tinyxml2::XMLElement* pButton, CTouchAction& action) { const char* szButton = pButton->Value(); diff --git a/xbmc/input/TouchTranslator.h b/xbmc/input/TouchTranslator.h index 989857edcd269..a28d735072195 100644 --- a/xbmc/input/TouchTranslator.h +++ b/xbmc/input/TouchTranslator.h @@ -13,7 +13,11 @@ #include #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +class XMLNode; +} // namespace tinyxml2 class CTouchTranslator : public IButtonMapper { @@ -21,7 +25,7 @@ class CTouchTranslator : public IButtonMapper CTouchTranslator() = default; // implementation of IButtonMapper - void MapActions(int windowID, const TiXmlNode* bDevice) override; + void MapActions(int windowID, const tinyxml2::XMLNode* bDevice) override; void Clear() override; bool TranslateTouchAction( @@ -50,7 +54,8 @@ class CTouchTranslator : public IButtonMapper TouchActionKey touchActionKey, std::string& actionString); - static unsigned int TranslateTouchCommand(const TiXmlElement* pButton, CTouchAction& action); + static unsigned int TranslateTouchCommand(const tinyxml2::XMLElement* pButton, + CTouchAction& action); static unsigned int GetTouchActionKey(unsigned int touchCommandId, int touchPointers); diff --git a/xbmc/input/mouse/MouseTranslator.cpp b/xbmc/input/mouse/MouseTranslator.cpp index b5cac7df6a007..00d0e58b62ca5 100644 --- a/xbmc/input/mouse/MouseTranslator.cpp +++ b/xbmc/input/mouse/MouseTranslator.cpp @@ -11,12 +11,13 @@ #include "MouseStat.h" #include "input/Key.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" #include "utils/log.h" #include #include +#include + using namespace KODI; using namespace MOUSE; @@ -44,13 +45,13 @@ static const std::map MouseKeys = {{"click", KEY_MOUSE_CLICK} } // anonymous namespace -uint32_t CMouseTranslator::TranslateCommand(const TiXmlElement* pButton) +uint32_t CMouseTranslator::TranslateCommand(const tinyxml2::XMLElement* pButton) { uint32_t buttonId = 0; if (pButton != nullptr) { - std::string szKey = pButton->ValueStr(); + std::string szKey = pButton->Value(); if (!szKey.empty()) { StringUtils::ToLower(szKey); @@ -66,7 +67,7 @@ uint32_t CMouseTranslator::TranslateCommand(const TiXmlElement* pButton) else { int id = 0; - if ((pButton->QueryIntAttribute("id", &id) == TIXML_SUCCESS)) + if ((pButton->QueryIntAttribute("id", &id) == tinyxml2::XML_SUCCESS)) { if (0 <= id && id < MOUSE_MAX_BUTTON) buttonId += id; diff --git a/xbmc/input/mouse/MouseTranslator.h b/xbmc/input/mouse/MouseTranslator.h index e3d4d4b906dcc..bb4300d70c466 100644 --- a/xbmc/input/mouse/MouseTranslator.h +++ b/xbmc/input/mouse/MouseTranslator.h @@ -12,7 +12,10 @@ #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} class CMouseTranslator { @@ -20,7 +23,7 @@ class CMouseTranslator /*! * \brief Translate a keymap element to a key ID */ - static uint32_t TranslateCommand(const TiXmlElement* pButton); + static uint32_t TranslateCommand(const tinyxml2::XMLElement* pButton); /*! * \brief Translate a mouse event ID to a mouse button index diff --git a/xbmc/peripherals/Peripherals.cpp b/xbmc/peripherals/Peripherals.cpp index 3dc5ca6a9ce21..9c83a0cdaaa75 100644 --- a/xbmc/peripherals/Peripherals.cpp +++ b/xbmc/peripherals/Peripherals.cpp @@ -57,7 +57,7 @@ #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" @@ -503,21 +503,22 @@ bool CPeripherals::LoadMappings() { std::unique_lock lock(m_critSectionMappings); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile("special://xbmc/system/peripherals.xml")) { - CLog::Log(LOGWARNING, "{} - peripherals.xml does not exist", __FUNCTION__); + CLog::LogF(LOGWARNING, "peripherals.xml does not exist"); return true; } - TiXmlElement* pRootElement = xmlDoc.RootElement(); - if (!pRootElement || StringUtils::CompareNoCase(pRootElement->Value(), "peripherals") != 0) + auto* pRootElement = xmlDoc.RootElement(); + if (pRootElement == nullptr || + StringUtils::CompareNoCase(pRootElement->Value(), "peripherals") != 0) { - CLog::Log(LOGERROR, "{} - peripherals.xml does not contain ", __FUNCTION__); + CLog::LogF(LOGERROR, "peripherals.xml does not contain "); return false; } - for (TiXmlElement* currentNode = pRootElement->FirstChildElement("peripheral"); currentNode; + for (auto* currentNode = pRootElement->FirstChildElement("peripheral"); currentNode != nullptr; currentNode = currentNode->NextSiblingElement("peripheral")) { PeripheralID id; @@ -536,8 +537,8 @@ bool CPeripherals::LoadMappings() std::vector idArray = StringUtils::Split(i, ":"); if (idArray.size() != 2) { - CLog::Log(LOGERROR, "{} - ignoring node \"{}\" with invalid vendor_product attribute", - __FUNCTION__, mapping.m_strDeviceName); + CLog::LogF(LOGERROR, "ignoring node \"{}\" with invalid vendor_product attribute", + mapping.m_strDeviceName); continue; } @@ -556,19 +557,19 @@ bool CPeripherals::LoadMappings() GetSettingsFromMappingsFile(currentNode, mapping.m_settings); m_mappings.push_back(mapping); - CLog::Log(LOGDEBUG, "{} - loaded node \"{}\"", __FUNCTION__, mapping.m_strDeviceName); + CLog::LogF(LOGDEBUG, "loaded node \"{}\"", mapping.m_strDeviceName); } return true; } void CPeripherals::GetSettingsFromMappingsFile( - TiXmlElement* xmlNode, std::map& settings) + tinyxml2::XMLElement* xmlNode, std::map& settings) { - TiXmlElement* currentNode = xmlNode->FirstChildElement("setting"); + auto* currentNode = xmlNode->FirstChildElement("setting"); int iMaxOrder = 0; - while (currentNode) + while (currentNode != nullptr) { SettingPtr setting; std::string strKey = XMLUtils::GetAttribute(currentNode, "key"); @@ -640,7 +641,7 @@ void CPeripherals::GetSettingsFromMappingsFile( /* set the order */ int iOrder = 0; - currentNode->Attribute("order", &iOrder); + currentNode->Attribute("order", std::to_string(iOrder).c_str()); /* if the order attribute is invalid or 0, then the setting will be added at the end */ if (iOrder < 0) iOrder = 0; diff --git a/xbmc/peripherals/Peripherals.h b/xbmc/peripherals/Peripherals.h index d9931d7699034..7d6bf88d324b7 100644 --- a/xbmc/peripherals/Peripherals.h +++ b/xbmc/peripherals/Peripherals.h @@ -25,10 +25,14 @@ class CFileItemList; class CInputManager; class CSetting; class CSettingsCategory; -class TiXmlElement; class CAction; class CKey; +namespace tinyxml2 +{ +class XMLElement; +} + namespace KODI { namespace GAME @@ -350,7 +354,7 @@ class CPeripherals : public ISettingCallback, bool LoadMappings(); bool GetMappingForDevice(const CPeripheralBus& bus, PeripheralScanResult& result) const; static void GetSettingsFromMappingsFile( - TiXmlElement* xmlNode, std::map& m_settings); + tinyxml2::XMLElement* xmlNode, std::map& m_settings); void OnDeviceChanged(); diff --git a/xbmc/peripherals/devices/Peripheral.cpp b/xbmc/peripherals/devices/Peripheral.cpp index 5e43bd399c409..f6161d55ae37b 100644 --- a/xbmc/peripherals/devices/Peripheral.cpp +++ b/xbmc/peripherals/devices/Peripheral.cpp @@ -24,7 +24,7 @@ #include "settings/lib/Setting.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" @@ -484,13 +484,19 @@ bool CPeripheral::SetSetting(const std::string& strKey, const std::string& strVa void CPeripheral::PersistSettings(bool bExiting /* = false */) { - CXBMCTinyXML doc; - TiXmlElement node("settings"); + CXBMCTinyXML2 doc; + auto* node = doc.NewElement("settings"); + if (node == nullptr) + return; + doc.InsertEndChild(node); for (const auto& itr : m_settings) { - TiXmlElement nodeSetting("setting"); - nodeSetting.SetAttribute("id", itr.first.c_str()); + auto* nodeSetting = doc.NewElement("setting"); + if (nodeSetting == nullptr) + continue; + + nodeSetting->SetAttribute("id", itr.first.c_str()); std::string strValue; switch (itr.second.m_setting->GetType()) { @@ -529,7 +535,7 @@ void CPeripheral::PersistSettings(bool bExiting /* = false */) default: break; } - nodeSetting.SetAttribute("value", strValue.c_str()); + nodeSetting->SetAttribute("value", strValue.c_str()); doc.RootElement()->InsertEndChild(nodeSetting); } @@ -545,11 +551,11 @@ void CPeripheral::PersistSettings(bool bExiting /* = false */) void CPeripheral::LoadPersistedSettings(void) { - CXBMCTinyXML doc; + CXBMCTinyXML2 doc; if (doc.LoadFile(m_strSettingsFile)) { - const TiXmlElement* setting = doc.RootElement()->FirstChildElement("setting"); - while (setting) + const auto* setting = doc.RootElement()->FirstChildElement("setting"); + while (setting != nullptr) { std::string strId = XMLUtils::GetAttribute(setting, "id"); std::string strValue = XMLUtils::GetAttribute(setting, "value"); diff --git a/xbmc/peripherals/devices/Peripheral.h b/xbmc/peripherals/devices/Peripheral.h index 2e62a3ff8effa..39bde4218a2a9 100644 --- a/xbmc/peripherals/devices/Peripheral.h +++ b/xbmc/peripherals/devices/Peripheral.h @@ -19,7 +19,6 @@ #include #include -class TiXmlDocument; class CDateTime; class CSetting; class IKeymap; From 3a6da6331a495e2e1cfe2902b075df70a8c093ac Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Aug 2023 19:08:50 +1000 Subject: [PATCH 055/811] [network] WakeOnAccess migrate to tinyxml2 usage --- xbmc/network/WakeOnAccess.cpp | 62 ++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp index e00fd14d87fd8..7e3679938304b 100644 --- a/xbmc/network/WakeOnAccess.cpp +++ b/xbmc/network/WakeOnAccess.cpp @@ -29,6 +29,7 @@ #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/XTimeUtils.h" #include "utils/log.h" @@ -799,19 +800,18 @@ void CWakeOnAccess::LoadFromXML() { bool enabled = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_POWERMANAGEMENT_WAKEONACCESS); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(GetSettingFile())) { if (enabled) - CLog::Log(LOGINFO, "{} - unable to load:{}", __FUNCTION__, GetSettingFile()); + CLog::LogF(LOGINFO, "unable to load:{}", GetSettingFile()); return; } - TiXmlElement* pRootElement = xmlDoc.RootElement(); - if (StringUtils::CompareNoCase(pRootElement->Value(), "onaccesswakeup")) + auto* rootElement = xmlDoc.RootElement(); + if (StringUtils::CompareNoCase(rootElement->Value(), "onaccesswakeup")) { - CLog::Log(LOGERROR, "{} - XML file {} doesn't contain ", __FUNCTION__, - GetSettingFile()); + CLog::LogF(LOGERROR, "XML file {} doesn't contain ", GetSettingFile()); return; } @@ -822,16 +822,16 @@ void CWakeOnAccess::LoadFromXML() SetEnabled(enabled); int tmp; - if (XMLUtils::GetInt(pRootElement, "netinittimeout", tmp, 0, 5 * 60)) + if (XMLUtils::GetInt(rootElement, "netinittimeout", tmp, 0, 5 * 60)) m_netinit_sec = tmp; CLog::Log(LOGINFO, " -Network init timeout : [{}] sec", m_netinit_sec); - if (XMLUtils::GetInt(pRootElement, "netsettletime", tmp, 0, 5 * 1000)) + if (XMLUtils::GetInt(rootElement, "netsettletime", tmp, 0, 5 * 1000)) m_netsettle_ms = tmp; CLog::Log(LOGINFO, " -Network settle time : [{}] ms", m_netsettle_ms); - const TiXmlNode* pWakeUp = pRootElement->FirstChildElement("wakeup"); - while (pWakeUp) + const auto* pWakeUp = rootElement->FirstChildElement("wakeup"); + while (pWakeUp != nullptr) { WakeUpEntry entry; @@ -843,9 +843,9 @@ void CWakeOnAccess::LoadFromXML() entry.mac = strtmp; if (entry.host.empty()) - CLog::Log(LOGERROR, "{} - Missing tag or it's empty", __FUNCTION__); + CLog::LogF(LOGERROR, "Missing tag or it's empty"); else if (entry.mac.empty()) - CLog::Log(LOGERROR, "{} - Missing tag or it's empty", __FUNCTION__); + CLog::Log(LOGERROR, "Missing tag or it's empty"); else { if (XMLUtils::GetInt(pWakeUp, "pingport", tmp, 0, USHRT_MAX)) @@ -885,8 +885,8 @@ void CWakeOnAccess::LoadFromXML() // load upnp server map m_UPnPServers.clear(); - const TiXmlNode* pUPnPNode = pRootElement->FirstChildElement("upnp_map"); - while (pUPnPNode) + const auto* pUPnPNode = rootElement->FirstChildElement("upnp_map"); + while (pUPnPNode != nullptr) { UPnPServer server; @@ -898,7 +898,7 @@ void CWakeOnAccess::LoadFromXML() server.m_name = server.m_uuid; if (server.m_uuid.empty() || server.m_mac.empty()) - CLog::Log(LOGERROR, "{} - Missing or empty entry", __FUNCTION__); + CLog::LogF(LOGERROR, "Missing or empty entry"); else { CLog::Log(LOGINFO, " Registering upnp_map entry [{} : {}] -> [{}]", server.m_name, @@ -913,19 +913,25 @@ void CWakeOnAccess::LoadFromXML() void CWakeOnAccess::SaveToXML() { - CXBMCTinyXML xmlDoc; - TiXmlElement xmlRootElement("onaccesswakeup"); - TiXmlNode *pRoot = xmlDoc.InsertEndChild(xmlRootElement); - if (!pRoot) return; + CXBMCTinyXML2 xmlDoc; + auto xmlRootElement = xmlDoc.NewElement("onaccesswakeup"); + if (xmlRootElement == nullptr) + return; + + auto* root = xmlDoc.InsertEndChild(xmlRootElement); + if (root == nullptr) + return; - XMLUtils::SetInt(pRoot, "netinittimeout", m_netinit_sec); - XMLUtils::SetInt(pRoot, "netsettletime", m_netsettle_ms); + XMLUtils::SetInt(root, "netinittimeout", m_netinit_sec); + XMLUtils::SetInt(root, "netsettletime", m_netsettle_ms); for (const auto& i : m_entries) { - TiXmlElement xmlSetting("wakeup"); - TiXmlNode* pWakeUpNode = pRoot->InsertEndChild(xmlSetting); - if (pWakeUpNode) + auto* xmlSetting = xmlDoc.NewElement("wakeup"); + if (xmlSetting == nullptr) + continue; + auto* pWakeUpNode = root->InsertEndChild(xmlSetting); + if (pWakeUpNode != nullptr) { XMLUtils::SetString(pWakeUpNode, "host", i.host); XMLUtils::SetString(pWakeUpNode, "mac", i.mac); @@ -940,9 +946,11 @@ void CWakeOnAccess::SaveToXML() for (const auto& upnp : m_UPnPServers) { - TiXmlElement xmlSetting("upnp_map"); - TiXmlNode* pUPnPNode = pRoot->InsertEndChild(xmlSetting); - if (pUPnPNode) + auto* xmlSetting = xmlDoc.NewElement("upnp_map"); + if (xmlSetting == nullptr) + continue; + auto* pUPnPNode = root->InsertEndChild(xmlSetting); + if (pUPnPNode != nullptr) { XMLUtils::SetString(pUPnPNode, "name", upnp.m_name); XMLUtils::SetString(pUPnPNode, "uuid", upnp.m_uuid); From 2a0ea47219dfaddcd26e13d5f29825af7e9366d7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Aug 2023 19:11:48 +1000 Subject: [PATCH 056/811] [platform/linux] Libinputsettings migrate to tinyxml2 --- xbmc/platform/linux/input/LibInputSettings.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xbmc/platform/linux/input/LibInputSettings.cpp b/xbmc/platform/linux/input/LibInputSettings.cpp index 25a916e5e73e5..ff7558bed678f 100644 --- a/xbmc/platform/linux/input/LibInputSettings.cpp +++ b/xbmc/platform/linux/input/LibInputSettings.cpp @@ -15,7 +15,7 @@ #include "settings/lib/Setting.h" #include "settings/lib/SettingDefinitions.h" #include "settings/lib/SettingsManager.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include @@ -64,28 +64,28 @@ CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) : /* load the keyboard layouts from xkeyboard-config */ std::string xkbFile("/usr/share/X11/xkb/rules/base.xml"); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(xkbFile)) { CLog::Log(LOGWARNING, "CLibInputSettings: unable to open: {}", xkbFile); return; } - const TiXmlElement* rootElement = xmlDoc.RootElement(); + const auto* rootElement = xmlDoc.RootElement(); if (!rootElement) { CLog::Log(LOGWARNING, "CLibInputSettings: missing or invalid XML root element in: {}", xkbFile); return; } - if (rootElement->ValueStr() != "xkbConfigRegistry") + if (strcmp(rootElement->Value(), "xkbConfigRegistry") != 0) { CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML root element {} in: {}", rootElement->Value(), xkbFile); return; } - const TiXmlElement* layoutListElement = rootElement->FirstChildElement("layoutList"); + const auto* layoutListElement = rootElement->FirstChildElement("layoutList"); if (!layoutListElement) { CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", @@ -93,10 +93,10 @@ CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) : return; } - const TiXmlElement* layoutElement = layoutListElement->FirstChildElement("layout"); + const auto* layoutElement = layoutListElement->FirstChildElement("layout"); while (layoutElement) { - const TiXmlElement* configElement = layoutElement->FirstChildElement("configItem"); + const auto* configElement = layoutElement->FirstChildElement("configItem"); if (!configElement) { CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", @@ -104,7 +104,7 @@ CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) : return; } - const TiXmlElement* nameElement = configElement->FirstChildElement("name"); + const auto* nameElement = configElement->FirstChildElement("name"); if (!nameElement) { CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", @@ -112,7 +112,7 @@ CLibInputSettings::CLibInputSettings(CLibInputHandler *handler) : return; } - const TiXmlElement* descriptionElement = configElement->FirstChildElement("description"); + const auto* descriptionElement = configElement->FirstChildElement("description"); if (!descriptionElement) { CLog::Log(LOGWARNING, "CLibInputSettings: unexpected XML child element {} in: {}", From e553a9a48df0266f01c009f858fca6969038fb4d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Aug 2023 19:34:57 +1000 Subject: [PATCH 057/811] [passwordManager] Migrate to tinyxml2 --- xbmc/PasswordManager.cpp | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/xbmc/PasswordManager.cpp b/xbmc/PasswordManager.cpp index fdbb902afcc16..d379b84c1334a 100644 --- a/xbmc/PasswordManager.cpp +++ b/xbmc/PasswordManager.cpp @@ -15,9 +15,11 @@ #include "settings/SettingsComponent.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" +#include #include CPasswordManager &CPasswordManager::GetInstance() @@ -145,19 +147,22 @@ void CPasswordManager::Load() std::string passwordsFile = profileManager->GetUserDataItem("passwords.xml"); if (CFileUtils::Exists(passwordsFile)) { - CXBMCTinyXML doc; + CXBMCTinyXML2 doc; if (!doc.LoadFile(passwordsFile)) { - CLog::Log(LOGERROR, "{} - Unable to load: {}, Line {}\n{}", __FUNCTION__, passwordsFile, - doc.ErrorRow(), doc.ErrorDesc()); + CLog::LogF(LOGERROR, "Unable to load: {}, Line {}\n{}", passwordsFile, doc.ErrorLineNum(), + doc.ErrorStr()); return; } - const TiXmlElement *root = doc.RootElement(); - if (root->ValueStr() != "passwords") + const auto* root = doc.RootElement(); + if (root == nullptr) + return; + + if (std::strcmp(root->Value(), "passwords") != 0) return; // read in our passwords - const TiXmlElement *path = root->FirstChildElement("path"); - while (path) + const auto* path = root->FirstChildElement("path"); + while (path != nullptr) { std::string from, to; if (XMLUtils::GetPath(path, "from", from) && XMLUtils::GetPath(path, "to", to)) @@ -177,16 +182,25 @@ void CPasswordManager::Save() const if (m_permanentCache.empty()) return; - CXBMCTinyXML doc; - TiXmlElement rootElement("passwords"); - TiXmlNode *root = doc.InsertEndChild(rootElement); - if (!root) + CXBMCTinyXML2 doc; + auto* rootElement = doc.NewElement("passwords"); + if (rootElement == nullptr) + return; + + auto* root = doc.InsertEndChild(rootElement); + if (root == nullptr) return; for (std::map::const_iterator i = m_permanentCache.begin(); i != m_permanentCache.end(); ++i) { - TiXmlElement pathElement("path"); - TiXmlNode *path = root->InsertEndChild(pathElement); + auto* pathElement = doc.NewElement("path"); + if (pathElement == nullptr) + continue; + + auto* path = root->InsertEndChild(pathElement); + if (path == nullptr) + continue; + XMLUtils::SetPath(path, "from", i->first); XMLUtils::SetPath(path, "to", i->second); } From 1d9a2228b608f2a9ad6283082252f0f2792dc3de Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Aug 2023 20:02:05 +1000 Subject: [PATCH 058/811] [media] decoderfilter migrate to tinyxml2 --- .../decoderfilter/DecoderFilterManager.cpp | 49 ++++++++++--------- .../decoderfilter/DecoderFilterManager.h | 15 +++--- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/xbmc/media/decoderfilter/DecoderFilterManager.cpp b/xbmc/media/decoderfilter/DecoderFilterManager.cpp index 2ace1e1054ba6..5c059538f5054 100644 --- a/xbmc/media/decoderfilter/DecoderFilterManager.cpp +++ b/xbmc/media/decoderfilter/DecoderFilterManager.cpp @@ -18,6 +18,7 @@ #include "Util.h" #include "cores/VideoPlayer/DVDStreamInfo.h" #include "filesystem/File.h" +#include "utils/XBMCTinyXML2.h" #include "utils/XMLUtils.h" #include "utils/log.h" @@ -31,7 +32,6 @@ static const char* TAG_STILLS = "stills-allowed"; static const char* TAG_DVD = "dvd-allowed"; static const char* TAG_MINHEIGHT = "min-height"; static const char* HWDFFileName = "special://masterprofile/decoderfilter.xml"; -static const char* CLASSNAME = "CDecoderFilter"; CDecoderFilter::CDecoderFilter(const std::string& name, uint32_t flags, int minHeight) : m_name(name) @@ -60,7 +60,7 @@ bool CDecoderFilter::isValid(const CDVDStreamInfo& streamInfo) const return true; } -bool CDecoderFilter::Load(const TiXmlNode *node) +bool CDecoderFilter::Load(const tinyxml2::XMLNode* node) { bool flagBool = false; @@ -84,7 +84,7 @@ bool CDecoderFilter::Load(const TiXmlNode *node) return true; } -bool CDecoderFilter::Save(TiXmlNode *node) const +bool CDecoderFilter::Save(tinyxml2::XMLNode* node) const { // Now write each of the pieces of information we need... XMLUtils::SetString(node, TAG_NAME, m_name); @@ -122,30 +122,29 @@ bool CDecoderFilterManager::Load() if (!XFILE::CFile::Exists(fileName)) return true; - CLog::Log(LOGINFO, "{}: loading filters from {}", CLASSNAME, fileName); + CLog::LogF(LOGINFO, "loading filters from {}", fileName); - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; if (!xmlDoc.LoadFile(fileName)) { - CLog::Log(LOGERROR, "{}: error loading: line {}, {}", CLASSNAME, xmlDoc.ErrorRow(), - xmlDoc.ErrorDesc()); + CLog::LogF(LOGERROR, "error loading: line {}, {}", xmlDoc.ErrorLineNum(), xmlDoc.ErrorStr()); return false; } - const TiXmlElement *pRootElement = xmlDoc.RootElement(); - if (!pRootElement || !StringUtils::EqualsNoCase(pRootElement->ValueStr(), TAG_ROOT)) + const auto* rootElement = xmlDoc.RootElement(); + if (rootElement == nullptr || !StringUtils::EqualsNoCase(rootElement->Value(), TAG_ROOT)) { - CLog::Log(LOGERROR, "{}: invalid root element ({})", CLASSNAME, pRootElement->ValueStr()); + CLog::LogF(LOGERROR, "invalid root element ({})", rootElement ? rootElement->Value() : ""); return false; } - const TiXmlElement *pFilter = pRootElement->FirstChildElement(TAG_FILTER); - while (pFilter) + const auto* tagFilter = rootElement->FirstChildElement(TAG_FILTER); + while (tagFilter != nullptr) { CDecoderFilter filter(""); - if (filter.Load(pFilter)) + if (filter.Load(tagFilter)) m_filters.insert(filter); - pFilter = pFilter->NextSiblingElement(TAG_FILTER); + tagFilter = tagFilter->NextSiblingElement(TAG_FILTER); } return true; } @@ -156,21 +155,27 @@ bool CDecoderFilterManager::Save() const if (!m_dirty || m_filters.empty()) return true; - CXBMCTinyXML doc; - TiXmlElement xmlRootElement(TAG_ROOT); - TiXmlNode *pRoot = doc.InsertEndChild(xmlRootElement); - if (pRoot == NULL) + CXBMCTinyXML2 doc; + auto* xmlRootElement = doc.NewElement(TAG_ROOT); + if (xmlRootElement == nullptr) + return false; + + auto* root = doc.InsertEndChild(xmlRootElement); + if (root == nullptr) return false; for (const CDecoderFilter& filter : m_filters) { // Write the resolution tag - TiXmlElement filterElem(TAG_FILTER); - TiXmlNode *pNode = pRoot->InsertEndChild(filterElem); - if (pNode == NULL) + auto* filterElem = doc.NewElement(TAG_FILTER); + if (filterElem == nullptr) + return false; + + auto* node = root->InsertEndChild(filterElem); + if (node == nullptr) return false; - filter.Save(pNode); + filter.Save(node); } std::string fileName = CUtil::TranslateSpecialSource(HWDFFileName); return doc.SaveFile(fileName); diff --git a/xbmc/media/decoderfilter/DecoderFilterManager.h b/xbmc/media/decoderfilter/DecoderFilterManager.h index 146538b00a7e8..78298c071444c 100644 --- a/xbmc/media/decoderfilter/DecoderFilterManager.h +++ b/xbmc/media/decoderfilter/DecoderFilterManager.h @@ -19,9 +19,13 @@ #include #include -class TiXmlNode; class CDVDStreamInfo; +namespace tinyxml2 +{ +class XMLNode; +} + /** * @class CDecoderFilter * @@ -75,21 +79,20 @@ class CDecoderFilter virtual bool isValid(const CDVDStreamInfo& streamInfo) const; /** - * \fn CDecoderFilter::Load(const TiXmlNode *settings); + * \fn CDecoderFilter::Load(const XMLNode* settings); * \brief load all members from XML node * \param node filter node from where to get the values * \return true if operation was successful, false on error */ - virtual bool Load(const TiXmlNode *node); + virtual bool Load(const tinyxml2::XMLNode* node); /** - * \fn CDecoderFilter::Save(TiXmlNode *settings); + * \fn CDecoderFilter::Save(XMLNode* settings); * \brief store all members in XML node * \param node a ready to use filter setting node * \return true if operation was successful, false on error */ - virtual bool Save(TiXmlNode *node) const; - + virtual bool Save(tinyxml2::XMLNode* node) const; private: std::string m_name; From 2996381970d427513916d92ff9a3f7038feb4e8d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Aug 2023 21:02:58 +1000 Subject: [PATCH 059/811] [DVDInputStream] BlurayStateSerializer and DVDStateSerializer migrate to tinyxml2 --- .../DVDInputStreams/BlurayStateSerializer.cpp | 46 +++++++++------- .../DVDInputStreams/BlurayStateSerializer.h | 2 - .../DVDInputStreams/DVDStateSerializer.cpp | 52 ++++++++++--------- .../DVDInputStreams/DVDStateSerializer.h | 7 ++- 4 files changed, 60 insertions(+), 47 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp index 0a810c77d2d6f..a38b3569e9e16 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.cpp @@ -9,7 +9,7 @@ #include "BlurayStateSerializer.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include @@ -24,39 +24,47 @@ constexpr int BLURAYSTATESERIALIZER_VERSION = 1; bool CBlurayStateSerializer::BlurayStateToXML(std::string& xmlstate, const BlurayState& state) { - CXBMCTinyXML xmlDoc{"libbluraystate"}; + CXBMCTinyXML2 xmlDoc; - TiXmlElement eRoot{"libbluraystate"}; - eRoot.SetAttribute("version", BLURAYSTATESERIALIZER_VERSION); + auto* eRoot = xmlDoc.NewElement("libbluraystate"); + if (eRoot == nullptr) + return false; + eRoot->SetAttribute("version", BLURAYSTATESERIALIZER_VERSION); + + auto* xmlElement = xmlDoc.NewElement("playlistId"); + if (xmlElement == nullptr) + return false; + + auto* xmlElementValue = xmlDoc.NewText(std::to_string(state.playlistId).c_str()); + if (xmlElementValue == nullptr) + return false; - TiXmlElement xmlElement{"playlistId"}; - TiXmlText xmlElementValue = std::to_string(state.playlistId); - xmlElement.InsertEndChild(xmlElementValue); - eRoot.InsertEndChild(xmlElement); + xmlElement->InsertEndChild(xmlElementValue); + eRoot->InsertEndChild(xmlElement); xmlDoc.InsertEndChild(eRoot); - std::stringstream stream; - stream << xmlDoc; - xmlstate = stream.str(); + tinyxml2::XMLPrinter printer; + xmlDoc.Accept(&printer); + xmlstate = printer.CStr(); return true; } bool CBlurayStateSerializer::XMLToBlurayState(BlurayState& state, const std::string& xmlstate) { - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; - xmlDoc.Parse(xmlstate); - if (xmlDoc.Error()) + if (!xmlDoc.Parse(xmlstate)) return false; - TiXmlHandle hRoot(xmlDoc.RootElement()); - if (!hRoot.Element() || !StringUtils::EqualsNoCase(hRoot.Element()->Value(), "libbluraystate")) + tinyxml2::XMLHandle hRoot(xmlDoc.RootElement()); + if (!hRoot.ToElement() || + !StringUtils::EqualsNoCase(hRoot.ToElement()->Value(), "libbluraystate")) { CLog::LogF(LOGERROR, "Failed to deserialize bluray state - failed to detect root element."); return false; } - auto version = hRoot.Element()->Attribute("version"); + auto version = hRoot.ToElement()->Attribute("version"); if (!version || !StringUtils::EqualsNoCase(version, std::to_string(BLURAYSTATESERIALIZER_VERSION))) { @@ -64,8 +72,8 @@ bool CBlurayStateSerializer::XMLToBlurayState(BlurayState& state, const std::str return false; } - const TiXmlElement* childElement = hRoot.Element()->FirstChildElement(); - while (childElement) + const auto* childElement = hRoot.ToElement()->FirstChildElement(); + while (childElement != nullptr) { const std::string property = childElement->Value(); if (property == "playlistId") diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h index ca808c0460c62..83284acc108f4 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/BlurayStateSerializer.h @@ -10,8 +10,6 @@ #include -class TiXmlElement; - /*! \brief Pod structure which represents the current Bluray state */ struct BlurayState { diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp index c09779bae2049..4924417e68c61 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.cpp @@ -9,7 +9,7 @@ #include "DVDStateSerializer.h" #include "utils/StringUtils.h" -#include "utils/XBMCTinyXML.h" +#include "utils/XBMCTinyXML2.h" #include "utils/log.h" #include @@ -24,50 +24,53 @@ constexpr int DVDSTATESERIALIZER_VERSION = 2; bool CDVDStateSerializer::DVDStateToXML(std::string& xmlstate, const DVDState& state) { - CXBMCTinyXML xmlDoc{"navstate"}; + CXBMCTinyXML2 xmlDoc; - TiXmlElement eRoot{"navstate"}; - eRoot.SetAttribute("version", DVDSTATESERIALIZER_VERSION); + auto* eRoot = xmlDoc.NewElement("navstate"); + if (eRoot == nullptr) + return false; + + eRoot->SetAttribute("version", DVDSTATESERIALIZER_VERSION); - AddXMLElement(eRoot, "title", std::to_string(state.title)); - AddXMLElement(eRoot, "pgn", std::to_string(state.pgn)); - AddXMLElement(eRoot, "pgcn", std::to_string(state.pgcn)); - AddXMLElement(eRoot, "current_angle", std::to_string(state.current_angle)); - AddXMLElement(eRoot, "audio_num", std::to_string(state.audio_num)); - AddXMLElement(eRoot, "subp_num", std::to_string(state.subp_num)); - AddXMLElement(eRoot, "sub_enabled", state.sub_enabled ? "true" : "false"); + AddXMLElement(*eRoot, "title", std::to_string(state.title).c_str()); + AddXMLElement(*eRoot, "pgn", std::to_string(state.pgn).c_str()); + AddXMLElement(*eRoot, "pgcn", std::to_string(state.pgcn).c_str()); + AddXMLElement(*eRoot, "current_angle", std::to_string(state.current_angle).c_str()); + AddXMLElement(*eRoot, "audio_num", std::to_string(state.audio_num).c_str()); + AddXMLElement(*eRoot, "subp_num", std::to_string(state.subp_num).c_str()); + AddXMLElement(*eRoot, "sub_enabled", state.sub_enabled ? "true" : "false"); xmlDoc.InsertEndChild(eRoot); - std::stringstream stream; - stream << xmlDoc; - xmlstate = stream.str(); + tinyxml2::XMLPrinter printer; + xmlDoc.Accept(&printer); + xmlstate = printer.CStr(); return true; } bool CDVDStateSerializer::XMLToDVDState(DVDState& state, const std::string& xmlstate) { - CXBMCTinyXML xmlDoc; + CXBMCTinyXML2 xmlDoc; xmlDoc.Parse(xmlstate); if (xmlDoc.Error()) return false; - TiXmlHandle hRoot(xmlDoc.RootElement()); - if (!hRoot.Element() || !StringUtils::EqualsNoCase(hRoot.Element()->Value(), "navstate")) + tinyxml2::XMLHandle hRoot(xmlDoc.RootElement()); + if (!hRoot.ToElement() || !StringUtils::EqualsNoCase(hRoot.ToElement()->Value(), "navstate")) { CLog::LogF(LOGERROR, "Failed to deserialize dvd state - failed to detect root element."); return false; } - auto version = hRoot.Element()->Attribute("version"); + auto version = hRoot.ToElement()->Attribute("version"); if (!version || !StringUtils::EqualsNoCase(version, std::to_string(DVDSTATESERIALIZER_VERSION))) { CLog::LogF(LOGERROR, "Failed to deserialize dvd state - incompatible serializer version."); return false; } - const TiXmlElement* childElement = hRoot.Element()->FirstChildElement(); - while (childElement) + const auto* childElement = hRoot.ToElement()->FirstChildElement(); + while (childElement != nullptr) { const std::string property = childElement->Value(); if (property == "title") @@ -116,12 +119,13 @@ bool CDVDStateSerializer::XMLToDVDState(DVDState& state, const std::string& xmls return true; } -void CDVDStateSerializer::AddXMLElement(TiXmlElement& root, +void CDVDStateSerializer::AddXMLElement(tinyxml2::XMLElement& root, const std::string& name, const std::string& value) { - TiXmlElement xmlElement{name}; - TiXmlText xmlElementValue = value; - xmlElement.InsertEndChild(xmlElementValue); + auto* xmlDoc = root.GetDocument(); + auto* xmlElement = xmlDoc->NewElement(name.c_str()); + auto* xmlElementValue = xmlDoc->NewText(value.c_str()); + xmlElement->InsertEndChild(xmlElementValue); root.InsertEndChild(xmlElement); } diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h index debba101dc35b..af9f1168d54c7 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDStateSerializer.h @@ -10,7 +10,10 @@ #include -class TiXmlElement; +namespace tinyxml2 +{ +class XMLElement; +} /*! \brief Pod structure which represents the current dvd state with respect to dvdnav properties */ struct DVDState @@ -63,5 +66,5 @@ class CDVDStateSerializer * \param name the new element name * \param value the new element value */ - void AddXMLElement(TiXmlElement& root, const std::string& name, const std::string& value); + void AddXMLElement(tinyxml2::XMLElement& root, const std::string& name, const std::string& value); }; From c1ab65fd90f0493d32babbc4ab448e589ceb745c Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 21 Aug 2023 13:31:35 +0200 Subject: [PATCH 060/811] [Windows] fix WASAPI devices enumeration: "WASAPI:default" is repeated multiple times --- xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp index 1dcfbc0f788c9..c728155c56a2f 100644 --- a/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp +++ b/xbmc/cores/AudioEngine/Sinks/windows/AESinkFactoryWin32.cpp @@ -68,7 +68,7 @@ std::vector CAESinkFactoryWin::GetRendererDetails() for (UINT i = 0; i < uiCount; i++) { - RendererDetail details; + RendererDetail details{}; ComPtr pDevice = nullptr; ComPtr pProperty = nullptr; PROPVARIANT varName; From b6d42c632b659680f9ae53ec60f9bcee73683d60 Mon Sep 17 00:00:00 2001 From: Rechi Date: Mon, 21 Aug 2023 18:48:45 -0700 Subject: [PATCH 061/811] [clang-tidy] performance-faster-string-find --- xbmc/games/addons/input/GameClientTopology.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/games/addons/input/GameClientTopology.cpp b/xbmc/games/addons/input/GameClientTopology.cpp index 900c3ec072ade..5e1969d0884bc 100644 --- a/xbmc/games/addons/input/GameClientTopology.cpp +++ b/xbmc/games/addons/input/GameClientTopology.cpp @@ -18,7 +18,7 @@ using namespace KODI; using namespace GAME; -#define CONTROLLER_ADDRESS_SEPARATOR "/" +#define CONTROLLER_ADDRESS_SEPARATOR '/' CGameClientTopology::CGameClientTopology(GameClientPortVec ports, int playerLimit) : m_ports(std::move(ports)), m_playerLimit(playerLimit), m_controllers(GetControllerTree(m_ports)) From 98c076eae6c4847df7408432c599a73d2c0eaa95 Mon Sep 17 00:00:00 2001 From: Rechi Date: Mon, 21 Aug 2023 18:48:45 -0700 Subject: [PATCH 062/811] [clang-tidy] performance-unnecessary-copy-initialization --- xbmc/games/agents/windows/GUIAgentList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/games/agents/windows/GUIAgentList.cpp b/xbmc/games/agents/windows/GUIAgentList.cpp index eaac3b1e77ef2..2b4546ea24a6e 100644 --- a/xbmc/games/agents/windows/GUIAgentList.cpp +++ b/xbmc/games/agents/windows/GUIAgentList.cpp @@ -211,7 +211,7 @@ void CGUIAgentList::AddItem(const CGameAgent& agent) // Create the list item from agent properties const std::string label = agent.GetPeripheralName(); const ControllerPtr controller = agent.GetController(); - const std::string path = agent.GetPeripheralLocation(); + const std::string& path = agent.GetPeripheralLocation(); CFileItemPtr item = std::make_shared(label); item->SetPath(path); From 3e04cb964d8d135b476fc70250db606c99f82948 Mon Sep 17 00:00:00 2001 From: Rechi Date: Mon, 21 Aug 2023 18:48:45 -0700 Subject: [PATCH 063/811] [clang-tidy] performance-unnecessary-value-param --- xbmc/games/ports/guicontrols/GUIActivePortList.cpp | 5 +++-- xbmc/games/ports/guicontrols/GUIActivePortList.h | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/xbmc/games/ports/guicontrols/GUIActivePortList.cpp b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp index 36769fdb44d1d..851ca86266fb3 100644 --- a/xbmc/games/ports/guicontrols/GUIActivePortList.cpp +++ b/xbmc/games/ports/guicontrols/GUIActivePortList.cpp @@ -167,14 +167,15 @@ void CGUIActivePortList::AddItems(const PortVec& ports) // Add controller ControllerPtr controller = port.GetActiveController().GetController(); const std::string& controllerAddress = port.GetActiveController().GetControllerAddress(); - AddItem(std::move(controller), controllerAddress); + AddItem(controller, controllerAddress); // Add child ports AddItems(port.GetActiveController().GetHub().GetPorts()); } } -void CGUIActivePortList::AddItem(ControllerPtr controller, const std::string& controllerAddress) +void CGUIActivePortList::AddItem(const ControllerPtr& controller, + const std::string& controllerAddress) { // Check if a controller is connected that provides input if (controller && controller->Topology().ProvidesInput()) diff --git a/xbmc/games/ports/guicontrols/GUIActivePortList.h b/xbmc/games/ports/guicontrols/GUIActivePortList.h index ed83f975fa341..a55e879d2de41 100644 --- a/xbmc/games/ports/guicontrols/GUIActivePortList.h +++ b/xbmc/games/ports/guicontrols/GUIActivePortList.h @@ -50,7 +50,7 @@ class CGUIActivePortList : public IActivePortList, public Observer void DeinitializeGUI(); void AddInputDisabled(); void AddItems(const PortVec& ports); - void AddItem(ControllerPtr controller, const std::string& controllerAddress); + void AddItem(const ControllerPtr& controller, const std::string& controllerAddress); void AddPadding(); void CleanupItems(); From ea2e7a23c88580b8fc6e2d6635a51655fd026d62 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 19 Aug 2023 19:31:58 +0100 Subject: [PATCH 064/811] [windowing][dx] Implement ForceFullScreen to get rid of CApp ifdef --- xbmc/application/Application.cpp | 11 ++--------- xbmc/windowing/WinSystem.h | 6 ++++++ xbmc/windowing/win10/WinSystemWin10.cpp | 5 +++++ xbmc/windowing/win10/WinSystemWin10.h | 1 + xbmc/windowing/windows/WinSystemWin32.cpp | 5 +++++ xbmc/windowing/windows/WinSystemWin32.h | 1 + 6 files changed, 20 insertions(+), 9 deletions(-) diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index 5bcf125e9d773..54c2c8ad7fa41 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -288,18 +288,11 @@ void CApplication::HandlePortEvents() settings->SetInt(CSettings::SETTING_WINDOW_HEIGHT, newEvent.resize.h); settings->Save(); } -#ifdef TARGET_WINDOWS else { - // this may occurs when OS tries to resize application window - //CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true); - //auto& gfxContext = CServiceBroker::GetWinSystem()->GetGfxContext(); - //gfxContext.SetVideoResolution(gfxContext.GetVideoResolution(), true); - // try to resize window back to it's full screen size - auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP); - CServiceBroker::GetWinSystem()->ResizeWindow(res_info.iScreenWidth, res_info.iScreenHeight, 0, 0); + const auto& res_info = CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP); + CServiceBroker::GetWinSystem()->ForceFullScreen(res_info); } -#endif } break; case XBMC_VIDEOMOVE: diff --git a/xbmc/windowing/WinSystem.h b/xbmc/windowing/WinSystem.h index f1a356a51ad85..43058c402aa57 100644 --- a/xbmc/windowing/WinSystem.h +++ b/xbmc/windowing/WinSystem.h @@ -84,6 +84,12 @@ class CWinSystemBase //the number of presentation buffers virtual int NoOfBuffers(); + /*! + * \brief Forces the window to fullscreen provided the window resolution + * \param resInfo - the resolution info + */ + virtual void ForceFullScreen(const RESOLUTION_INFO& resInfo) {} + /*! * \brief Get average display latency * diff --git a/xbmc/windowing/win10/WinSystemWin10.cpp b/xbmc/windowing/win10/WinSystemWin10.cpp index 7ce291df752fc..15eda060badc8 100644 --- a/xbmc/windowing/win10/WinSystemWin10.cpp +++ b/xbmc/windowing/win10/WinSystemWin10.cpp @@ -155,6 +155,11 @@ void CWinSystemWin10::FinishWindowResize(int newWidth, int newHeight) ApplicationView::PreferredLaunchWindowingMode(ApplicationViewWindowingMode::PreferredLaunchViewSize); } +void CWinSystemWin10::ForceFullScreen(const RESOLUTION_INFO& resInfo) +{ + ResizeWindow(resInfo.iScreenWidth, resInfo.iScreenHeight, 0, 0); +} + void CWinSystemWin10::AdjustWindow() { CLog::Log(LOGDEBUG, __FUNCTION__": adjusting window if required."); diff --git a/xbmc/windowing/win10/WinSystemWin10.h b/xbmc/windowing/win10/WinSystemWin10.h index 63f80a6918a78..78ac8c4f4f1f8 100644 --- a/xbmc/windowing/win10/WinSystemWin10.h +++ b/xbmc/windowing/win10/WinSystemWin10.h @@ -71,6 +71,7 @@ class CWinSystemWin10 : public CWinSystemBase bool DestroyWindowSystem() override; bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; void FinishWindowResize(int newWidth, int newHeight) override; + void ForceFullScreen(const RESOLUTION_INFO& resInfo) override; void UpdateResolutions() override; void NotifyAppFocusChange(bool bGaining) override; void ShowOSMouse(bool show) override; diff --git a/xbmc/windowing/windows/WinSystemWin32.cpp b/xbmc/windowing/windows/WinSystemWin32.cpp index 7c91721668c01..74bacc0f0a887 100644 --- a/xbmc/windowing/windows/WinSystemWin32.cpp +++ b/xbmc/windowing/windows/WinSystemWin32.cpp @@ -365,6 +365,11 @@ void CWinSystemWin32::FinishWindowResize(int newWidth, int newHeight) m_nHeight = newHeight; } +void CWinSystemWin32::ForceFullScreen(const RESOLUTION_INFO& resInfo) +{ + ResizeWindow(resInfo.iScreenWidth, resInfo.iScreenHeight, 0, 0); +} + void CWinSystemWin32::AdjustWindow(bool forceResize) { CLog::LogF(LOGDEBUG, "adjusting window if required."); diff --git a/xbmc/windowing/windows/WinSystemWin32.h b/xbmc/windowing/windows/WinSystemWin32.h index 95d7f40b0c5fd..31dc54c3a16e1 100644 --- a/xbmc/windowing/windows/WinSystemWin32.h +++ b/xbmc/windowing/windows/WinSystemWin32.h @@ -84,6 +84,7 @@ class CWinSystemWin32 : public CWinSystemBase bool DestroyWindowSystem() override; bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; void FinishWindowResize(int newWidth, int newHeight) override; + void ForceFullScreen(const RESOLUTION_INFO& resInfo) override; void UpdateResolutions() override; bool CenterWindow() override; virtual void NotifyAppFocusChange(bool bGaining) override; From a6a2f76f794e69373535f66024970751fda3b931 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Wed, 23 Aug 2023 00:48:50 +0200 Subject: [PATCH 065/811] [Wayland] Skip unknown key events --- xbmc/windowing/wayland/InputProcessorKeyboard.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/xbmc/windowing/wayland/InputProcessorKeyboard.cpp b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp index 37b02404ccc1e..6ec9ccb9f395c 100644 --- a/xbmc/windowing/wayland/InputProcessorKeyboard.cpp +++ b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp @@ -122,6 +122,14 @@ void CInputProcessorKeyboard::ConvertAndSendKey(std::uint32_t scancode, bool pre scancode = 0; } + if (xbmcKey == XBMCKey::XBMCK_UNKNOWN && utf32 == 0) + { + // Such an event would carry no useful information in it and thus can be safely dropped here + // This prevents starting an unnecessary timer for key presses + CLog::Log(LOGDEBUG, "Unknown key event ignored (scancode: {})", scancode); + return; + } + XBMC_Event event{SendKey(scancode, xbmcKey, static_cast (utf32), pressed)}; if (pressed && m_keymap->ShouldKeycodeRepeat(xkbCode) && m_keyRepeatInterval > 0) From d36056441e721c33ac8093718e113f0351f891dc Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Thu, 24 Aug 2023 08:19:32 +0200 Subject: [PATCH 066/811] [InputStream] Add support to audio codec profiles --- .../addon-instance/inputstream/stream_codec.h | 59 +++++++++++++++++++ .../DVDInputStreams/InputStreamAddon.cpp | 51 ++++++++++++++++ .../DVDInputStreams/InputStreamAddon.h | 1 + 3 files changed, 111 insertions(+) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_codec.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_codec.h index 91789e73d623f..34f41ba8ea337 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_codec.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_codec.h @@ -141,6 +141,65 @@ extern "C" /// [chroma subsampling](https://en.wikipedia.org/wiki/Chroma_subsampling): 4:2:0, 4:2:2, 4:4:4 /// see [AV1 specification](https://aomedia.org/av1/specification/) AV1CodecProfileProfessional, + + // Audio codec profiles + + /// @brief **AAC** Main Profile + AACCodecProfileMAIN, + + /// @brief **AAC** Low Profile + AACCodecProfileLOW, + + /// @brief **AAC** SSR Profile + AACCodecProfileSSR, + + /// @brief **AAC** LTP Profile + AACCodecProfileLTP, + + /// @brief **High Efficiency AAC** Profile + AACCodecProfileHE, + + /// @brief **High Efficiency AAC** v2 Profile + AACCodecProfileHEV2, + + /// @brief **AAC** LD Profile + AACCodecProfileLD, + + /// @brief **AAC** ELD Profile + AACCodecProfileELD, + + /// @brief **MPEG2 AAC** LOW Profile + MPEG2AACCodecProfileLOW, + + /// @brief **MPEG2 High Efficiency AAC** Profile + MPEG2AACCodecProfileHE, + + /// @brief **DTS** Profile + DTSCodecProfile, + + /// @brief **DTS** Extended Surround Profile + DTSCodecProfileES, + + /// @brief **DTS** 96/24 (96kHz / 24bit) Profile + DTSCodecProfile9624, + + /// @brief **DTS** High-Definition High Resolution Audio Profile + DTSCodecProfileHDHRA, + + /// @brief **DTS** High-Definition Master Audio Profile + DTSCodecProfileHDMA, + + /// @brief **DTS** High-Definition Express Profile + DTSCodecProfileHDExpress, + + /// @brief **DTS** High-Definition Master Audio + DTS:X Profile + DTSCodecProfileHDMAX, + + /// @brief **DTS** High-Definition Master Audio + DTS:X IMAX Profile + DTSCodecProfileHDMAIMAX, + + /// @brief **Dolby Digital** Atmos Profile + DDPlusCodecProfileAtmos, }; ///@} //------------------------------------------------------------------------------ diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp index f5bfe16264b2a..0901ba8944bb1 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp @@ -415,6 +415,7 @@ KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle, audioStream->iBlockAlign = stream->m_BlockAlign; audioStream->iBitRate = stream->m_BitRate; audioStream->iBitsPerSample = stream->m_BitsPerSample; + audioStream->profile = ConvertAudioCodecProfile(stream->m_codecProfile); demuxStream = audioStream; } else if (stream->m_streamType == INPUTSTREAM_TYPE_VIDEO) @@ -721,6 +722,56 @@ int CInputStreamAddon::ConvertVideoCodecProfile(STREAMCODEC_PROFILE profile) } } +int CInputStreamAddon::ConvertAudioCodecProfile(STREAMCODEC_PROFILE profile) +{ + switch (profile) + { + case AACCodecProfileMAIN: + return FF_PROFILE_AAC_MAIN; + case AACCodecProfileLOW: + return FF_PROFILE_AAC_LOW; + case AACCodecProfileSSR: + return FF_PROFILE_AAC_SSR; + case AACCodecProfileLTP: + return FF_PROFILE_AAC_LTP; + case AACCodecProfileHE: + return FF_PROFILE_AAC_HE; + case AACCodecProfileHEV2: + return FF_PROFILE_AAC_HE_V2; + case AACCodecProfileLD: + return FF_PROFILE_AAC_LD; + case AACCodecProfileELD: + return FF_PROFILE_AAC_ELD; + case MPEG2AACCodecProfileLOW: + return FF_PROFILE_MPEG2_AAC_LOW; + case MPEG2AACCodecProfileHE: + return FF_PROFILE_MPEG2_AAC_HE; + case DTSCodecProfile: + return FF_PROFILE_DTS; + case DTSCodecProfileES: + return FF_PROFILE_DTS_ES; + case DTSCodecProfile9624: + return FF_PROFILE_DTS_96_24; + case DTSCodecProfileHDHRA: + return FF_PROFILE_DTS_HD_HRA; + case DTSCodecProfileHDMA: + return FF_PROFILE_DTS_HD_MA; + case DTSCodecProfileHDExpress: + return FF_PROFILE_DTS_EXPRESS; + case DTSCodecProfileHDMAX: + //! @todo: with ffmpeg >= 6.1 set the appropriate profile + return FF_PROFILE_UNKNOWN; // FF_PROFILE_DTS_HD_MA_X + case DTSCodecProfileHDMAIMAX: + //! @todo: with ffmpeg >= 6.1 set the appropriate profile + return FF_PROFILE_UNKNOWN; // FF_PROFILE_DTS_HD_MA_X_IMAX + case DDPlusCodecProfileAtmos: + //! @todo: with ffmpeg >= 6.1 set the appropriate profile + return FF_PROFILE_UNKNOWN; // FF_PROFILE_EAC3_DDP_ATMOS + default: + return FF_PROFILE_UNKNOWN; + } +} + void CInputStreamAddon::DetectScreenResolution() { unsigned int videoWidth{1280}; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h index 8471404564357..54546e8305850 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.h @@ -105,6 +105,7 @@ class CInputStreamAddon protected: static int ConvertVideoCodecProfile(STREAMCODEC_PROFILE profile); + static int ConvertAudioCodecProfile(STREAMCODEC_PROFILE profile); IVideoPlayer* m_player; From 8c8d1ce64a7188f72aff74bbcefca47d817282a6 Mon Sep 17 00:00:00 2001 From: Glenn Guy Date: Mon, 14 Aug 2023 14:20:09 +0200 Subject: [PATCH 067/811] [Android] support ClearKey crypto type --- .../kodi/c-api/addon-instance/inputstream/stream_crypto.h | 5 ++++- xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp | 3 +++ .../DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp | 2 ++ .../cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp | 7 +++---- xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h | 1 + 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_crypto.h b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_crypto.h index 3d4f62131de38..d15b48ec6ad51 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_crypto.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/c-api/addon-instance/inputstream/stream_crypto.h @@ -44,7 +44,10 @@ extern "C" /// @brief **3** - To use Wiseplay for processing STREAM_CRYPTO_KEY_SYSTEM_WISEPLAY, - /// @brief **4** - The maximum value to use in a list. + /// @brief **4** - To use ClearKey for processing + STREAM_CRYPTO_KEY_SYSTEM_CLEARKEY, + + /// @brief **5** - The maximum value to use in a list. STREAM_CRYPTO_KEY_SYSTEM_COUNT }; ///@} diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp index 71f530c484a7e..a0f4d3edaff7c 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/AddonVideoCodec.cpp @@ -221,6 +221,9 @@ bool CAddonVideoCodec::CopyToInitData(VIDEOCODEC_INITDATA &initData, CDVDStreamI case CRYPTO_SESSION_SYSTEM_WISEPLAY: initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_WISEPLAY; break; + case CRYPTO_SESSION_SYSTEM_CLEARKEY: + initData.cryptoSession.keySystem = STREAM_CRYPTO_KEY_SYSTEM_CLEARKEY; + break; default: return false; } diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 0470092aac1fc..b966607aef76c 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -658,6 +658,8 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio uuid = CJNIUUID(0x9A04F07998404286LL, 0xAB92E65BE0885F95LL); else if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_WISEPLAY) uuid = CJNIUUID(0X3D5E6D359B9A41E8LL, 0XB843DD3C6E72C42CLL); + else if (m_hints.cryptoSession->keySystem == CRYPTO_SESSION_SYSTEM_CLEARKEY) + uuid = CJNIUUID(0XE2719D58A985B3C9LL, 0X781AB030AF78D30ELL); else { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Open Unsupported crypto-keysystem {}", diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp index 0901ba8944bb1..be8f46d8ab81b 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp @@ -539,10 +539,9 @@ KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle, stream->m_cryptoSession.keySystem < STREAM_CRYPTO_KEY_SYSTEM_COUNT) { static const CryptoSessionSystem map[] = { - CRYPTO_SESSION_SYSTEM_NONE, - CRYPTO_SESSION_SYSTEM_WIDEVINE, - CRYPTO_SESSION_SYSTEM_PLAYREADY, - CRYPTO_SESSION_SYSTEM_WISEPLAY, + CRYPTO_SESSION_SYSTEM_NONE, CRYPTO_SESSION_SYSTEM_WIDEVINE, + CRYPTO_SESSION_SYSTEM_PLAYREADY, CRYPTO_SESSION_SYSTEM_WISEPLAY, + CRYPTO_SESSION_SYSTEM_CLEARKEY, }; demuxStream->cryptoSession = std::shared_ptr( new DemuxCryptoSession(map[stream->m_cryptoSession.keySystem], diff --git a/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h b/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h index 4d2a859b3d6ed..b5a7dffee48a7 100644 --- a/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h +++ b/xbmc/cores/VideoPlayer/Interface/DemuxCrypto.h @@ -20,6 +20,7 @@ enum CryptoSessionSystem : uint8_t CRYPTO_SESSION_SYSTEM_WIDEVINE, CRYPTO_SESSION_SYSTEM_PLAYREADY, CRYPTO_SESSION_SYSTEM_WISEPLAY, + CRYPTO_SESSION_SYSTEM_CLEARKEY, }; struct DemuxCryptoSession From 83fa9d312703ed4d114b8135ccdccca7a7f61812 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Mon, 14 Aug 2023 09:22:12 +0200 Subject: [PATCH 068/811] [addons] increase global API to 3.3.0, videocodec to 2.1.0 --- xbmc/addons/kodi-dev-kit/include/kodi/versions.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h index 7d180cde11f8f..1f887a4ebe02c 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/versions.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/versions.h @@ -108,8 +108,8 @@ #define ADDON_INSTANCE_VERSION_IMAGEDECODER_DEPENDS "c-api/addon-instance/imagedecoder.h" \ "addon-instance/ImageDecoder.h" -#define ADDON_INSTANCE_VERSION_INPUTSTREAM "3.2.0" -#define ADDON_INSTANCE_VERSION_INPUTSTREAM_MIN "3.2.0" +#define ADDON_INSTANCE_VERSION_INPUTSTREAM "3.3.0" +#define ADDON_INSTANCE_VERSION_INPUTSTREAM_MIN "3.3.0" #define ADDON_INSTANCE_VERSION_INPUTSTREAM_XML_ID "kodi.binary.instance.inputstream" #define ADDON_INSTANCE_VERSION_INPUTSTREAM_DEPENDS "c-api/addon-instance/inputstream.h" \ "c-api/addon-instance/inputstream/demux_packet.h" \ @@ -174,8 +174,8 @@ #define ADDON_INSTANCE_VERSION_VISUALIZATION_DEPENDS "addon-instance/Visualization.h" \ "c-api/addon-instance/visualization.h" -#define ADDON_INSTANCE_VERSION_VIDEOCODEC "2.0.4" -#define ADDON_INSTANCE_VERSION_VIDEOCODEC_MIN "2.0.4" +#define ADDON_INSTANCE_VERSION_VIDEOCODEC "2.1.0" +#define ADDON_INSTANCE_VERSION_VIDEOCODEC_MIN "2.1.0" #define ADDON_INSTANCE_VERSION_VIDEOCODEC_XML_ID "kodi.binary.instance.videocodec" #define ADDON_INSTANCE_VERSION_VIDEOCODEC_DEPENDS "c-api/addon-instance/video_codec.h" \ "c-api/addon-instance/inputstream/stream_codec.h" \ From cd613c4d64278cd182c2ed661c6b105703e770de Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Fri, 18 Aug 2023 09:50:25 +0200 Subject: [PATCH 069/811] [DVDDemuxClient] Allow update profile on stream reset When InputStream interface do OpenStream addon callback the addon may change codec but also profiles, this is the case with InputStream Adaptive since it initially set stream info by using the manifest data, but the demuxer that act after may change these info (so reset stream properties) --- xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp index e6986467fa8b8..21ded53a1b9fd 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp @@ -604,9 +604,9 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::mapcodec)) + if (!currentStream || forceInit) { toStream->profile = stream->profile; toStream->level = stream->level; From 8722ebf773fd99b4191c97b07bff908e4506ce01 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 17 Aug 2023 18:34:13 +0100 Subject: [PATCH 070/811] [macOS][nativewindowing] Event location only makes sense for mouse events --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 180 +++++++++++++++++-------- 1 file changed, 122 insertions(+), 58 deletions(-) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index 54433149a0be6..626e9f5cee55f 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -18,6 +18,7 @@ #include "windowing/osx/WinSystemOSX.h" #include +#include #include #import @@ -195,103 +196,147 @@ - (void)ProcessInputEvent:(NSEvent*)nsEvent - (NSEvent*)InputEventHandler:(NSEvent*)nsEvent { bool passEvent = true; - // The incoming mouse position. - NSPoint location = nsEvent.locationInWindow; - if (!nsEvent.window || location.x < 0 || location.y < 0) - return nsEvent; - - location = [nsEvent.window convertPointToBacking:location]; - - // cocoa world is upside down ... - auto winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); - if (!winSystem) - return nsEvent; - - NSRect frame = [nsEvent.window convertRectToBacking:winSystem->GetWindowDimensions()]; - location.y = frame.size.height - location.y; - XBMC_Event newEvent = {}; switch (nsEvent.type) { // handle mouse events and transform them into the xbmc event world case NSEventTypeLeftMouseUp: - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_LEFT; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONUP; + newEvent.button.button = XBMC_BUTTON_LEFT; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeLeftMouseDown: - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_LEFT; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.button = XBMC_BUTTON_LEFT; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeRightMouseUp: - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_RIGHT; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONUP; + newEvent.button.button = XBMC_BUTTON_RIGHT; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeRightMouseDown: - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_RIGHT; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.button = XBMC_BUTTON_RIGHT; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeOtherMouseUp: - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_MIDDLE; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONUP; + newEvent.button.button = XBMC_BUTTON_MIDDLE; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeOtherMouseDown: - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_MIDDLE; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.button = XBMC_BUTTON_MIDDLE; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeMouseMoved: case NSEventTypeLeftMouseDragged: case NSEventTypeRightMouseDragged: case NSEventTypeOtherMouseDragged: - newEvent.type = XBMC_MOUSEMOTION; - newEvent.motion.x = location.x; - newEvent.motion.y = location.y; - [self MessagePush:&newEvent]; + { + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEMOTION; + newEvent.motion.x = locationCoordinates.x; + newEvent.motion.y = locationCoordinates.y; + [self MessagePush:&newEvent]; + } break; + } case NSEventTypeScrollWheel: + { // very strange, real scrolls have non-zero deltaY followed by same number of events // with a zero deltaY. This reverses our scroll which is WTF? anoying. Trap them out here. if (nsEvent.deltaY != 0.0) { - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.x = location.x; - newEvent.button.y = location.y; - newEvent.button.button = - nsEvent.scrollingDeltaY > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN; - [self MessagePush:&newEvent]; - - newEvent.type = XBMC_MOUSEBUTTONUP; - [self MessagePush:&newEvent]; + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + newEvent.type = XBMC_MOUSEBUTTONDOWN; + newEvent.button.x = locationCoordinates.x; + newEvent.button.y = locationCoordinates.y; + newEvent.button.button = + nsEvent.scrollingDeltaY > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN; + [self MessagePush:&newEvent]; + + newEvent.type = XBMC_MOUSEBUTTONUP; + [self MessagePush:&newEvent]; + } } break; + } // handle keyboard events and transform them into the xbmc event world case NSEventTypeKeyUp: + { newEvent = [self keyPressEvent:nsEvent]; newEvent.type = XBMC_KEYUP; [self MessagePush:&newEvent]; passEvent = false; break; + } case NSEventTypeKeyDown: + { newEvent = [self keyPressEvent:nsEvent]; newEvent.type = XBMC_KEYDOWN; @@ -300,6 +345,7 @@ - (NSEvent*)InputEventHandler:(NSEvent*)nsEvent passEvent = false; break; + } default: return nsEvent; } @@ -344,4 +390,22 @@ - (XBMC_Event)keyPressEvent:(NSEvent*)nsEvent return newEvent; } +- (std::optional)TranslateMouseLocation:(NSEvent*)nsEvent +{ + NSPoint location = nsEvent.locationInWindow; + if (!nsEvent.window || location.x < 0 || location.y < 0) + { + return std::nullopt; + } + location = [nsEvent.window convertPointToBacking:location]; + //! TODO: get rid of the call to winsystem via the service broker + auto winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); + if (!winSystem) + return std::nullopt; + NSRect frame = [nsEvent.window convertRectToBacking:winSystem->GetWindowDimensions()]; + // cocoa world is upside down ... + location.y = frame.size.height - location.y; + return location; +} + @end From b3c2af2dfa5d21a2194946a00659a372023f7259 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 17 Aug 2023 18:47:14 +0100 Subject: [PATCH 071/811] [macos][nativewindowing] Remove dependency on winsystemOSX --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index 626e9f5cee55f..34ad2705667f5 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -15,7 +15,6 @@ #include "input/mouse/MouseStat.h" #include "messaging/ApplicationMessenger.h" #include "utils/log.h" -#include "windowing/osx/WinSystemOSX.h" #include #include @@ -398,11 +397,7 @@ - (XBMC_Event)keyPressEvent:(NSEvent*)nsEvent return std::nullopt; } location = [nsEvent.window convertPointToBacking:location]; - //! TODO: get rid of the call to winsystem via the service broker - auto winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); - if (!winSystem) - return std::nullopt; - NSRect frame = [nsEvent.window convertRectToBacking:winSystem->GetWindowDimensions()]; + NSRect frame = [nsEvent.window convertRectToBacking:nsEvent.window.contentView.frame]; // cocoa world is upside down ... location.y = frame.size.height - location.y; return location; From 1fa0709f1a6ecd28013e42a285ec306b632e3fe6 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Wed, 23 Aug 2023 16:17:09 +0100 Subject: [PATCH 072/811] [macos][nativewindowing] Do not react to mouse events if out of view bounds --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index 34ad2705667f5..ba23c16d68010 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -392,10 +392,12 @@ - (XBMC_Event)keyPressEvent:(NSEvent*)nsEvent - (std::optional)TranslateMouseLocation:(NSEvent*)nsEvent { NSPoint location = nsEvent.locationInWindow; - if (!nsEvent.window || location.x < 0 || location.y < 0) + // ignore events if outside the view bounds + if (!nsEvent.window || !NSPointInRect(location, nsEvent.window.contentView.frame)) { return std::nullopt; } + // translate the location to backing units location = [nsEvent.window convertPointToBacking:location]; NSRect frame = [nsEvent.window convertRectToBacking:nsEvent.window.contentView.frame]; // cocoa world is upside down ... From 10af763a4268daa74dd8f397548e02bfd6e49487 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 24 Aug 2023 11:56:54 +0100 Subject: [PATCH 073/811] [macos][nativewindowing] Factor mouse button events into a common SendXBMCMouseButtonEvent --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 128 +++++++++++-------------- 1 file changed, 56 insertions(+), 72 deletions(-) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index ba23c16d68010..29fe75c4337ee 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -202,86 +202,50 @@ - (NSEvent*)InputEventHandler:(NSEvent*)nsEvent // handle mouse events and transform them into the xbmc event world case NSEventTypeLeftMouseUp: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_LEFT; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONUP + buttonCode:XBMC_BUTTON_LEFT]; break; } case NSEventTypeLeftMouseDown: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_LEFT; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONDOWN + buttonCode:XBMC_BUTTON_LEFT]; break; } case NSEventTypeRightMouseUp: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_RIGHT; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONUP + buttonCode:XBMC_BUTTON_RIGHT]; break; } case NSEventTypeRightMouseDown: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_RIGHT; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONDOWN + buttonCode:XBMC_BUTTON_RIGHT]; break; } case NSEventTypeOtherMouseUp: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = XBMC_BUTTON_MIDDLE; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONUP + buttonCode:XBMC_BUTTON_MIDDLE]; break; } case NSEventTypeOtherMouseDown: { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) - { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = XBMC_BUTTON_MIDDLE; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - [self MessagePush:&newEvent]; - } + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONDOWN + buttonCode:XBMC_BUTTON_MIDDLE]; break; } case NSEventTypeMouseMoved: @@ -306,19 +270,17 @@ - (NSEvent*)InputEventHandler:(NSEvent*)nsEvent // with a zero deltaY. This reverses our scroll which is WTF? anoying. Trap them out here. if (nsEvent.deltaY != 0.0) { - auto location = [self TranslateMouseLocation:nsEvent]; - if (location.has_value()) + auto button = nsEvent.scrollingDeltaY > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN; + if ([self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONDOWN + buttonCode:button]) { - NSPoint locationCoordinates = location.value(); - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.x = locationCoordinates.x; - newEvent.button.y = locationCoordinates.y; - newEvent.button.button = - nsEvent.scrollingDeltaY > 0 ? XBMC_BUTTON_WHEELUP : XBMC_BUTTON_WHEELDOWN; - [self MessagePush:&newEvent]; - - newEvent.type = XBMC_MOUSEBUTTONUP; - [self MessagePush:&newEvent]; + // scrollwhell need a subsquent button press with no button code + [self SendXBMCMouseButtonEvent:nsEvent + xbmcEvent:newEvent + mouseEventType:XBMC_MOUSEBUTTONUP + buttonCode:std::nullopt]; } } break; @@ -389,6 +351,28 @@ - (XBMC_Event)keyPressEvent:(NSEvent*)nsEvent return newEvent; } +- (BOOL)SendXBMCMouseButtonEvent:(NSEvent*)nsEvent + xbmcEvent:(XBMC_Event&)xbmcEvent + mouseEventType:(uint8_t)mouseEventType + buttonCode:(std::optional)buttonCode +{ + auto location = [self TranslateMouseLocation:nsEvent]; + if (location.has_value()) + { + NSPoint locationCoordinates = location.value(); + xbmcEvent.type = mouseEventType; + if (buttonCode.has_value()) + { + xbmcEvent.button.button = buttonCode.value(); + } + xbmcEvent.button.x = locationCoordinates.x; + xbmcEvent.button.y = locationCoordinates.y; + [self MessagePush:&xbmcEvent]; + return true; + } + return false; +} + - (std::optional)TranslateMouseLocation:(NSEvent*)nsEvent { NSPoint location = nsEvent.locationInWindow; From 2a2a00b06a33fcaee04b87d153cb2fcc67bb8968 Mon Sep 17 00:00:00 2001 From: ivanllc Date: Sun, 20 Aug 2023 20:08:25 +0800 Subject: [PATCH 074/811] add 2 minutes padding recording time option. --- xbmc/pvr/settings/PVRSettings.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/pvr/settings/PVRSettings.cpp b/xbmc/pvr/settings/PVRSettings.cpp index f28651a6551d7..ddcdba3780975 100644 --- a/xbmc/pvr/settings/PVRSettings.cpp +++ b/xbmc/pvr/settings/PVRSettings.cpp @@ -172,9 +172,8 @@ void CPVRSettings::MarginTimeFiller(const SettingConstPtr& /*setting*/, { list.clear(); - static const int marginTimeValues[] = - { - 0, 1, 3, 5, 10, 15, 20, 30, 60, 90, 120, 180 // minutes + static const int marginTimeValues[] = { + 0, 1, 2, 3, 5, 10, 15, 20, 30, 60, 90, 120, 180 // minutes }; for (int iValue : marginTimeValues) From ac247539c32d9d5294c40faf66b10070265d3e87 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 24 Aug 2023 18:49:31 +0100 Subject: [PATCH 075/811] [macos][windowing] Signal mouse enabled/disabled when window becomes key (and resigns key) --- xbmc/windowing/osx/WinSystemOSX.mm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xbmc/windowing/osx/WinSystemOSX.mm b/xbmc/windowing/osx/WinSystemOSX.mm index e1fc062c2643a..a5b4897b8ea63 100644 --- a/xbmc/windowing/osx/WinSystemOSX.mm +++ b/xbmc/windowing/osx/WinSystemOSX.mm @@ -1379,11 +1379,13 @@ static void DisplayReconfigured(CGDirectDisplayID display, void CWinSystemOSX::enableInputEvents() { m_winEvents->enableInputEvents(); + signalMouseEntered(); } void CWinSystemOSX::disableInputEvents() { m_winEvents->disableInputEvents(); + signalMouseExited(); } std::string CWinSystemOSX::GetClipboardText() From 3922a18bdb35299871cf3ac715f2ed0756e525c2 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Mon, 21 Aug 2023 16:07:51 +0100 Subject: [PATCH 076/811] [MUSIC][GUI]Fix flickering when updating multiple song items via json-rpc --- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 3 ++- xbmc/interfaces/json-rpc/JSONRPC.cpp | 7 +++++++ xbmc/interfaces/json-rpc/JSONRPCUtils.h | 3 +++ xbmc/interfaces/json-rpc/schema/version.txt | 2 +- 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index 3c73a84af52f9..3521a3a8aefab 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -1022,7 +1022,8 @@ JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITranspo if (!musicdatabase.UpdateSong(song, updateartists)) return InternalError; - CJSONRPCUtils::NotifyItemUpdated(); + const auto item = std::make_shared(song); + CJSONRPCUtils::NotifyItemUpdated(item); return ACK; } diff --git a/xbmc/interfaces/json-rpc/JSONRPC.cpp b/xbmc/interfaces/json-rpc/JSONRPC.cpp index 4b861887a44de..214f6305c56fc 100644 --- a/xbmc/interfaces/json-rpc/JSONRPC.cpp +++ b/xbmc/interfaces/json-rpc/JSONRPC.cpp @@ -379,6 +379,13 @@ void CJSONRPCUtils::NotifyItemUpdated() CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(message); } +void CJSONRPCUtils::NotifyItemUpdated(const std::shared_ptr& item) +{ + auto& wm = CServiceBroker::GetGUI()->GetWindowManager(); + CGUIMessage message(GUI_MSG_NOTIFY_ALL, wm.GetActiveWindow(), 0, GUI_MSG_UPDATE_ITEM, 0, item); + wm.SendThreadMessage(message); +} + void CJSONRPCUtils::NotifyItemUpdated(const CVideoInfoTag& info, const std::map& artwork) { diff --git a/xbmc/interfaces/json-rpc/JSONRPCUtils.h b/xbmc/interfaces/json-rpc/JSONRPCUtils.h index 0c0a8f8ddefc9..ce40d8e49fc3e 100644 --- a/xbmc/interfaces/json-rpc/JSONRPCUtils.h +++ b/xbmc/interfaces/json-rpc/JSONRPCUtils.h @@ -12,8 +12,10 @@ #include "ITransportLayer.h" #include +#include #include +class CFileItem; class CVariant; class CVideoInfoTag; @@ -157,6 +159,7 @@ namespace JSONRPC { public: static void NotifyItemUpdated(); + static void NotifyItemUpdated(const std::shared_ptr& item); static void NotifyItemUpdated(const CVideoInfoTag& info, const std::map& artwork); }; diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt index 3ca0ab37cf64b..3f74ab3e4c5c9 100644 --- a/xbmc/interfaces/json-rpc/schema/version.txt +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -1 +1 @@ -JSONRPC_VERSION 13.2.0 +JSONRPC_VERSION 13.2.1 From e96bcd2dec85c5e0dcaaf9be6c64d8237da7bd6f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 25 Aug 2023 15:15:38 +1000 Subject: [PATCH 077/811] [tools/depends][python3] Bump to 3.11.5 --- tools/depends/target/python3/PYTHON3-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/python3/PYTHON3-VERSION b/tools/depends/target/python3/PYTHON3-VERSION index 290e333fe826b..45eb16c7df531 100644 --- a/tools/depends/target/python3/PYTHON3-VERSION +++ b/tools/depends/target/python3/PYTHON3-VERSION @@ -1,4 +1,4 @@ LIBNAME=Python -VERSION=3.11.2 +VERSION=3.11.5 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=5684ec7eae2dce26facc54d448ccdb6901bbfa1cab03abbe8fd34e4268a2b701daa13df15903349492447035be78380d473389e8703b4e910a65b088d2462e8b +SHA512=93fa640bedcea449060caac8aa691aa315a19f172fd9f0422183d17749c3512d4ecac60e7599f9ef14e3cdb3c8b4b060e484c9061b1e7ee8d958200d6041e408 From d6fb1f7b8bc20f6defc53a7fdd4aa0218f9b41c7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Nov 2022 13:02:42 +1000 Subject: [PATCH 078/811] [cmake] FindLibDVD windows add to libkodi target dep list Instead of adding to export-files, add to libkodi as a dependency. This will facilitate the optional use of export-files target --- cmake/modules/FindLibDvd.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index 6853e84618959..f06df5559b88a 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -17,7 +17,7 @@ else() set(LIBDVD_TARGET_DIR dlls) endif() copy_file_to_buildtree(${DEPENDS_PATH}/bin/libdvdnav.dll DIRECTORY ${LIBDVD_TARGET_DIR}) - add_dependencies(export-files LibDvdNav::LibDvdNav) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LibDvdNav::LibDvdNav) endif() set(LIBDVD_INCLUDE_DIRS ${LIBDVDREAD_INCLUDE_DIR} ${LIBDVDNAV_INCLUDE_DIR}) From 6302fa683e39c835f8adf1e1522579b68c5310c9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Nov 2022 13:16:44 +1000 Subject: [PATCH 079/811] [cmake] Macros.cmake refactor copy_file_to_buildtree function for early exit --- cmake/scripts/common/Macros.cmake | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index b6863960b7f0c..f0e356ef90194 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -204,6 +204,14 @@ endfunction() # Files is mirrored to the build tree and added to ${install_data} # (if NO_INSTALL is not given). function(copy_file_to_buildtree file) + # Exclude autotools build artifacts and other blacklisted files in source tree. + if(file MATCHES "(Makefile|\\.in|\\.xbt|\\.so|\\.dylib|\\.gitignore)$") + if(VERBOSE) + message(STATUS "copy_file_to_buildtree - ignoring file: ${file}") + endif() + return() + endif() + cmake_parse_arguments(arg "NO_INSTALL" "DIRECTORY;KEEP_DIR_STRUCTURE" "" ${ARGN}) if(arg_DIRECTORY) set(outdir ${arg_DIRECTORY}) @@ -230,14 +238,6 @@ function(copy_file_to_buildtree file) file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake "# Export files to build tree\n") endif() - # Exclude autotools build artefacts and other blacklisted files in source tree. - if(file MATCHES "(Makefile|\\.in|\\.xbt|\\.so|\\.dylib|\\.gitignore)$") - if(VERBOSE) - message(STATUS "copy_file_to_buildtree - ignoring file: ${file}") - endif() - return() - endif() - if(NOT file STREQUAL ${CMAKE_BINARY_DIR}/${outfile}) if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows" OR NOT IS_SYMLINK "${file}") if(VERBOSE) From 42c938305d6ff8545c2c0385dae0f92a44adc6e2 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 17 Aug 2023 23:39:52 +0100 Subject: [PATCH 080/811] [macos][nativewindowing] Add support for mediakeys --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 72 ++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index 29fe75c4337ee..27b609da989c4 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -22,6 +22,7 @@ #import #import +#import #pragma mark - objc implementation @@ -29,6 +30,7 @@ @implementation CWinEventsOSXImpl { std::queue events; CCriticalSection m_inputlock; + id m_mediaKeysTap; bool m_inputEnabled; //! macOS requires the calls the NSCursor hide/unhide to be balanced @@ -47,10 +49,8 @@ - (instancetype)init { self = [super init]; - [self enableInputEvents]; m_lastAppCursorVisibilityAction = NSCursorVisibilityBalancer::NONE; - m_inputEnabled = true; - + [self enableInputEvents]; return self; } @@ -158,11 +158,16 @@ - (bool)ProcessOSXShortcuts:(XBMC_Event&)event - (void)enableInputEvents { + m_mediaKeysTap = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskSystemDefined + handler:^(NSEvent* event) { + return [self InputEventHandler:event]; + }]; m_inputEnabled = true; } - (void)disableInputEvents { + m_mediaKeysTap = nil; m_inputEnabled = false; } @@ -307,6 +312,21 @@ - (NSEvent*)InputEventHandler:(NSEvent*)nsEvent break; } + // media keys + case NSEventTypeSystemDefined: + { + if (nsEvent.subtype == NX_SUBTYPE_AUX_CONTROL_BUTTONS) + { + const int keyCode = (([nsEvent data1] & 0xFFFF0000) >> 16); + const int keyFlags = ([nsEvent data1] & 0x0000FFFF); + const int keyState = (((keyFlags & 0xFF00) >> 8)) == 0xA; + if (keyState == 1) // if pressed + { + passEvent = [self HandleMediaKey:keyCode]; + } + } + break; + } default: return nsEvent; } @@ -389,4 +409,50 @@ - (BOOL)SendXBMCMouseButtonEvent:(NSEvent*)nsEvent return location; } +- (void)SendPlayerAction:(int)actionId +{ + CAction* action = new CAction(actionId); + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast(action)); +} + +- (bool)HandleMediaKey:(int)keyCode +{ + bool passEvent = false; + switch (keyCode) + { + case NX_KEYTYPE_PLAY: + { + [self SendPlayerAction:ACTION_PLAYER_PLAYPAUSE]; + break; + } + case NX_KEYTYPE_NEXT: + { + [self SendPlayerAction:ACTION_NEXT_ITEM]; + break; + } + case NX_KEYTYPE_PREVIOUS: + { + [self SendPlayerAction:ACTION_PREV_ITEM]; + break; + } + case NX_KEYTYPE_FAST: + { + [self SendPlayerAction:ACTION_PLAYER_FORWARD]; + break; + } + case NX_KEYTYPE_REWIND: + { + [self SendPlayerAction:ACTION_PLAYER_REWIND]; + break; + } + default: + { + passEvent = true; + break; + } + } + return passEvent; +} + @end From b93ac7db8ab9397e8361a508f7c7809953366270 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 23 Aug 2023 10:56:26 +0200 Subject: [PATCH 081/811] [video] TV Shows: Consider shows with no completely watched episodes, but with at least one episode with a resume bookmark as 'in progress'." --- xbmc/video/VideoDatabase.cpp | 21 +++++++++++++++---- xbmc/video/VideoDatabase.h | 1 + .../windows/VideoFileItemListModifier.cpp | 3 +++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index d5bb916d7692d..e820b1d503941 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -397,19 +397,24 @@ void CVideoDatabase::CreateViews() m_pDS->exec(episodeview); CLog::Log(LOGINFO, "create tvshowcounts"); + // clang-format off std::string tvshowcounts = PrepareSQL("CREATE VIEW tvshowcounts AS SELECT " " tvshow.idShow AS idShow," " MAX(files.lastPlayed) AS lastPlayed," " NULLIF(COUNT(episode.c12), 0) AS totalCount," " COUNT(files.playCount) AS watchedcount," " NULLIF(COUNT(DISTINCT(episode.c12)), 0) AS totalSeasons, " - " MAX(files.dateAdded) as dateAdded " + " MAX(files.dateAdded) as dateAdded, " + " COUNT(bookmark.type) AS inProgressCount " " FROM tvshow" " LEFT JOIN episode ON" " episode.idShow=tvshow.idShow" " LEFT JOIN files ON" " files.idFile=episode.idFile " + " LEFT JOIN bookmark ON" + " bookmark.idFile=files.idFile AND bookmark.type=1 " "GROUP BY tvshow.idShow"); + // clang-format on m_pDS->exec(tvshowcounts); CLog::Log(LOGINFO, "create tvshowlinkpath_minview"); @@ -427,6 +432,7 @@ void CVideoDatabase::CreateViews() m_pDS->exec(tvshowlinkpathview); CLog::Log(LOGINFO, "create tvshow_view"); + // clang-format off std::string tvshowview = PrepareSQL("CREATE VIEW tvshow_view AS SELECT " " tvshow.*," " path.idParentPath AS idParentPath," @@ -437,7 +443,8 @@ void CVideoDatabase::CreateViews() " rating.votes AS votes, " " rating.rating_type AS rating_type, " " uniqueid.value AS uniqueid_value, " - " uniqueid.type AS uniqueid_type " + " uniqueid.type AS uniqueid_type, " + " tvshowcounts.inProgressCount AS inProgressCount " "FROM tvshow" " LEFT JOIN tvshowlinkpath_minview ON " " tvshowlinkpath_minview.idShow=tvshow.idShow" @@ -450,6 +457,7 @@ void CVideoDatabase::CreateViews() " LEFT JOIN uniqueid ON" " uniqueid.uniqueid_id=tvshow.c%02d ", VIDEODB_ID_TV_RATING_ID, VIDEODB_ID_TV_IDENT_ID); + // clang-format on m_pDS->exec(tvshowview); CLog::Log(LOGINFO, "create season_view"); @@ -4159,6 +4167,10 @@ CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* con details.SetUniqueID(record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE).get_asString(), record->at(VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE).get_asString(), true); details.SetDuration(record->at(VIDEODB_DETAILS_TVSHOW_DURATION).get_asInt()); + //! @todo videotag member + guiinfo int needed? + //! -- Currently not needed; having it available as item prop seems sufficient for skinning + const int inProgressEpisodes = record->at(VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS).get_asInt(); + if (getDetails) { if (getDetails & VideoDbDetailsCast) @@ -4186,6 +4198,7 @@ CVideoInfoTag CVideoDatabase::GetDetailsForTvShow(const dbiplus::sql_record* con item->SetProperty("numepisodes", details.m_iEpisode); // will be changed later to reflect watchmode setting item->SetProperty("watchedepisodes", details.GetPlayCount()); item->SetProperty("unwatchedepisodes", details.m_iEpisode - details.GetPlayCount()); + item->SetProperty("inprogressepisodes", inProgressEpisodes); item->SetProperty("watchedepisodepercent", details.m_iEpisode > 0 ? (details.GetPlayCount() * 100 / details.m_iEpisode) : 0); @@ -5933,7 +5946,7 @@ void CVideoDatabase::UpdateTables(int iVersion) int CVideoDatabase::GetSchemaVersion() const { - return 121; + return 122; } bool CVideoDatabase::LookupByFolders(const std::string &path, bool shows) @@ -8070,7 +8083,7 @@ bool CVideoDatabase::GetInProgressTvShowsNav(const std::string& strBaseDir, CFil { Filter filter; filter.order = PrepareSQL("c%02d", VIDEODB_ID_TV_TITLE); - filter.where = "watchedCount != 0 AND totalCount != watchedCount"; + filter.where = "totalCount != watchedCount AND (inProgressCount > 0 OR watchedCount != 0)"; return GetTvShowsByWhere(strBaseDir, filter, items, SortDescription(), getDetails); } diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index 8e9facdbab947..ebd6eccf6b072 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -130,6 +130,7 @@ enum VideoDbDetails #define VIDEODB_DETAILS_TVSHOW_RATING_TYPE VIDEODB_MAX_COLUMNS + 12 #define VIDEODB_DETAILS_TVSHOW_UNIQUEID_VALUE VIDEODB_MAX_COLUMNS + 13 #define VIDEODB_DETAILS_TVSHOW_UNIQUEID_TYPE VIDEODB_MAX_COLUMNS + 14 +#define VIDEODB_DETAILS_TVSHOW_NUM_INPROGRESS VIDEODB_MAX_COLUMNS + 15 #define VIDEODB_DETAILS_MUSICVIDEO_USER_RATING VIDEODB_MAX_COLUMNS + 2 #define VIDEODB_DETAILS_MUSICVIDEO_PREMIERED VIDEODB_MAX_COLUMNS + 3 diff --git a/xbmc/video/windows/VideoFileItemListModifier.cpp b/xbmc/video/windows/VideoFileItemListModifier.cpp index 4d26af37b0838..4205d68af13b4 100644 --- a/xbmc/video/windows/VideoFileItemListModifier.cpp +++ b/xbmc/video/windows/VideoFileItemListModifier.cpp @@ -71,11 +71,13 @@ void CVideoFileItemListModifier::AddQueuingFolder(CFileItemList& items) // set the number of watched and unwatched items accordingly int watched = 0; int unwatched = 0; + int inprogress = 0; for (int i = 0; i < items.Size(); i++) { CFileItemPtr item = items[i]; watched += static_cast(item->GetProperty("watchedepisodes").asInteger()); unwatched += static_cast(item->GetProperty("unwatchedepisodes").asInteger()); + inprogress += static_cast(item->GetProperty("inprogressepisodes").asInteger()); } const int totalEpisodes = watched + unwatched; pItem->SetProperty("totalepisodes", totalEpisodes); @@ -83,6 +85,7 @@ void CVideoFileItemListModifier::AddQueuingFolder(CFileItemList& items) totalEpisodes); // will be changed later to reflect watchmode setting pItem->SetProperty("watchedepisodes", watched); pItem->SetProperty("unwatchedepisodes", unwatched); + pItem->SetProperty("inprogressepisodes", inprogress); pItem->SetProperty("watchedepisodepercent", totalEpisodes > 0 ? watched * 100 / totalEpisodes : 0); From 046553fe1b4f232d5306758e684b50409bd161d1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 23 Aug 2023 12:20:02 +0200 Subject: [PATCH 082/811] [Estuary] TV Shows: Show in progress episodes count along with watched episodes and total episodes count in home screen listings and video window listings. --- addons/skin.estuary/xml/Variables.xml | 8 +++----- addons/skin.estuary/xml/View_501_Banner.xml | 6 +++--- addons/skin.estuary/xml/View_51_Poster.xml | 2 +- addons/skin.estuary/xml/View_53_Shift.xml | 4 ++-- addons/skin.estuary/xml/View_54_InfoWall.xml | 2 +- 5 files changed, 10 insertions(+), 12 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 6090d86e48620..c7e19d8dfb847 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -86,7 +86,7 @@ $INFO[ListItem.Label] - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + $VAR[WatchedStatusVar] $INFO[ListItem.Year] $LOCALIZE[38026]: $INFO[ListItem.Appearances] $INFO[ListItem.Duration] @@ -275,10 +275,8 @@ $INFO[ListItem.Art(season.poster)] $INFO[ListItem.Art(tvshow.poster)] - - $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] - + + $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes), / ,]$INFO[ListItem.Property(TotalEpisodes), / ,] 2x diff --git a/addons/skin.estuary/xml/View_501_Banner.xml b/addons/skin.estuary/xml/View_501_Banner.xml index 6219bd9440b9e..f63e2f650bc72 100644 --- a/addons/skin.estuary/xml/View_501_Banner.xml +++ b/addons/skin.estuary/xml/View_501_Banner.xml @@ -118,7 +118,7 @@ !Listitem.IsParentFolder - 220 + 230 143 22 22 @@ -126,7 +126,7 @@ !String.IsEmpty(ListItem.Rating) - 258 + 268 144 800 20 @@ -152,7 +152,7 @@ font12 right center - + diff --git a/addons/skin.estuary/xml/View_51_Poster.xml b/addons/skin.estuary/xml/View_51_Poster.xml index 62e976fadbaf7..7ee7748c19fe9 100644 --- a/addons/skin.estuary/xml/View_51_Poster.xml +++ b/addons/skin.estuary/xml/View_51_Poster.xml @@ -158,7 +158,7 @@ 690 435 24 - + font20_title text_shadow right diff --git a/addons/skin.estuary/xml/View_53_Shift.xml b/addons/skin.estuary/xml/View_53_Shift.xml index b9bf6e00077bd..d6c2372a9d0dc 100644 --- a/addons/skin.estuary/xml/View_53_Shift.xml +++ b/addons/skin.estuary/xml/View_53_Shift.xml @@ -107,7 +107,7 @@ 522 292 24 - + font20_title text_shadow right @@ -195,7 +195,7 @@ 522 292 24 - + font20_title text_shadow right diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index f014a5c6d9731..9beacf1f95ded 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -321,7 +321,7 @@ 0 340 244 - + font20_title text_shadow right From 12c3b94ec66a45c8a28119c2b16aec714c83286b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 23 Aug 2023 13:55:05 +0200 Subject: [PATCH 083/811] [video] Movie sets: Expose number of in progress movies via item property. --- xbmc/utils/GroupUtils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xbmc/utils/GroupUtils.cpp b/xbmc/utils/GroupUtils.cpp index a51399fa461cc..ef5615a80a8fc 100644 --- a/xbmc/utils/GroupUtils.cpp +++ b/xbmc/utils/GroupUtils.cpp @@ -91,6 +91,7 @@ bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileI int ratings = 0; float totalRatings = 0; int iWatched = 0; // have all the movies been played at least once? + int inProgress = 0; std::set pathSet; for (std::set::const_iterator movie = set->second.begin(); movie != set->second.end(); ++movie) { @@ -119,6 +120,11 @@ bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileI if (movieInfo->GetPlayCount() > 0) iWatched++; + // handle resume points + CBookmark bookmark = movieInfo->GetResumePoint(); + if (bookmark.IsSet()) + inProgress++; + //accumulate the path for a multipath construction CFileItem video(movieInfo->m_basePath, false); if (video.IsVideo()) @@ -135,6 +141,7 @@ bool GroupUtils::Group(GroupBy groupBy, const std::string &baseDir, const CFileI pItem->SetProperty("total", (int)set->second.size()); pItem->SetProperty("watched", iWatched); pItem->SetProperty("unwatched", (int)set->second.size() - iWatched); + pItem->SetProperty("inprogress", inProgress); pItem->SetOverlayImage(CGUIListItem::ICON_OVERLAY_UNWATCHED, setInfo->GetPlayCount() > 0); groupedItems.Add(pItem); From 0d3f7bf39af453e8dcf37ff7791af72a44dedf56 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:13:28 +0200 Subject: [PATCH 084/811] [Estuary] Movie sets: Show watched/in progress/total counts in home screen listings and video window listings (aligned with what we have for TV shows). --- addons/skin.estuary/xml/Variables.xml | 2 ++ addons/skin.estuary/xml/View_51_Poster.xml | 2 +- addons/skin.estuary/xml/View_53_Shift.xml | 4 ++-- addons/skin.estuary/xml/View_54_InfoWall.xml | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index c7e19d8dfb847..e762db3c606a6 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -87,6 +87,7 @@ $VAR[WatchedStatusVar] + $VAR[WatchedStatusVar] $INFO[ListItem.Year] $LOCALIZE[38026]: $INFO[ListItem.Appearances] $INFO[ListItem.Duration] @@ -277,6 +278,7 @@ $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes), / ,]$INFO[ListItem.Property(TotalEpisodes), / ,] + $INFO[ListItem.Property(InProgress)]$INFO[ListItem.Property(Watched), / ,]$INFO[ListItem.Property(Total), / ,] 2x diff --git a/addons/skin.estuary/xml/View_51_Poster.xml b/addons/skin.estuary/xml/View_51_Poster.xml index 7ee7748c19fe9..5f0d2fb72b1ee 100644 --- a/addons/skin.estuary/xml/View_51_Poster.xml +++ b/addons/skin.estuary/xml/View_51_Poster.xml @@ -144,7 +144,7 @@ $VAR[PosterThumbVar] - String.IsEqual(ListItem.DBtype,tvshow) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) 4 670 diff --git a/addons/skin.estuary/xml/View_53_Shift.xml b/addons/skin.estuary/xml/View_53_Shift.xml index d6c2372a9d0dc..877b98b8af4de 100644 --- a/addons/skin.estuary/xml/View_53_Shift.xml +++ b/addons/skin.estuary/xml/View_53_Shift.xml @@ -93,7 +93,7 @@ - String.IsEqual(ListItem.DBtype,tvshow) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) 35 500 @@ -181,7 +181,7 @@ True - String.IsEqual(ListItem.DBtype,tvshow) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) 35 500 diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index 9beacf1f95ded..92487d5fa4565 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -308,7 +308,7 @@ - String.IsEqual(ListItem.DBtype,tvshow) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) 35 320 From 52a4d389242e0a0795171bc4c2f9a53889808339 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 23 Aug 2023 23:49:13 +0200 Subject: [PATCH 085/811] [video] Seasons: Expose number of in progress episodes via item property. --- xbmc/video/VideoDatabase.cpp | 10 +++++++++- xbmc/video/VideoDatabase.h | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index e820b1d503941..6690659d842fc 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -461,6 +461,7 @@ void CVideoDatabase::CreateViews() m_pDS->exec(tvshowview); CLog::Log(LOGINFO, "create season_view"); + // clang-format off std::string seasonview = PrepareSQL("CREATE VIEW season_view AS SELECT " " seasons.idSeason AS idSeason," " seasons.idShow AS idShow," @@ -476,7 +477,8 @@ void CVideoDatabase::CreateViews() " tvshow_view.c%02d AS mpaa," " count(DISTINCT episode.idEpisode) AS episodes," " count(files.playCount) AS playCount," - " min(episode.c%02d) AS aired " + " min(episode.c%02d) AS aired, " + " count(bookmark.type) AS inProgressCount " "FROM seasons" " JOIN tvshow_view ON" " tvshow_view.idShow = seasons.idShow" @@ -484,6 +486,8 @@ void CVideoDatabase::CreateViews() " episode.idShow = seasons.idShow AND episode.c%02d = seasons.season" " JOIN files ON" " files.idFile = episode.idFile " + " LEFT JOIN bookmark ON" + " bookmark.idFile = files.idFile AND bookmark.type = 1 " "GROUP BY seasons.idSeason," " seasons.idShow," " seasons.season," @@ -501,6 +505,7 @@ void CVideoDatabase::CreateViews() VIDEODB_ID_EPISODE_AIRED, VIDEODB_ID_EPISODE_SEASON, VIDEODB_ID_TV_TITLE, VIDEODB_ID_TV_PLOT, VIDEODB_ID_TV_PREMIERED, VIDEODB_ID_TV_GENRE, VIDEODB_ID_TV_STUDIOS, VIDEODB_ID_TV_MPAA); + // clang-format on m_pDS->exec(seasonview); CLog::Log(LOGINFO, "create musicvideo_view"); @@ -7497,11 +7502,14 @@ bool CVideoDatabase::GetSeasonsByWhere(const std::string& strBaseDir, const Filt const int totalEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_TOTAL).get_asInt(); const int watchedEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_WATCHED).get_asInt(); + const int inProgressEpisodes = m_pDS->fv(VIDEODB_ID_SEASON_EPISODES_INPROGRESS).get_asInt(); + pItem->GetVideoInfoTag()->m_iEpisode = totalEpisodes; pItem->SetProperty("totalepisodes", totalEpisodes); pItem->SetProperty("numepisodes", totalEpisodes); // will be changed later to reflect watchmode setting pItem->SetProperty("watchedepisodes", watchedEpisodes); pItem->SetProperty("unwatchedepisodes", totalEpisodes - watchedEpisodes); + pItem->SetProperty("inprogressepisodes", inProgressEpisodes); pItem->SetProperty("watchedepisodepercent", totalEpisodes > 0 ? (watchedEpisodes * 100 / totalEpisodes) : 0); if (iSeason == 0) diff --git a/xbmc/video/VideoDatabase.h b/xbmc/video/VideoDatabase.h index ebd6eccf6b072..c946edfa5addf 100644 --- a/xbmc/video/VideoDatabase.h +++ b/xbmc/video/VideoDatabase.h @@ -291,6 +291,7 @@ typedef enum // this enum MUST match the offset struct further down!! and make s VIDEODB_ID_SEASON_EPISODES_TOTAL = 12, VIDEODB_ID_SEASON_EPISODES_WATCHED = 13, VIDEODB_ID_SEASON_PREMIERED = 14, + VIDEODB_ID_SEASON_EPISODES_INPROGRESS = 15, VIDEODB_ID_SEASON_MAX } VIDEODB_SEASON_IDS; From 4c4d544410e038c688845f98b6dd7519567f66a0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 24 Aug 2023 08:08:17 +0200 Subject: [PATCH 086/811] [Estuary] Seasons: Show watched/in progress/total counts in home screen listings and video window listings (aligned with what we have for TV shows and movie sets). --- addons/skin.estuary/xml/Variables.xml | 3 ++- addons/skin.estuary/xml/View_501_Banner.xml | 2 +- addons/skin.estuary/xml/View_51_Poster.xml | 2 +- addons/skin.estuary/xml/View_53_Shift.xml | 4 ++-- addons/skin.estuary/xml/View_54_InfoWall.xml | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index e762db3c606a6..89bd4f5de224c 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -87,6 +87,7 @@ $VAR[WatchedStatusVar] + $VAR[WatchedStatusVar] $VAR[WatchedStatusVar] $INFO[ListItem.Year] $LOCALIZE[38026]: $INFO[ListItem.Appearances] @@ -277,7 +278,7 @@ $INFO[ListItem.Art(tvshow.poster)] - $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes), / ,]$INFO[ListItem.Property(TotalEpisodes), / ,] + $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes), / ,]$INFO[ListItem.Property(TotalEpisodes), / ,] $INFO[ListItem.Property(InProgress)]$INFO[ListItem.Property(Watched), / ,]$INFO[ListItem.Property(Total), / ,] diff --git a/addons/skin.estuary/xml/View_501_Banner.xml b/addons/skin.estuary/xml/View_501_Banner.xml index f63e2f650bc72..0340525520af3 100644 --- a/addons/skin.estuary/xml/View_501_Banner.xml +++ b/addons/skin.estuary/xml/View_501_Banner.xml @@ -142,7 +142,7 @@ 22 22 lists/played-total.png - String.IsEqual(Listitem.dbtype,tvshow) + !String.IsEmpty(ListItem.Property(TotalEpisodes)) + (String.IsEqual(Listitem.dbtype,tvshow) | String.IsEqual(Listitem.dbtype,season)) + !String.IsEmpty(ListItem.Property(TotalEpisodes)) 103 diff --git a/addons/skin.estuary/xml/View_51_Poster.xml b/addons/skin.estuary/xml/View_51_Poster.xml index 5f0d2fb72b1ee..b4a94a696abc6 100644 --- a/addons/skin.estuary/xml/View_51_Poster.xml +++ b/addons/skin.estuary/xml/View_51_Poster.xml @@ -144,7 +144,7 @@ $VAR[PosterThumbVar] - String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) | String.IsEqual(ListItem.DBType,season) 4 670 diff --git a/addons/skin.estuary/xml/View_53_Shift.xml b/addons/skin.estuary/xml/View_53_Shift.xml index 877b98b8af4de..4c125fbbbfbbe 100644 --- a/addons/skin.estuary/xml/View_53_Shift.xml +++ b/addons/skin.estuary/xml/View_53_Shift.xml @@ -93,7 +93,7 @@ - String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) | String.IsEqual(ListItem.DBType,season) 35 500 @@ -181,7 +181,7 @@ True - String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) | String.IsEqual(ListItem.DBType,season) 35 500 diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index 92487d5fa4565..86ecc8a8b8f36 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -308,7 +308,7 @@ - String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) + String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) | String.IsEqual(ListItem.DBType,season) 35 320 From 650c196f25ac3fbe3d7bdbcc4aeea3f6135769e4 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 25 Aug 2023 17:16:41 +0200 Subject: [PATCH 087/811] [PVR] Recording folders: Expose number of in progress episodes via item property and via item label2. --- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index 24b33e1b99c43..5a4c37d5ab132 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -317,6 +317,7 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath, item->SetProperty("totalepisodes", 0); item->SetProperty("watchedepisodes", 0); item->SetProperty("unwatchedepisodes", 0); + item->SetProperty("inprogressepisodes", 0); item->SetProperty("sizeinbytes", UINT64_C(0)); // Assume all folders are watched, we'll change the overlay later @@ -340,7 +341,13 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath, { item->IncrementProperty("watchedepisodes", 1); } - item->SetLabel2(StringUtils::Format("{} / {}", item->GetProperty("watchedepisodes").asString(), + if (recording->GetResumePoint().IsPartWay()) + { + item->IncrementProperty("inprogressepisodes", 1); + } + item->SetLabel2(StringUtils::Format("{}|{}|{}", + item->GetProperty("inprogressepisodes").asString(), + item->GetProperty("watchedepisodes").asString(), item->GetProperty("totalepisodes").asString())); item->IncrementProperty("sizeinbytes", recording->GetSizeInBytes()); From 31785633f7d3f14db1f4667fb9a83f18f45ac395 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 24 Aug 2023 23:05:25 +0200 Subject: [PATCH 088/811] [Estuary] Use | instead of / for separating unwatched|in progress|total counts to avoid confusion with date format. --- addons/skin.estuary/xml/Variables.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 89bd4f5de224c..7541e834af212 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -278,8 +278,8 @@ $INFO[ListItem.Art(tvshow.poster)] - $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes), / ,]$INFO[ListItem.Property(TotalEpisodes), / ,] - $INFO[ListItem.Property(InProgress)]$INFO[ListItem.Property(Watched), / ,]$INFO[ListItem.Property(Total), / ,] + $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes),|,]$INFO[ListItem.Property(TotalEpisodes),|,] + $INFO[ListItem.Property(InProgress)]$INFO[ListItem.Property(Watched),|,]$INFO[ListItem.Property(Total),|,] 2x From 7f5fbd745934854100163155a9f4bd4a2bc9264a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Aug 2023 15:33:01 +0200 Subject: [PATCH 089/811] [video] Fix watched state / last played not preserved on internet update of movies / TV show episodes. --- xbmc/video/VideoInfoScanner.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 629e76762414d..7b8c760d5fba9 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -1464,10 +1464,12 @@ namespace VIDEO if (!pItem->m_bIsFolder) { - if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bVideoLibraryImportWatchedState || libraryImport) + const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if ((libraryImport || advancedSettings->m_bVideoLibraryImportWatchedState) && + (movieDetails.IsPlayCountSet() || movieDetails.m_lastPlayed.IsValid())) m_database.SetPlayCount(*pItem, movieDetails.GetPlayCount(), movieDetails.m_lastPlayed); - if ((CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_bVideoLibraryImportResumePoint || libraryImport) && + if ((libraryImport || advancedSettings->m_bVideoLibraryImportResumePoint) && movieDetails.GetResumePoint().IsSet()) m_database.AddBookMarkToFile(pItem->GetPath(), movieDetails.GetResumePoint(), CBookmark::RESUME); } From 3abc8ac39da44e5020edbedca666065c7712665e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 26 Aug 2023 14:18:53 +0200 Subject: [PATCH 090/811] [PVR] Fix TV channel subtitles not displayed on playback start, although activated in subtitle settings. --- xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp index 1c2c742ab28d2..24f26184225c2 100644 --- a/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogSubtitleSettings.cpp @@ -99,6 +99,11 @@ void CGUIDialogSubtitleSettings::OnSettingChanged(const std::shared_ptr(setting)->GetValue(); + if (value) + { + // Ensure that we use/store the subtitle stream the user currently sees in the dialog. + appPlayer->SetSubtitle(m_subtitleStream); + } appPlayer->SetSubtitleVisible(value); } else if (settingId == SETTING_SUBTITLE_DELAY) From 6508c4cb7292cdeb5d04251a9a5c9c6a82035e63 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 9 Nov 2022 13:36:51 +1000 Subject: [PATCH 091/811] [cmake] Enable Windows Multiconfig Gen to bundle a complete working executable folder This allows VS (a Multiconfig Generator) to build and create a full file structure required to execute the main app target immediately from the build location that is created in a folder path that is not known at cmake generation. An example, is if you build Kodi as Debug, the executable will be placed in {CMAKE_BINARY_DIR}/Debug/kodi.exe but if you change the Build Type to Release, it will go in ${CMAKE_BINARY_DIR}/Release/kodi.exe. As you can change to any build type you wish after cmake generation, we can not know the location at cmake generation time, so currently all the required dlls/files/folders for a complete bundle are just placed in ${CMAKE_BINARY_DIR}/ and therefore you need to copy the kodi.exe from the folder it was built into the parent folder to be able to execute it. --- CMakeLists.txt | 72 ++++++++++++++----- cmake/installdata/windowsstore/dlls.txt | 2 +- cmake/modules/FindLibDvd.cmake | 3 - .../scripts/common/GenerateCompileInfo.cmake | 14 ++++ ...Files.cmake => GenerateSystemAddons.cmake} | 17 +---- cmake/scripts/common/Macros.cmake | 46 +++++++----- cmake/scripts/common/ProjectMacros.cmake | 23 +++--- cmake/scripts/windowsstore/Install.cmake | 30 +++++--- cmake/scripts/windowsstore/Macros.cmake | 8 +-- tools/buildsteps/windows/BuildSetup.bat | 10 +-- .../interfaces/json-rpc/schema/CMakeLists.txt | 21 ++++-- 11 files changed, 159 insertions(+), 87 deletions(-) create mode 100644 cmake/scripts/common/GenerateCompileInfo.cmake rename cmake/scripts/common/{GenerateVersionedFiles.cmake => GenerateSystemAddons.cmake} (52%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ca211a874378..0bff58c17c944 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,9 @@ set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED QUIET) list(APPEND DEPLIBS ${CMAKE_THREAD_LIBS_INIT}) +# Clean any existing generated build dir cmake files +file(REMOVE ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake) +file(REMOVE ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake) foreach(depspec ${PLATFORM_REQUIRED_DEPS}) # We need to specify ENABLE_${PLATFORM_REQUIRED_DEPS} in order for the @@ -267,7 +270,6 @@ endif() # Compile Info add_custom_command(OUTPUT ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp - ${ADDON_XML_OUTPUTS} COMMAND ${CMAKE_COMMAND} -DCORE_SOURCE_DIR=${CMAKE_SOURCE_DIR} -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} -DCORE_PLATFORM_NAME_LC="${CORE_PLATFORM_NAME_LC}" @@ -282,10 +284,8 @@ add_custom_command(OUTPUT ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp -DPYTHON_VERSION=${PYTHON_VERSION} -Dprefix=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} -DKODI_WEBSERVER_EXTRA_WHITELIST="${KODI_WEBSERVER_EXTRA_WHITELIST}" - -P ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateVersionedFiles.cmake + -P ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateCompileInfo.cmake DEPENDS ${CMAKE_SOURCE_DIR}/version.txt - export-files - ${ADDON_XML_DEPENDS} ${CMAKE_SOURCE_DIR}/xbmc/CompileInfo.cpp.in) list(APPEND install_data ${ADDON_INSTALL_DATA}) add_library(compileinfo OBJECT ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp) @@ -327,18 +327,9 @@ copy_files_from_filelist_to_buildtree(${CMAKE_SOURCE_DIR}/cmake/installdata/comm list(APPEND SKINS "${CMAKE_SOURCE_DIR}/addons/skin.estuary\;${CMAKE_SOURCE_DIR}") list(APPEND SKINS "${CMAKE_SOURCE_DIR}/addons/skin.estouchy\;${CMAKE_SOURCE_DIR}") -# These are skins that are copied into place from the source tree -foreach(skin ${SKINS}) - list(GET skin 0 dir) - list(GET skin 1 relative) - copy_skin_to_buildtree(${dir} ${relative}) -endforeach() - -add_custom_target(pack-skins ALL - DEPENDS TexturePacker::TexturePacker::Executable export-files ${XBT_FILES}) -set_target_properties(pack-skins PROPERTIES FOLDER "Build Utilities") - -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/system/players/VideoPlayer) +if(NOT ${CORE_SYSTEM_NAME} MATCHES "windows") + file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/system/players/VideoPlayer) +endif() set(LIBCEC_SONAME "${CEC_SONAME}") if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL android AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) @@ -408,11 +399,56 @@ else() # Related to https://stackoverflow.com/questions/46307266/including-objects-to-a-shared-library-from-a-c-archive-a set_target_properties(${APP_NAME_LC} PROPERTIES LINK_FLAGS "-Wl,-Bsymbolic") endif() -add_dependencies(${APP_NAME_LC} ${APP_NAME_LC}-libraries export-files pack-skins) +add_dependencies(${APP_NAME_LC} ${APP_NAME_LC}-libraries) + whole_archive(_MAIN_LIBRARIES ${FSTRCMP_LIBRARY} ${core_DEPENDS}) target_link_libraries(${APP_NAME_LC} ${_MAIN_LIBRARIES} lib${APP_NAME_LC} ${DEPLIBS}) unset(_MAIN_LIBRARIES) +if(${CORE_SYSTEM_NAME} MATCHES "windows") + set(_bundle_dir $) +else() + set(_bundle_dir ${CMAKE_BINARY_DIR}) +endif() + +# These are skins that are copied into place from the source tree +foreach(skin ${SKINS}) + list(GET skin 0 dir) + list(GET skin 1 relative) + copy_skin_to_buildtree(${dir} ${relative}) +endforeach() + +# Generate system addons +add_custom_target(gen_system_addons + COMMAND ${CMAKE_COMMAND} -DCORE_SOURCE_DIR=${CMAKE_SOURCE_DIR} + -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} + -DCORE_PLATFORM_NAME_LC="${CORE_PLATFORM_NAME_LC}" + -DCORE_BUILD_DIR=${CORE_BUILD_DIR} + -DCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR} + -DBUNDLEDIR=${_bundle_dir} + -P ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateSystemAddons.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Pack skins and copy to correct build dir (MultiConfig Generator aware) +add_custom_target(gen_skin_pack + COMMAND ${CMAKE_COMMAND} -DBUNDLEDIR=${_bundle_dir} + -DTEXTUREPACKER_EXECUTABLE=$ + -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Packaging target. This generates system addon, xbt creation, copy files to build tree +add_custom_target(generate-packaging ALL + DEPENDS TexturePacker::TexturePacker::Executable export-files gen_skin_pack gen_system_addons ${XBT_FILES}) +# Make sure we build any libs before we look to export-files. +# We may need to export some shared libs/data (eg Python) +add_dependencies(export-files ${GLOBAL_TARGET_DEPS}) + +# Add to lib${APP_NAME_LC} solely for Win UWP. msix building doesnt seem to pick up the +# generated buildtree if we do it later. Other platforms dont care when this happens. +add_dependencies(lib${APP_NAME_LC} generate-packaging) + +set_target_properties(generate-packaging PROPERTIES FOLDER "Build Utilities") + if(WIN32) set_target_properties(${APP_NAME_LC} PROPERTIES WIN32_EXECUTABLE ON) set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT ${APP_NAME_LC}) @@ -450,7 +486,7 @@ if(HOST_CAN_EXECUTE_TARGET AND ENABLE_TESTING) unset(_TEST_LIBRARIES) if (ENABLE_INTERNAL_GTEST) - add_dependencies(${APP_NAME_LC}-test ${APP_NAME_LC}-libraries export-files gtest) + add_dependencies(${APP_NAME_LC}-test ${APP_NAME_LC}-libraries generate-packaging gtest) endif() # Enable unit-test related targets diff --git a/cmake/installdata/windowsstore/dlls.txt b/cmake/installdata/windowsstore/dlls.txt index 8b4544968c95e..5f0e3397493de 100644 --- a/cmake/installdata/windowsstore/dlls.txt +++ b/cmake/installdata/windowsstore/dlls.txt @@ -1 +1 @@ -project/BuildDependencies/win10-${ARCH}/bin/*.dll dlls +project/BuildDependencies/win10-${ARCH}/bin/*.dll . diff --git a/cmake/modules/FindLibDvd.cmake b/cmake/modules/FindLibDvd.cmake index f06df5559b88a..dd7d8a26d2168 100644 --- a/cmake/modules/FindLibDvd.cmake +++ b/cmake/modules/FindLibDvd.cmake @@ -13,9 +13,6 @@ if(NOT CORE_SYSTEM_NAME MATCHES windows) core_link_library(${LIBDVDNAV_LIBRARY} system/players/VideoPlayer/libdvdnav libdvdnav archives "${_dvdlibs}") else() set(LIBDVD_TARGET_DIR .) - if(CORE_SYSTEM_NAME STREQUAL windowsstore) - set(LIBDVD_TARGET_DIR dlls) - endif() copy_file_to_buildtree(${DEPENDS_PATH}/bin/libdvdnav.dll DIRECTORY ${LIBDVD_TARGET_DIR}) set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LibDvdNav::LibDvdNav) endif() diff --git a/cmake/scripts/common/GenerateCompileInfo.cmake b/cmake/scripts/common/GenerateCompileInfo.cmake new file mode 100644 index 0000000000000..0fe61d5e19fde --- /dev/null +++ b/cmake/scripts/common/GenerateCompileInfo.cmake @@ -0,0 +1,14 @@ +include(${CORE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake) + +core_find_versions() + +# configure_file without dependency tracking +# configure_file would register additional file dependencies that interfere +# with the ones from add_custom_command (and the generation would happen twice) +function(generate_versioned_file _SRC _DEST) + file(READ ${CORE_SOURCE_DIR}/${_SRC} file_content) + string(CONFIGURE "${file_content}" file_content @ONLY) + file(WRITE ${CMAKE_BINARY_DIR}/${_DEST} "${file_content}") +endfunction() + +generate_versioned_file(xbmc/CompileInfo.cpp.in ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp) diff --git a/cmake/scripts/common/GenerateVersionedFiles.cmake b/cmake/scripts/common/GenerateSystemAddons.cmake similarity index 52% rename from cmake/scripts/common/GenerateVersionedFiles.cmake rename to cmake/scripts/common/GenerateSystemAddons.cmake index d54b5242197fa..55472f0af68bb 100644 --- a/cmake/scripts/common/GenerateVersionedFiles.cmake +++ b/cmake/scripts/common/GenerateSystemAddons.cmake @@ -2,15 +2,6 @@ include(${CORE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake) core_find_versions() -# configure_file without dependency tracking -# configure_file would register additional file dependencies that interfere -# with the ones from add_custom_command (and the generation would happen twice) -function(generate_versioned_file _SRC _DEST) - file(READ ${CORE_SOURCE_DIR}/${_SRC} file_content) - string(CONFIGURE "${file_content}" file_content @ONLY) - file(WRITE ${CMAKE_BINARY_DIR}/${_DEST} "${file_content}") -endfunction() - # add-on xml's file(GLOB ADDON_XML_IN_FILE ${CORE_SOURCE_DIR}/addons/*/addon.xml.in) @@ -21,15 +12,13 @@ foreach(loop_var ${ADDON_XML_IN_FILE}) list(GET loop_var 0 xml_name) string(REPLACE "/addon.xml.in" "" source_dir ${xml_name}) - string(REPLACE ${CORE_SOURCE_DIR} ${CMAKE_BINARY_DIR} dest_dir ${source_dir}) - file(MAKE_DIRECTORY ${dest_dir}) + string(REPLACE ${CORE_SOURCE_DIR} ${BUNDLEDIR} dest_dir ${source_dir}) + + file(MAKE_DIRECTORY ${dest_dir}) configure_file(${source_dir}/addon.xml.in ${dest_dir}/addon.xml @ONLY) unset(source_dir) unset(dest_dir) unset(xml_name) endforeach() - - -generate_versioned_file(xbmc/CompileInfo.cpp.in ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp) diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index f0e356ef90194..f30a961efc2f4 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -231,32 +231,42 @@ function(copy_file_to_buildtree file) endif() if(NOT TARGET export-files) + if(${CORE_SYSTEM_NAME} MATCHES "windows") + set(_bundle_dir $) + else() + set(_bundle_dir ${CMAKE_BINARY_DIR}) + endif() file(REMOVE ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake) add_custom_target(export-files ALL COMMENT "Copying files into build tree" - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake) + COMMAND ${CMAKE_COMMAND} -DBUNDLEDIR=${_bundle_dir} + -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake) set_target_properties(export-files PROPERTIES FOLDER "Build Utilities") - file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake "# Export files to build tree\n") endif() - if(NOT file STREQUAL ${CMAKE_BINARY_DIR}/${outfile}) - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Windows" OR NOT IS_SYMLINK "${file}") - if(VERBOSE) - message(STATUS "copy_file_to_buildtree - copying file: ${file} -> ${CMAKE_BINARY_DIR}/${outfile}") - endif() - file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake - "file(COPY \"${file}\" DESTINATION \"${CMAKE_BINARY_DIR}/${outdir}\")\n" ) - else() - if(VERBOSE) - message(STATUS "copy_file_to_buildtree - copying symlinked file: ${file} -> ${CMAKE_BINARY_DIR}/${outfile}") + if(${CORE_SYSTEM_NAME} MATCHES "windows") + file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake + "file(COPY \"${file}\" DESTINATION \"\$\{BUNDLEDIR\}/${outdir}\")\n" ) + else() + if(NOT file STREQUAL ${CMAKE_BINARY_DIR}/${outfile}) + if(NOT IS_SYMLINK "${file}") + if(VERBOSE) + message(STATUS "copy_file_to_buildtree - copying file: ${file} -> ${CMAKE_BINARY_DIR}/${outfile}") + endif() + file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake + "file(COPY \"${file}\" DESTINATION \"${CMAKE_BINARY_DIR}/${outdir}\")\n" ) + else() + if(VERBOSE) + message(STATUS "copy_file_to_buildtree - copying symlinked file: ${file} -> ${CMAKE_BINARY_DIR}/${outfile}") + endif() + file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake + "execute_process(COMMAND \"\${CMAKE_COMMAND}\" -E copy_if_different \"${file}\" \"${CMAKE_BINARY_DIR}/${outfile}\")\n") endif() - file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ExportFiles.cmake - "execute_process(COMMAND \"\${CMAKE_COMMAND}\" -E copy_if_different \"${file}\" \"${CMAKE_BINARY_DIR}/${outfile}\")\n") endif() - endif() - if(NOT arg_NO_INSTALL) - list(APPEND install_data ${outfile}) - set(install_data ${install_data} PARENT_SCOPE) + if(NOT arg_NO_INSTALL) + list(APPEND install_data ${outfile}) + set(install_data ${install_data} PARENT_SCOPE) + endif() endif() endfunction() diff --git a/cmake/scripts/common/ProjectMacros.cmake b/cmake/scripts/common/ProjectMacros.cmake index 3a1910caa6f9c..015cc3db94a76 100644 --- a/cmake/scripts/common/ProjectMacros.cmake +++ b/cmake/scripts/common/ProjectMacros.cmake @@ -8,16 +8,19 @@ # xbt is added to ${XBT_FILES} function(pack_xbt input output) file(GLOB_RECURSE MEDIA_FILES ${input}/*) + get_filename_component(dir ${output} DIRECTORY) - add_custom_command(OUTPUT ${output} - COMMAND ${CMAKE_COMMAND} -E make_directory ${dir} - COMMAND TexturePacker::TexturePacker::Executable - ARGS -input ${input} - -output ${output} - -dupecheck - DEPENDS ${MEDIA_FILES}) - list(APPEND XBT_FILES ${output}) - set(XBT_FILES ${XBT_FILES} PARENT_SCOPE) + if(${CORE_SYSTEM_NAME} MATCHES "windows") + string(REPLACE "${CMAKE_BINARY_DIR}" "\$\{BUNDLEDIR\}" dir ${dir}) + string(REPLACE "${CMAKE_BINARY_DIR}" "\$\{BUNDLEDIR\}" output ${output}) + endif() + + file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake +"execute_process(COMMAND \"${CMAKE_COMMAND}\" -E make_directory ${dir}) +execute_process(COMMAND \$\{TEXTUREPACKER_EXECUTABLE\} -input ${input} -output ${output} -dupecheck)\n") + + list(APPEND XBT_FILES ${output}) + set(XBT_FILES ${XBT_FILES} PARENT_SCOPE) endfunction() # Add a skin to installation list, mirroring it in build tree, packing textures @@ -32,7 +35,7 @@ function(copy_skin_to_buildtree skin) foreach(file ${FILES}) copy_file_to_buildtree(${file}) endforeach() - file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/${dest}/media) + string(REPLACE "${CMAKE_SOURCE_DIR}/" "" dest ${skin}) pack_xbt(${skin}/media ${CMAKE_BINARY_DIR}/${dest}/media/Textures.xbt) diff --git a/cmake/scripts/windowsstore/Install.cmake b/cmake/scripts/windowsstore/Install.cmake index a0522d3a4806f..73b3905225585 100644 --- a/cmake/scripts/windowsstore/Install.cmake +++ b/cmake/scripts/windowsstore/Install.cmake @@ -1,10 +1,24 @@ # Fix UWP addons security issue caused by empty __init__.py Python Lib files packaged with Kodi -set(uwp_pythonlibinit_filepattern "${DEPENDS_PATH}/bin/Python/Lib/__init__.py") -file(GLOB_RECURSE uwp_pythonlibinit_foundfiles "${uwp_pythonlibinit_filepattern}") -foreach(uwp_pythonlibinit_file ${uwp_pythonlibinit_foundfiles}) - file(SIZE "${uwp_pythonlibinit_file}" uwp_pythonlibinit_filesize) - if(${uwp_pythonlibinit_filesize} EQUAL 0) - message("Adding hash comment character in the following empty file: ${uwp_pythonlibinit_file}") - file(APPEND ${uwp_pythonlibinit_file} "#") +# Encapsulate fix script to allow post generation execution in the event the python lib is +# built after project generation. + +file(REMOVE ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedUWPPythonInitFix.cmake) +file(APPEND ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedUWPPythonInitFix.cmake +"set(uwp_pythonlibinit_filepattern \"\$\{DEPENDS_PATH\}/bin/Python/Lib/__init__.py\") +file(GLOB_RECURSE uwp_pythonlibinit_foundfiles \"\$\{uwp_pythonlibinit_filepattern\}\") +foreach(uwp_pythonlibinit_file \$\{uwp_pythonlibinit_foundfiles\}) + file(SIZE \"\$\{uwp_pythonlibinit_file\}\" uwp_pythonlibinit_filesize) + if(\$\{uwp_pythonlibinit_filesize\} EQUAL 0) + message(\"Adding hash comment character in the following empty file: \$\{uwp_pythonlibinit_file\}\") + file(APPEND ${uwp_pythonlibinit_file} \"#\") endif() -endforeach() +endforeach()\n") + +# Change to Python3::Python target when built internal +add_custom_target(generate-UWP-pythonfix + COMMAND ${CMAKE_COMMAND} -DDEPENDS_PATH=${DEPENDS_PATH} + -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedUWPPythonInitFix.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Make sure we apply fix to dependspath before we export-files copy to buildtree +add_dependencies(export-files generate-UWP-pythonfix) diff --git a/cmake/scripts/windowsstore/Macros.cmake b/cmake/scripts/windowsstore/Macros.cmake index 713e8788b0ba8..6c28e38033b9c 100644 --- a/cmake/scripts/windowsstore/Macros.cmake +++ b/cmake/scripts/windowsstore/Macros.cmake @@ -115,9 +115,9 @@ macro(winstore_append_props target) set(DEBUG_DLLS zlibd.dll) foreach(_dll ${DEBUG_DLLS}) if (DEBUG_DLLS_EXCLUDE) - list(APPEND DEBUG_DLLS_EXCLUDE "\;$(BuildRootPath)/dlls/${_dll}") + list(APPEND DEBUG_DLLS_EXCLUDE "\;$(BuildRootPath)/${_dll}") else() - list(APPEND DEBUG_DLLS_EXCLUDE "$(BuildRootPath)/dlls/${_dll}") + list(APPEND DEBUG_DLLS_EXCLUDE "$(BuildRootPath)/${_dll}") endif() string(CONCAT DEBUG_DLLS_LINKAGE_PROPS "${DEBUG_DLLS_LINKAGE_PROPS}" " \n" @@ -127,7 +127,7 @@ macro(winstore_append_props target) " \n") endforeach(_dll DEBUG_DLLS) - add_deployment_content_group($(BuildRootPath)/dlls "" *.dll "${DEBUG_DLLS_EXCLUDE}") + add_deployment_content_group($(BuildRootPath) "" *.dll "${DEBUG_DLLS_EXCLUDE}") add_deployment_content_group($(BuildRootPath)/system system **/* "$(BuildRootPath)/**/shaders/**") add_deployment_content_group($(BuildRootPath)/system/shaders system/shaders **/*.fx "") add_deployment_content_group($(BuildRootPath)/media media **/* "") @@ -158,7 +158,7 @@ macro(winstore_append_props target) " \n" " \n" " ${DEPENDENCIES_DIR_NATIVE}\\bin\n" - " ${CMAKE_CURRENT_BINARY_DIR_NATIVE}\n" + " ${CMAKE_CURRENT_BINARY_DIR_NATIVE}\\$(Configuration)\n" " ${BINARY_ADDONS_DIR_NATIVE}\n" " \n" "${DEBUG_DLLS_LINKAGE_PROPS}" diff --git a/tools/buildsteps/windows/BuildSetup.bat b/tools/buildsteps/windows/BuildSetup.bat index f0a57874c7701..cfb0ea1ec51a8 100644 --- a/tools/buildsteps/windows/BuildSetup.bat +++ b/tools/buildsteps/windows/BuildSetup.bat @@ -132,11 +132,11 @@ set WORKSPACE=%base_dir%\kodi-build.%TARGET_PLATFORM% copy %base_dir%\privacy-policy.txt BUILD_WIN32\application > NUL copy %base_dir%\known_issues.txt BUILD_WIN32\application > NUL - xcopy %WORKSPACE%\addons BUILD_WIN32\application\addons /E /Q /I /Y /EXCLUDE:exclude.txt > NUL - xcopy %WORKSPACE%\*.dll BUILD_WIN32\application /Q /I /Y > NUL - xcopy %WORKSPACE%\libbluray-*.jar BUILD_WIN32\application /Q /I /Y > NUL - xcopy %WORKSPACE%\system BUILD_WIN32\application\system /E /Q /I /Y /EXCLUDE:exclude.txt+exclude_dll.txt > NUL - xcopy %WORKSPACE%\media BUILD_WIN32\application\media /E /Q /I /Y /EXCLUDE:exclude.txt > NUL + xcopy %WORKSPACE%\%buildconfig%\addons BUILD_WIN32\application\addons /E /Q /I /Y /EXCLUDE:exclude.txt > NUL + xcopy %WORKSPACE%\%buildconfig%\*.dll BUILD_WIN32\application /Q /I /Y > NUL + xcopy %WORKSPACE%\%buildconfig%\libbluray-*.jar BUILD_WIN32\application /Q /I /Y > NUL + xcopy %WORKSPACE%\%buildconfig%\system BUILD_WIN32\application\system /E /Q /I /Y /EXCLUDE:exclude.txt+exclude_dll.txt > NUL + xcopy %WORKSPACE%\%buildconfig%\media BUILD_WIN32\application\media /E /Q /I /Y /EXCLUDE:exclude.txt > NUL REM create AppxManifest.xml @PowerShell "(GC .\AppxManifest.xml.in)|%%{$_" ^ diff --git a/xbmc/interfaces/json-rpc/schema/CMakeLists.txt b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt index a4d5583fdbedf..83018bd6dddbf 100644 --- a/xbmc/interfaces/json-rpc/schema/CMakeLists.txt +++ b/xbmc/interfaces/json-rpc/schema/CMakeLists.txt @@ -10,21 +10,30 @@ add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescripti DEPENDS ${JSON_SRCS} COMMENT "Generating ServiceDescription.h") -add_custom_command(OUTPUT ${CMAKE_BINARY_DIR}/addons/xbmc.json/addon.xml +if(${CORE_SYSTEM_NAME} MATCHES "windows") + set(CORE_BINARY_DIR $) +else() + set(CORE_BINARY_DIR ${CMAKE_BINARY_DIR}) +endif() + +add_custom_target(generate_json_header ALL + DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ServiceDescription.h) + +add_custom_command(TARGET generate_json_header + PRE_BUILD COMMAND ${CMAKE_COMMAND} -DCMAKE_SOURCE_DIR=${CMAKE_SOURCE_DIR} - -DCORE_BINARY_DIR=${CMAKE_BINARY_DIR} + -DCORE_BINARY_DIR=${CORE_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") +# We require this for lib target. +add_dependencies(lib${APP_NAME_LC} generate_json_header) + if(BOOTSTRAP_IN_TREE) add_dependencies(generate_json_header JsonSchemaBuilder::JsonSchemaBuilder) endif() From 94ba192e96b010780bf4ff489aa8f64a8b8808eb Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 24 Aug 2023 23:06:50 +0200 Subject: [PATCH 092/811] [video] VIDEO_UTILS: Simplify GetFolderItemResumeInformation implementation. --- xbmc/video/VideoUtils.cpp | 78 +++++++++++++-------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index 808cd61fac9f6..a32c02a073ef6 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -604,54 +604,21 @@ bool IsItemPlayable(const CFileItem& item) namespace { -bool HasInProgressVideo(const std::string& path, CVideoDatabase& db) -{ - //! @todo this function is really very expensive and should be optimized (at db level). - - CFileItemList items; - CUtil::GetRecursiveListing(path, items, {}, XFILE::DIR_FLAG_DEFAULTS); - - if (items.IsEmpty()) - return false; - - for (const auto& item : items) - { - const auto videoTag = item->GetVideoInfoTag(); - if (!item->HasVideoInfoTag()) - continue; - - if (videoTag->GetPlayCount() > 0) - continue; - - // get resume point - CBookmark bookmark(videoTag->GetResumePoint()); - if (!bookmark.IsSet() && db.GetResumeBookMark(videoTag->m_strFileNameAndPath, bookmark)) - videoTag->SetResumePoint(bookmark); - - if (bookmark.IsSet()) - return true; - } - - return false; -} - ResumeInformation GetFolderItemResumeInformation(const CFileItem& item) { if (!item.m_bIsFolder) return {}; - bool hasInProgressVideo = false; - CFileItem folderItem(item); - if ((!folderItem.HasProperty("watchedepisodes") || // season/show - (folderItem.GetProperty("watchedepisodes").asInteger() == 0)) && - (!folderItem.HasProperty("watched") || // movie set - (folderItem.GetProperty("watched").asInteger() == 0))) + if ((!folderItem.HasProperty("inprogressepisodes") || // season/show + (folderItem.GetProperty("inprogressepisodes").asInteger() == 0)) && + (!folderItem.HasProperty("inprogress") || // movie set + (folderItem.GetProperty("inprogress").asInteger() == 0))) { CVideoDatabase db; if (db.Open()) { - if (!folderItem.HasProperty("watchedepisodes") && !folderItem.HasProperty("watched")) + if (!folderItem.HasProperty("inprogressepisodes") && !folderItem.HasProperty("inprogress")) { XFILE::VIDEODATABASEDIRECTORY::CQueryParams params; XFILE::VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(item.GetPath(), params); @@ -681,29 +648,36 @@ ResumeInformation GetFolderItemResumeInformation(const CFileItem& item) db.GetSetInfo(static_cast(params.GetSetId()), details, &folderItem); } } - - // no episodes/movies watched completely, but there could be some or more we have - // started watching - if ((folderItem.HasProperty("watchedepisodes") && // season/show - folderItem.GetProperty("watchedepisodes").asInteger() == 0) || - (folderItem.HasProperty("watched") && // movie set - folderItem.GetProperty("watched").asInteger() == 0)) - hasInProgressVideo = HasInProgressVideo(item.GetPath(), db); - db.Close(); } } - if (hasInProgressVideo || - (folderItem.GetProperty("watchedepisodes").asInteger() > 0 && - folderItem.GetProperty("unwatchedepisodes").asInteger() > 0) || - (folderItem.GetProperty("watched").asInteger() > 0 && - folderItem.GetProperty("unwatched").asInteger() > 0)) + int64_t watched = 0; + int64_t inprogress = 0; + int64_t total = 0; + if (folderItem.HasProperty("inprogressepisodes")) + { + // show/season + watched = folderItem.GetProperty("watchedepisodes").asInteger(); + inprogress = folderItem.GetProperty("inprogressepisodes").asInteger(); + total = folderItem.GetProperty("totalepisodes").asInteger(); + } + else if (folderItem.HasProperty("inprogress")) + { + // movie set + watched = folderItem.GetProperty("watched").asInteger(); + inprogress = folderItem.GetProperty("inprogress").asInteger(); + total = folderItem.GetProperty("total").asInteger(); + } + + if ((total != watched) && (inprogress > 0 || watched != 0)) { ResumeInformation resumeInfo; resumeInfo.isResumable = true; return resumeInfo; } + + CLog::LogF(LOGERROR, "Cannot obtain inprogress state for {}", folderItem.GetPath()); return {}; } From 167f53ff9390381627e2432081d255fcf7a6c2d4 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 11 Aug 2023 09:11:07 +0200 Subject: [PATCH 093/811] [video] Unify code paths for 'show video information' to gain consistent behavior. Optimize and modernize code a bit. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 5 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 200 ++++++++++------------ xbmc/video/windows/GUIWindowVideoBase.h | 6 +- xbmc/video/windows/GUIWindowVideoNav.cpp | 24 +-- xbmc/video/windows/GUIWindowVideoNav.h | 2 - 5 files changed, 91 insertions(+), 146 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 46122ff34bcd0..89cab34d2bca9 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -2406,8 +2406,5 @@ void CGUIDialogVideoInfo::ShowFor(const CFileItem& item) { auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_VIDEO_NAV); if (window) - { - ADDON::ScraperPtr info; - window->OnItemInfo(item, info); - } + window->OnItemInfo(item); } diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 2eead26fa5107..a1643d10b1d8c 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -200,14 +200,60 @@ bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message) return CGUIMediaWindow::OnMessage(message); } -bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper) +bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) { if (fileItem.IsParentFolder() || fileItem.m_bIsShareOrDrive || fileItem.IsPath("add") || (fileItem.IsPlayList() && !URIUtils::HasExtension(fileItem.GetDynPath(), ".strm"))) return false; + // "Videos/Video Add-ons" lists addons in the video window + if (fileItem.HasAddonInfo()) + return CGUIDialogAddonInfo::ShowForItem(std::make_shared(fileItem)); + + // Movie set + if (fileItem.m_bIsFolder && fileItem.IsVideoDb() && + fileItem.GetPath() != "videodb://movies/sets/" && + StringUtils::StartsWith(fileItem.GetPath(), "videodb://movies/sets/")) + return ShowInfo(std::make_shared(fileItem), nullptr); + + // Music video. Match visibility test of CMusicInfo::IsVisible + if (fileItem.IsVideoDb() && fileItem.HasVideoInfoTag() && + (fileItem.HasProperty("artist_musicid") || fileItem.HasProperty("album_musicid"))) + { + CGUIDialogMusicInfo::ShowFor(std::make_shared(fileItem).get()); + return true; + } + + std::string strDir; + if (fileItem.IsVideoDb() && fileItem.HasVideoInfoTag() && + !fileItem.GetVideoInfoTag()->m_strPath.empty()) + strDir = fileItem.GetVideoInfoTag()->m_strPath; + else + strDir = URIUtils::GetDirectory(fileItem.GetPath()); + + m_database.Open(); + + SScanSettings settings; + bool foundDirectly = false; + const ADDON::ScraperPtr scraper = m_database.GetScraperForPath(strDir, settings, foundDirectly); + + if (!scraper && !(fileItem.IsPlugin() || fileItem.IsScript()) && + !(m_database.HasMovieInfo(fileItem.GetDynPath()) || m_database.HasTvShowInfo(strDir) || + m_database.HasEpisodeInfo(fileItem.GetDynPath()))) + { + HELPERS::ShowOKDialogText(CVariant{20176}, // Show video information + CVariant{19055}); // no information available + m_database.Close(); + return false; + } + + m_database.Close(); + + if (scraper && scraper->Content() == CONTENT_TVSHOWS && foundDirectly && + !settings.parent_name_root) // dont lookup on root tvshow folder + return true; + CFileItem item(fileItem); - bool fromDB = false; if ((item.IsVideoDb() && item.HasVideoInfoTag()) || (item.HasVideoInfoTag() && item.GetVideoInfoTag()->m_iDbId != -1)) { @@ -218,38 +264,36 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPt } item.SetProperty("original_listitem_url", item.GetPath()); item.SetPath(item.GetVideoInfoTag()->GetPath()); - fromDB = true; } else { if (item.m_bIsFolder && scraper && scraper->Content() != CONTENT_TVSHOWS) { CFileItemList items; - CDirectory::GetDirectory(item.GetPath(), items, CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), - DIR_FLAG_DEFAULTS); + const std::string fileExts = CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + CDirectory::GetDirectory(item.GetPath(), items, fileExts, DIR_FLAG_DEFAULTS); // Check for cases 1_dir/1_dir/.../file (e.g. by packages where have a extra folder) while (items.Size() == 1 && items[0]->m_bIsFolder && items[0]->GetOpticalMediaPath().empty()) { const std::string path = items[0]->GetPath(); items.Clear(); - CDirectory::GetDirectory(path, items, - CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(), - DIR_FLAG_DEFAULTS); + CDirectory::GetDirectory(path, items, fileExts, DIR_FLAG_DEFAULTS); } items.Stack(); // check for media files bool bFoundFile(false); - for (int i = 0; i < items.Size(); ++i) + const std::vector& excludeFromScan = CServiceBroker::GetSettingsComponent() + ->GetAdvancedSettings() + ->m_moviesExcludeFromScanRegExps; + for (const auto& i : items) { - CFileItemPtr item2 = items[i]; - - if (item2->IsVideo() && !item2->IsPlayList() && - !CUtil::ExcludeFileOrFolder(item2->GetPath(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_moviesExcludeFromScanRegExps)) + if (i->IsVideo() && !i->IsPlayList() && + !CUtil::ExcludeFileOrFolder(i->GetPath(), excludeFromScan)) { - item.SetPath(item2->GetPath()); + item.SetPath(i->GetPath()); item.m_bIsFolder = false; bFoundFile = true; break; @@ -269,18 +313,10 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPt if (fileItem.m_bIsFolder) item.SetProperty("set_folder_thumb", fileItem.GetPath()); - bool modified = ShowIMDB(CFileItemPtr(new CFileItem(item)), scraper, fromDB); - if (modified && - (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_NAV)) // since we can be called from the music library we need this check - { - int itemNumber = m_viewControl.GetSelectedItem(); - Refresh(); - m_viewControl.SetSelectedItem(itemNumber); - } - return true; + return ShowInfo(std::make_shared(fileItem), scraper); } -// ShowIMDB is called as follows: +// ShowInfo is called as follows: // 1. To lookup info on a file. // 2. To lookup info on a folder (which may or may not contain a file) // 3. To lookup info just for fun (no file or folder related) @@ -301,25 +337,14 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPt // and show the information. // 6. Check for a refresh, and if so, go to 3. -bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, bool fromDB) +bool CGUIWindowVideoBase::ShowInfo(const CFileItemPtr& item2, const ScraperPtr& info2) { - /* - CLog::Log(LOGDEBUG,"CGUIWindowVideoBase::ShowIMDB"); - CLog::Log(LOGDEBUG," strMovie = [{}]", strMovie); - CLog::Log(LOGDEBUG," strFile = [{}]", strFile); - CLog::Log(LOGDEBUG," strFolder = [{}]", strFolder); - CLog::Log(LOGDEBUG," bFolder = [{}]", ((int)bFolder ? "true" : "false")); - */ - - CGUIDialogProgress* pDlgProgress = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_PROGRESS); - CGUIDialogSelect* pDlgSelect = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); CGUIDialogVideoInfo* pDlgInfo = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_VIDEO_INFO); + if (!pDlgInfo) + return false; const ScraperPtr& info(info2); // use this as nfo might change it.. - - if (!pDlgProgress) return false; - if (!pDlgSelect) return false; - if (!pDlgInfo) return false; + CFileItemPtr item(item2); // we might replace item.. // 1. Check for already downloaded information, and if we have it, display our dialog // Return if no Refresh is needed. @@ -451,7 +476,14 @@ bool CGUIWindowVideoBase::ShowIMDB(CFileItemPtr item, const ScraperPtr &info2, b listNeedsUpdating = true; } while (needsRefresh); - return listNeedsUpdating; + if (listNeedsUpdating && + IsActive()) // since we can be called from other windows (music, home) we need this check + { + int itemNumber = m_viewControl.GetSelectedItem(); + Refresh(); + m_viewControl.SetSelectedItem(itemNumber); + } + return true; } void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first) @@ -583,59 +615,7 @@ bool CGUIWindowVideoBase::OnItemInfo(int iItem) if (iItem < 0 || iItem >= m_vecItems->Size()) return false; - CFileItemPtr item = m_vecItems->Get(iItem); - - if (item->IsPath("add") || item->IsParentFolder() || - (item->IsPlayList() && !URIUtils::HasExtension(item->GetDynPath(), ".strm"))) - return false; - - if (!m_vecItems->IsPlugin() && (item->IsPlugin() || item->IsScript())) - return CGUIDialogAddonInfo::ShowForItem(item); - - if (item->m_bIsFolder && - item->IsVideoDb() && - StringUtils::StartsWith(item->GetPath(), "videodb://movies/sets/")) - return ShowIMDB(item, nullptr, true); - - ADDON::ScraperPtr scraper; - - // Match visibility test of CMusicInfo::IsVisible - if (item->IsVideoDb() && item->HasVideoInfoTag() && - (item->HasProperty("artist_musicid") || item->HasProperty("album_musicid"))) - { - CGUIDialogMusicInfo::ShowFor(item.get()); - return true; - } - if (!m_vecItems->IsPlugin() && !m_vecItems->IsRSS() && !m_vecItems->IsLiveTV()) - { - std::string strDir; - if (item->IsVideoDb() && - item->HasVideoInfoTag() && - !item->GetVideoInfoTag()->m_strPath.empty()) - { - strDir = item->GetVideoInfoTag()->m_strPath; - } - else - strDir = URIUtils::GetDirectory(item->GetPath()); - - SScanSettings settings; - bool foundDirectly = false; - scraper = m_database.GetScraperForPath(strDir, settings, foundDirectly); - - if (!scraper && - !(m_database.HasMovieInfo(item->GetDynPath()) || m_database.HasTvShowInfo(strDir) || - m_database.HasEpisodeInfo(item->GetDynPath()))) - { - HELPERS::ShowOKDialogText(CVariant{20176}, // Show video information - CVariant{19055}); // no information available - return false; - } - - if (scraper && scraper->Content() == CONTENT_TVSHOWS && foundDirectly && !settings.parent_name_root) // dont lookup on root tvshow folder - return true; - } - - return OnItemInfo(*item, scraper); + return OnItemInfo(*m_vecItems->Get(iItem)); } void CGUIWindowVideoBase::OnRestartItem(int iItem, const std::string &player) @@ -1022,31 +1002,23 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; case CONTEXT_BUTTON_SCAN: - { - if( !item) - return false; - ADDON::ScraperPtr info; - SScanSettings settings; - GetScraperForItem(item.get(), info, settings); - std::string strPath = item->GetPath(); - if (item->IsVideoDb() && (!item->m_bIsFolder || item->GetVideoInfoTag()->m_strPath.empty())) - return false; + if (!item) + return false; - if (item->IsVideoDb()) - strPath = item->GetVideoInfoTag()->m_strPath; + if (item->IsVideoDb() && (!item->m_bIsFolder || item->GetVideoInfoTag()->m_strPath.empty())) + return false; - if (!info || info->Content() == CONTENT_NONE) - return false; + if (item->m_bIsFolder) + { + const std::string strPath = + item->IsVideoDb() ? item->GetVideoInfoTag()->m_strPath : item->GetPath(); + OnScan(strPath, true); + } + else + OnItemInfo(*item); - if (item->m_bIsFolder) - { - OnScan(strPath, true); - } - else - OnItemInfo(*item, info); + return true; - return true; - } case CONTEXT_BUTTON_DELETE: OnDeleteItem(itemNumber); return true; diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index 9ab51ab94f530..afb18012363e0 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -37,11 +37,9 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse Default implementation shows a dialog containing information for the movie/episode/... represented by the file item. \param fileItem the item for which information is to be presented. - \param scraper a scraper addon instance that can be used to obtain additional information for - the given item \return true if information was presented, false otherwise. */ - virtual bool OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper); + bool OnItemInfo(const CFileItem& fileItem); /*! \brief Show the resume menu for this item (if it has a resume bookmark) If a resume bookmark is found, we set the item's m_lStartOffset to STARTOFFSET_RESUME. @@ -126,7 +124,7 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse using CGUIMediaWindow::LoadPlayList; void LoadPlayList(const std::string& strPlayList, PLAYLIST::Id playlistId = PLAYLIST::TYPE_VIDEO); - bool ShowIMDB(CFileItemPtr item, const ADDON::ScraperPtr& content, bool fromDB); + bool ShowInfo(const CFileItemPtr& item, const ADDON::ScraperPtr& content); void OnSearch(); void OnSearchItemFound(const CFileItem* pSelItem); diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index 4b579836860d6..07f23e8a5ce63 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -138,8 +138,7 @@ bool CGUIWindowVideoNav::OnMessage(CGUIMessage& message) i = -1; if (url.GetOption("showinfo") == "true") { - ADDON::ScraperPtr scrapper; - OnItemInfo(*pItem, scrapper); + OnItemInfo(*pItem); } break; } @@ -159,8 +158,7 @@ bool CGUIWindowVideoNav::OnMessage(CGUIMessage& message) if (!item.GetVideoInfoTag()->IsEmpty()) { item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath); - ADDON::ScraperPtr scrapper; - OnItemInfo(item, scrapper); + OnItemInfo(item); } } } @@ -652,24 +650,6 @@ void CGUIWindowVideoNav::PlayItem(int iItem) CGUIWindowVideoBase::PlayItem(iItem); } -bool CGUIWindowVideoNav::OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& scraper) -{ - if (!scraper || scraper->Content() == CONTENT_NONE) - { - m_database.Open(); // since we can be called from the music library without being inited - if (fileItem.IsVideoDb()) - scraper = m_database.GetScraperForPath(fileItem.GetVideoInfoTag()->m_strPath); - else - { - std::string strPath,strFile; - URIUtils::Split(fileItem.GetPath(),strPath,strFile); - scraper = m_database.GetScraperForPath(strPath); - } - m_database.Close(); - } - return CGUIWindowVideoBase::OnItemInfo(fileItem, scraper); -} - void CGUIWindowVideoNav::OnDeleteItem(const CFileItemPtr& pItem) { if (m_vecItems->IsParentFolder()) diff --git a/xbmc/video/windows/GUIWindowVideoNav.h b/xbmc/video/windows/GUIWindowVideoNav.h index 82daf00529c0c..977e7f866c1b1 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.h +++ b/xbmc/video/windows/GUIWindowVideoNav.h @@ -37,8 +37,6 @@ class CGUIWindowVideoNav : public CGUIWindowVideoBase bool OnAction(const CAction &action) override; bool OnMessage(CGUIMessage& message) override; - bool OnItemInfo(const CFileItem& fileItem, ADDON::ScraperPtr& info) override; - protected: bool ApplyWatchedFilter(CFileItemList &items); bool GetFilteredItems(const std::string &filter, CFileItemList &items) override; From 519a0a63c93fb5d6c48a85b589cf8f286b0adb0d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 19 Aug 2023 11:55:38 +0200 Subject: [PATCH 094/811] [video] Unify code paths for 'choose art' to gain consistent behavior. Optimize and modernize code a bit. --- xbmc/dialogs/GUIDialogContextMenu.h | 149 +++++++------- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 224 +++++++--------------- xbmc/video/dialogs/GUIDialogVideoInfo.h | 3 + 3 files changed, 152 insertions(+), 224 deletions(-) diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h index 420e458b82a0f..3a3cdf9f6c431 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.h +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -17,80 +17,81 @@ class CMediaSource; -enum CONTEXT_BUTTON { CONTEXT_BUTTON_CANCELLED = 0, - CONTEXT_BUTTON_RENAME, - CONTEXT_BUTTON_DELETE, - CONTEXT_BUTTON_MOVE, - CONTEXT_BUTTON_SETTINGS, - CONTEXT_BUTTON_RIP_CD, - CONTEXT_BUTTON_CANCEL_RIP_CD, - CONTEXT_BUTTON_RIP_TRACK, - CONTEXT_BUTTON_EJECT_DRIVE, - CONTEXT_BUTTON_EDIT_SOURCE, - CONTEXT_BUTTON_REMOVE_SOURCE, - CONTEXT_BUTTON_SET_DEFAULT, - CONTEXT_BUTTON_CLEAR_DEFAULT, - CONTEXT_BUTTON_SET_THUMB, - CONTEXT_BUTTON_ADD_LOCK, - CONTEXT_BUTTON_REMOVE_LOCK, - CONTEXT_BUTTON_CHANGE_LOCK, - CONTEXT_BUTTON_RESET_LOCK, - CONTEXT_BUTTON_REACTIVATE_LOCK, - CONTEXT_BUTTON_VIEW_SLIDESHOW, - CONTEXT_BUTTON_RECURSIVE_SLIDESHOW, - CONTEXT_BUTTON_REFRESH_THUMBS, - CONTEXT_BUTTON_SWITCH_MEDIA, - CONTEXT_BUTTON_MOVE_ITEM, - CONTEXT_BUTTON_MOVE_HERE, - CONTEXT_BUTTON_CANCEL_MOVE, - CONTEXT_BUTTON_MOVE_ITEM_UP, - CONTEXT_BUTTON_MOVE_ITEM_DOWN, - CONTEXT_BUTTON_CLEAR, - CONTEXT_BUTTON_QUEUE_ITEM, - CONTEXT_BUTTON_PLAY_ITEM, - CONTEXT_BUTTON_PLAY_WITH, - CONTEXT_BUTTON_PLAY_PARTYMODE, - CONTEXT_BUTTON_PLAY_PART, - CONTEXT_BUTTON_RESUME_ITEM, - CONTEXT_BUTTON_EDIT, - CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, - CONTEXT_BUTTON_INFO, - CONTEXT_BUTTON_INFO_ALL, - CONTEXT_BUTTON_CDDB, - CONTEXT_BUTTON_SCAN, - CONTEXT_BUTTON_SCAN_TO_LIBRARY, - CONTEXT_BUTTON_SET_ARTIST_THUMB, - CONTEXT_BUTTON_SET_SEASON_ART, - CONTEXT_BUTTON_CANCEL_PARTYMODE, - CONTEXT_BUTTON_MARK_WATCHED, - CONTEXT_BUTTON_MARK_UNWATCHED, - CONTEXT_BUTTON_SET_CONTENT, - CONTEXT_BUTTON_EDIT_PARTYMODE, - CONTEXT_BUTTON_LINK_MOVIE, - CONTEXT_BUTTON_UNLINK_MOVIE, - CONTEXT_BUTTON_GO_TO_ARTIST, - CONTEXT_BUTTON_GO_TO_ALBUM, - CONTEXT_BUTTON_PLAY_OTHER, - CONTEXT_BUTTON_SET_ACTOR_THUMB, - CONTEXT_BUTTON_UNLINK_BOOKMARK, - CONTEXT_BUTTON_ACTIVATE, - CONTEXT_BUTTON_GROUP_MANAGER, - CONTEXT_BUTTON_CHANNEL_MANAGER, - CONTEXT_BUTTON_SET_MOVIESET_ART, - CONTEXT_BUTTON_PLAY_AND_QUEUE, - CONTEXT_BUTTON_PLAY_ONLY_THIS, - CONTEXT_BUTTON_UPDATE_EPG, - CONTEXT_BUTTON_TAGS_ADD_ITEMS, - CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, - CONTEXT_BUTTON_SET_MOVIESET, - CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, - CONTEXT_BUTTON_BROWSE_INTO, - CONTEXT_BUTTON_EDIT_SORTTITLE, - CONTEXT_BUTTON_DELETE_ALL, - CONTEXT_BUTTON_HELP, - CONTEXT_BUTTON_PLAY_NEXT, - CONTEXT_BUTTON_NAVIGATE, - }; +enum CONTEXT_BUTTON +{ + CONTEXT_BUTTON_CANCELLED = 0, + CONTEXT_BUTTON_RENAME, + CONTEXT_BUTTON_DELETE, + CONTEXT_BUTTON_MOVE, + CONTEXT_BUTTON_SETTINGS, + CONTEXT_BUTTON_RIP_CD, + CONTEXT_BUTTON_CANCEL_RIP_CD, + CONTEXT_BUTTON_RIP_TRACK, + CONTEXT_BUTTON_EJECT_DRIVE, + CONTEXT_BUTTON_EDIT_SOURCE, + CONTEXT_BUTTON_REMOVE_SOURCE, + CONTEXT_BUTTON_SET_DEFAULT, + CONTEXT_BUTTON_CLEAR_DEFAULT, + CONTEXT_BUTTON_SET_THUMB, + CONTEXT_BUTTON_ADD_LOCK, + CONTEXT_BUTTON_REMOVE_LOCK, + CONTEXT_BUTTON_CHANGE_LOCK, + CONTEXT_BUTTON_RESET_LOCK, + CONTEXT_BUTTON_REACTIVATE_LOCK, + CONTEXT_BUTTON_VIEW_SLIDESHOW, + CONTEXT_BUTTON_RECURSIVE_SLIDESHOW, + CONTEXT_BUTTON_REFRESH_THUMBS, + CONTEXT_BUTTON_SWITCH_MEDIA, + CONTEXT_BUTTON_MOVE_ITEM, + CONTEXT_BUTTON_MOVE_HERE, + CONTEXT_BUTTON_CANCEL_MOVE, + CONTEXT_BUTTON_MOVE_ITEM_UP, + CONTEXT_BUTTON_MOVE_ITEM_DOWN, + CONTEXT_BUTTON_CLEAR, + CONTEXT_BUTTON_QUEUE_ITEM, + CONTEXT_BUTTON_PLAY_ITEM, + CONTEXT_BUTTON_PLAY_WITH, + CONTEXT_BUTTON_PLAY_PARTYMODE, + CONTEXT_BUTTON_PLAY_PART, + CONTEXT_BUTTON_RESUME_ITEM, + CONTEXT_BUTTON_EDIT, + CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, + CONTEXT_BUTTON_INFO, + CONTEXT_BUTTON_INFO_ALL, + CONTEXT_BUTTON_CDDB, + CONTEXT_BUTTON_SCAN, + CONTEXT_BUTTON_SCAN_TO_LIBRARY, + CONTEXT_BUTTON_SET_ARTIST_THUMB, + CONTEXT_BUTTON_SET_ART, + CONTEXT_BUTTON_CANCEL_PARTYMODE, + CONTEXT_BUTTON_MARK_WATCHED, + CONTEXT_BUTTON_MARK_UNWATCHED, + CONTEXT_BUTTON_SET_CONTENT, + CONTEXT_BUTTON_EDIT_PARTYMODE, + CONTEXT_BUTTON_LINK_MOVIE, + CONTEXT_BUTTON_UNLINK_MOVIE, + CONTEXT_BUTTON_GO_TO_ARTIST, + CONTEXT_BUTTON_GO_TO_ALBUM, + CONTEXT_BUTTON_PLAY_OTHER, + CONTEXT_BUTTON_SET_ACTOR_THUMB, + CONTEXT_BUTTON_UNLINK_BOOKMARK, + CONTEXT_BUTTON_ACTIVATE, + CONTEXT_BUTTON_GROUP_MANAGER, + CONTEXT_BUTTON_CHANNEL_MANAGER, + CONTEXT_BUTTON_PLAY_AND_QUEUE, + CONTEXT_BUTTON_PLAY_ONLY_THIS, + CONTEXT_BUTTON_UPDATE_EPG, + CONTEXT_BUTTON_TAGS_ADD_ITEMS, + CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, + CONTEXT_BUTTON_SET_MOVIESET, + CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, + CONTEXT_BUTTON_BROWSE_INTO, + CONTEXT_BUTTON_EDIT_SORTTITLE, + CONTEXT_BUTTON_DELETE_ALL, + CONTEXT_BUTTON_HELP, + CONTEXT_BUTTON_PLAY_NEXT, + CONTEXT_BUTTON_NAVIGATE, +}; class CContextButtons : public std::vector< std::pair > { diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 46122ff34bcd0..bb6642ae05c04 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -879,145 +879,17 @@ std::string CGUIDialogVideoInfo::ChooseArtType(const CFileItem &videoItem) void CGUIDialogVideoInfo::OnGetArt() { - if (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeVideoCollection) + bool finished = false; + while (!finished) { - ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type); - return; - } - std::string type = ChooseArtType(*m_movieItem); - if (type.empty()) - return; // cancelled - - //! @todo this can be removed once these are unified. - if (type == "fanart") - OnGetFanart(); - else - { - CFileItemList items; - - // Current thumb - if (m_movieItem->HasArt(type)) - { - CFileItemPtr item(new CFileItem("thumb://Current", false)); - item->SetArt("thumb", m_movieItem->GetArt(type)); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13512)); - items.Add(item); - } - else if ((type == "poster" || type == "banner") && m_movieItem->HasArt("thumb")) - { // add the 'thumb' type in - CFileItemPtr item(new CFileItem("thumb://Thumb", false)); - item->SetArt("thumb", m_movieItem->GetArt("thumb")); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13512)); - items.Add(item); - } - - std::string embeddedArt; - if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) - { - CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false); - CVideoTagLoaderFFmpeg loader(item, nullptr, false); - CVideoInfoTag tag; - loader.Load(tag, false, nullptr); - for (const auto& it : tag.m_coverArt) - { - if (it.m_type == type) - { - CFileItemPtr itemF(new CFileItem("thumb://Embedded", false)); - embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_" + type); - itemF->SetArt("thumb", embeddedArt); - itemF->SetLabel(g_localizeStrings.Get(13519)); - items.Add(itemF); - } - } - } - - // Grab the thumbnails from the web - m_movieItem->GetVideoInfoTag()->m_strPictureURL.Parse(); - std::vector thumbs; - int season = (m_movieItem->GetVideoInfoTag()->m_type == MediaTypeSeason) ? m_movieItem->GetVideoInfoTag()->m_iSeason : -1; - m_movieItem->GetVideoInfoTag()->m_strPictureURL.GetThumbUrls(thumbs, type, season); - - for (unsigned int i = 0; i < thumbs.size(); ++i) + if (ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type, finished)) { - std::string strItemPath = StringUtils::Format("thumb://Remote{}", i); - CFileItemPtr item(new CFileItem(strItemPath, false)); - item->SetArt("thumb", thumbs[i]); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13513)); - - //! @todo Do we need to clear the cached image? - // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); - items.Add(item); - } - - std::string localThumb = CVideoThumbLoader::GetLocalArt(*m_movieItem, type); - if (!localThumb.empty()) - { - CFileItemPtr item(new CFileItem("thumb://Local", false)); - item->SetArt("thumb", localThumb); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13514)); - items.Add(item); - } - else - { // no local thumb exists, so we are just using the IMDb thumb or cached thumb - // which is probably the IMDb thumb. These could be wrong, so allow the user - // to delete the incorrect thumb - CFileItemPtr item(new CFileItem("thumb://None", false)); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13515)); - items.Add(item); - } - - std::string result; - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); - AddItemPathToFileBrowserSources(sources, *m_movieItem); - CServiceBroker::GetMediaManager().GetLocalDrives(sources); - if (CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result) && - result != "thumb://Current") // user didn't choose the one they have - { - std::string newThumb; - if (StringUtils::StartsWith(result, "thumb://Remote")) - { - int number = atoi(result.substr(14).c_str()); - newThumb = thumbs[number]; - } - else if (result == "thumb://Thumb") - newThumb = m_movieItem->GetArt("thumb"); - else if (result == "thumb://Local") - newThumb = localThumb; - else if (result == "thumb://Embedded") - newThumb = embeddedArt; - else if (CFileUtils::Exists(result)) - newThumb = result; - else // none - newThumb.clear(); - - // update thumb in the database - CVideoDatabase db; - if (db.Open()) - { - db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, type, newThumb); - db.Close(); - } - CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show - m_movieItem->SetArt(type, newThumb); - if (m_movieItem->HasProperty("set_folder_thumb")) - { // have a folder thumb to set as well - VIDEO::CVideoInfoScanner::ApplyThumbToFolder(m_movieItem->GetProperty("set_folder_thumb").asString(), newThumb); - } m_hasUpdatedThumb = true; - } - } - - // Update our screen - Update(); - // re-open the art selection dialog as we come back from - // the image selection dialog - OnGetArt(); + // Update our screen + Update(); + } + }; } // Allow user to select a Fanart @@ -1269,7 +1141,7 @@ int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) type == MediaTypeMusicVideo) buttons.Add(CONTEXT_BUTTON_EDIT, 16105); - if (type == MediaTypeMovie || type == MediaTypeTvShow) + if (type == MediaTypeMovie || type == MediaTypeTvShow || type == MediaTypeSeason) buttons.Add(CONTEXT_BUTTON_EDIT_SORTTITLE, 16107); if (type == MediaTypeMovie) @@ -1290,17 +1162,16 @@ int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) item->GetVideoInfoTag()->m_iBookmarkId > 0) buttons.Add(CONTEXT_BUTTON_UNLINK_BOOKMARK, 20405); + if (type == MediaTypeVideoCollection || type == MediaTypeMovie || type == MediaTypeTvShow || + type == MediaTypeSeason || type == MediaTypeEpisode) + buttons.Add(CONTEXT_BUTTON_SET_ART, 13511); + // movie sets if (item->m_bIsFolder && type == MediaTypeVideoCollection) { - buttons.Add(CONTEXT_BUTTON_SET_MOVIESET_ART, 13511); buttons.Add(CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, 20465); } - // seasons - if (item->m_bIsFolder && type == MediaTypeSeason) - buttons.Add(CONTEXT_BUTTON_SET_SEASON_ART, 13511); - // tags if (item->m_bIsFolder && type == "tag") { @@ -1365,8 +1236,7 @@ int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) result = DeleteVideoItem(item); break; - case CONTEXT_BUTTON_SET_MOVIESET_ART: - case CONTEXT_BUTTON_SET_SEASON_ART: + case CONTEXT_BUTTON_SET_ART: result = ManageVideoItemArtwork(item, type); break; @@ -2012,12 +1882,29 @@ std::string FindLocalMovieSetArtworkFile(const CFileItemPtr& item, const std::st bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, const MediaType& type) { + bool result = false; + + bool finished = false; + while (!finished) + { + result = ManageVideoItemArtwork(item, type, finished); + } + + return result; +} + +bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, + const MediaType& type, + bool& finished) +{ + finished = true; + if (item == nullptr || !item->HasVideoInfoTag() || type.empty()) return false; CVideoDatabase videodb; if (!videodb.Open()) - return true; + return false; // Grab the thumbnails from the web CFileItemList items; @@ -2050,13 +1937,17 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); else - { // SEASON, SET + { // SEASON, SHOW, SET artType = ChooseArtType(*item); if (artType.empty()) return false; if (artType == "fanart" && type != MediaTypeVideoCollection) - return OnGetFanart(item); + { + const bool result = OnGetFanart(item); + finished = false; + return result; + } if (item->HasArt(artType)) currentThumb = item->GetArt(artType); @@ -2074,6 +1965,26 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt("icon", "DefaultFolder.png"); noneitem->SetLabel(g_localizeStrings.Get(13515)); + std::string embeddedArt; + if (URIUtils::HasExtension(item->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) + { + CFileItem itemCopy(item->GetVideoInfoTag()->m_strFileNameAndPath, false); + CVideoTagLoaderFFmpeg loader(itemCopy, nullptr, false); + CVideoInfoTag tag; + loader.Load(tag, false, nullptr); + for (const auto& it : tag.m_coverArt) + { + if (it.m_type == type) + { + CFileItemPtr itemF(new CFileItem("thumb://Embedded", false)); + embeddedArt = CTextureUtils::GetWrappedImageURL(itemCopy.GetPath(), "video_" + type); + itemF->SetArt("thumb", embeddedArt); + itemF->SetLabel(g_localizeStrings.Get(13519)); + items.Add(itemF); + } + } + } + bool local = false; std::vector thumbs; if (type != MediaTypeArtist) @@ -2197,12 +2108,19 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType, result); + } else { CMusicDatabase musicdb; @@ -2227,11 +2144,18 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt(artType, result); + if (item->HasProperty("set_folder_thumb")) + { + // have a folder thumb to set as well + VIDEO::CVideoInfoScanner::ApplyThumbToFolder(item->GetProperty("set_folder_thumb").asString(), + result); + } CUtil::DeleteVideoDatabaseDirectoryCache(); CGUIMessage msg(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_REFRESH_THUMBS); CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + finished = false; return true; } diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.h b/xbmc/video/dialogs/GUIDialogVideoInfo.h index ac19e98abe446..ef57ff68c37b4 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.h +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.h @@ -112,4 +112,7 @@ class CGUIDialogVideoInfo : private: static std::string ChooseArtType(const CFileItem& item); + static bool ManageVideoItemArtwork(const std::shared_ptr& item, + const MediaType& type, + bool& finished); }; From 5c5f098b28b14e5f9661cafcd3e5d33c9cf1b1c9 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 27 Aug 2023 19:49:23 +0100 Subject: [PATCH 095/811] [macos] Return 'not available' instead of 0.0 for cpu/gpu temps --- xbmc/guilib/guiinfo/SystemGUIInfo.cpp | 7 ++++++- xbmc/platform/darwin/osx/CPUInfoOsx.cpp | 4 ++++ xbmc/utils/test/TestCPUInfo.cpp | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp index 8004405d981d7..898a0e46fbaaf 100644 --- a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp @@ -96,7 +96,12 @@ CTemperature CSystemGUIInfo::GetGPUTemperature() const #if defined(TARGET_DARWIN_OSX) value = SMCGetTemperature(SMC_KEY_GPU_TEMP); - return CTemperature::CreateFromCelsius(value); + auto temperature = CTemperature::CreateFromCelsius(value); + if (temperature == CTemperature::CreateFromCelsius(0.0)) + { + temperature.SetValid(false); + } + return temperature; #elif defined(TARGET_WINDOWS_STORE) return CTemperature::CreateFromCelsius(0); #else diff --git a/xbmc/platform/darwin/osx/CPUInfoOsx.cpp b/xbmc/platform/darwin/osx/CPUInfoOsx.cpp index 0a9f827280ae4..c100d3aac965b 100644 --- a/xbmc/platform/darwin/osx/CPUInfoOsx.cpp +++ b/xbmc/platform/darwin/osx/CPUInfoOsx.cpp @@ -121,6 +121,10 @@ bool CCPUInfoOsx::GetTemperature(CTemperature& temperature) int value = SMCGetTemperature(SMC_KEY_CPU_TEMP); temperature = CTemperature::CreateFromCelsius(value); + if (temperature == CTemperature::CreateFromCelsius(0.0)) + { + temperature.SetValid(false); + } return true; } diff --git a/xbmc/utils/test/TestCPUInfo.cpp b/xbmc/utils/test/TestCPUInfo.cpp index bd9572ac2c06b..b8ca20e70e756 100644 --- a/xbmc/utils/test/TestCPUInfo.cpp +++ b/xbmc/utils/test/TestCPUInfo.cpp @@ -41,7 +41,7 @@ TEST_F(TestCPUInfo, GetCPUFrequency) EXPECT_GE(CServiceBroker::GetCPUInfo()->GetCPUFrequency(), 0.f); } -#if defined(TARGET_WINDOWS) +#if defined(TARGET_WINDOWS) or defined(TARGET_DARWIN_OSX) TEST_F(TestCPUInfo, DISABLED_GetTemperature) #else TEST_F(TestCPUInfo, GetTemperature) From 4b329a9a87bc734c996a07688884d1218cae48a5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 27 Aug 2023 23:49:14 +0200 Subject: [PATCH 096/811] [Estuary] View_501_Banner.xml: Fixed syntax error (unmatched parentheses). --- addons/skin.estuary/xml/View_501_Banner.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/skin.estuary/xml/View_501_Banner.xml b/addons/skin.estuary/xml/View_501_Banner.xml index 0340525520af3..07aa554495aa5 100644 --- a/addons/skin.estuary/xml/View_501_Banner.xml +++ b/addons/skin.estuary/xml/View_501_Banner.xml @@ -142,7 +142,7 @@ 22 22 lists/played-total.png - (String.IsEqual(Listitem.dbtype,tvshow) | String.IsEqual(Listitem.dbtype,season)) + !String.IsEmpty(ListItem.Property(TotalEpisodes)) + [String.IsEqual(Listitem.dbtype,tvshow) | String.IsEqual(Listitem.dbtype,season)] + !String.IsEmpty(ListItem.Property(TotalEpisodes)) 103 From bba1d29f0044950bfbbe4c5cf0cd8ad7ecf05f4c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 21 Aug 2023 22:24:02 +0200 Subject: [PATCH 097/811] [listproviders][Estuary] Add special 'more...' item to size limited lists in case there are more items available. --- addons/skin.estuary/xml/Home.xml | 6 +- addons/skin.estuary/xml/Includes_Home.xml | 81 ++++++++++++++++------- addons/skin.estuary/xml/Variables.xml | 9 +++ xbmc/listproviders/DirectoryProvider.cpp | 45 ++++++++++--- 4 files changed, 106 insertions(+), 35 deletions(-) diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index 9a27af758a787..cf896750007a5 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -365,7 +365,7 @@ - + @@ -398,6 +398,7 @@ + @@ -407,6 +408,7 @@ + @@ -468,6 +470,7 @@ + @@ -477,6 +480,7 @@ + diff --git a/addons/skin.estuary/xml/Includes_Home.xml b/addons/skin.estuary/xml/Includes_Home.xml index bfcae455146c9..f608f49bfe2e9 100644 --- a/addons/skin.estuary/xml/Includes_Home.xml +++ b/addons/skin.estuary/xml/Includes_Home.xml @@ -449,23 +449,38 @@ $PARAM[icon] keep + + !String.IsEqual(ListItem.Property(node.type),target_folder) + + 42 + 247 + 245 + 70 + + font12 + text_shadow + center + top + + + 42 + 277 + 245 + 65 + + font12 + text_shadow + center + top + + 42 247 245 70 - - font12 - text_shadow - center - top - - - 42 - 277 - 245 - 65 - + String.IsEqual(ListItem.Property(node.type),target_folder) + font12 text_shadow center @@ -532,24 +547,40 @@ $PARAM[icon] keep + + !String.IsEqual(ListItem.Property(node.type),target_folder) + + 42 + 247 + 245 + 70 + + font12 + text_shadow + center + true + top + + + 42 + 277 + 245 + 65 + + font12 + text_shadow + center + true + top + + 42 247 245 70 - - font12 - text_shadow - center - true - top - - - 42 - 277 - 245 - 65 - + String.IsEqual(ListItem.Property(node.type),target_folder) + font12 text_shadow center diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 7541e834af212..7acf83468f22d 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -159,13 +159,19 @@ $INFO[MusicPlayer.DiscNumber]$INFO[MusicPlayer.DiscTitle, - ] + $INFO[ListItem.Icon] $INFO[ListItem.Label,resource://resource.images.moviegenreicons.transparent/,.png] DefaultGenre.png + $INFO[ListItem.Icon] $INFO[ListItem.Label,resource://resource.images.studios.white/,.png] DefaultStudios.png + + $INFO[ListItem.Icon] + DefaultPlaylist.png + $INFO[ListItem.Property(addon.status)] $INFO[ListItem.Label2] @@ -190,6 +196,7 @@ $LOCALIZE[37015] + ActivateWindow(music,$INFO[ListItem.FilenameAndPath],return) ActivateWindow(music,musicdb://albums/$INFO[ListItem.DBID]/,return) PlayMedia(musicdb://albums/$INFO[ListItem.DBID]/) QueueMedia(musicdb://albums/$INFO[ListItem.DBID]/,playnext) @@ -205,6 +212,7 @@ $LOCALIZE[37015] + ActivateWindow(videos,$INFO[ListItem.FilenameAndPath],return) ActivateWindow(videos,videodb://tvshows/titles/$INFO[ListItem.DBID]/,return) PlayMedia(videodb://tvshows/titles/$INFO[ListItem.DBID]/,resume) PlayMedia(videodb://tvshows/titles/$INFO[ListItem.DBID]/,noresume) @@ -221,6 +229,7 @@ $LOCALIZE[37015] + ActivateWindow(videos,$INFO[ListItem.FilenameAndPath],return) ActivateWindow(videos,videodb://movies/sets/$INFO[ListItem.DBID]/,return) PlayMedia(videodb://movies/sets/$INFO[ListItem.DBID]/,resume) PlayMedia(videodb://movies/sets/$INFO[ListItem.DBID]/,noresume) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 0d9872e823e6a..76135d4ac7452 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -18,6 +18,7 @@ #include "filesystem/Directory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" #include "interfaces/AnnouncementManager.h" #include "music/MusicThumbLoader.h" #include "music/dialogs/GUIDialogMusicInfo.h" @@ -50,11 +51,12 @@ using namespace PVR; class CDirectoryJob : public CJob { public: - CDirectoryJob(const std::string &url, SortDescription sort, int limit, int parentID) - : m_url(url), - m_sort(sort), - m_limit(limit), - m_parentID(parentID) + CDirectoryJob(const std::string& url, + const std::string& target, + SortDescription sort, + int limit, + int parentID) + : m_url(url), m_target(target), m_sort(sort), m_limit(limit), m_parentID(parentID) { } ~CDirectoryJob() override = default; @@ -80,9 +82,12 @@ class CDirectoryJob : public CJob items.Sort(m_sort); // limit must not exceed the number of items - int limit = (m_limit == 0) ? items.Size() : std::min((int) m_limit, items.Size()); + int limit = (m_limit == 0) ? items.Size() : std::min(static_cast(m_limit), items.Size()); + if (limit < items.Size()) + m_items.reserve(limit + 1); + else + m_items.reserve(limit); // convert to CGUIStaticItem's and set visibility and targets - m_items.reserve(limit); for (int i = 0; i < limit; i++) { CGUIStaticItemPtr item(new CGUIStaticItem(*items[i])); @@ -93,7 +98,27 @@ class CDirectoryJob : public CJob m_items.push_back(item); } - m_target = items.GetProperty("node.target").asString(); + + if (items.HasProperty("node.target")) + m_target = items.GetProperty("node.target").asString(); + + if (limit < items.Size()) + { + // Add a special item to the end of the limited list, which can be used to open the + // full listing containg all items in the given target window. + if (!m_target.empty()) + { + CFileItem item(m_url, true); + item.SetLabel(g_localizeStrings.Get(22082)); // More... + item.SetArt("icon", "DefaultFolder.png"); + item.SetProperty("node.target", m_target); + item.SetProperty("node.type", "target_folder"); // make item identifyable, e.g. by skins + + m_items.emplace_back(std::make_shared(item)); + } + else + CLog::LogF(LOGWARNING, "Cannot add 'More...' item to list. No target window given."); + } } return true; } @@ -231,7 +256,9 @@ bool CDirectoryProvider::Update(bool forceRefresh) if (m_jobID) CServiceBroker::GetJobManager()->CancelJob(m_jobID); m_jobID = CServiceBroker::GetJobManager()->AddJob( - new CDirectoryJob(m_currentUrl, m_currentSort, m_currentLimit, m_parentID), this); + new CDirectoryJob(m_currentUrl, m_target.GetLabel(m_parentID, false), m_currentSort, + m_currentLimit, m_parentID), + this); } if (!changed) From e2b9f545df43b860b785acb4215352bce996e697 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:26:18 +0200 Subject: [PATCH 098/811] [PVR] CGUIWindowPVRChannelsBase: Fix all channels wildcard not resolved on init of window with group URL. --- xbmc/pvr/windows/GUIWindowPVRChannels.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp index 6b6e87776b6bb..6e97a504b7e46 100644 --- a/xbmc/pvr/windows/GUIWindowPVRChannels.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRChannels.cpp @@ -161,7 +161,17 @@ bool CGUIWindowPVRChannelsBase::OnMessage(CGUIMessage& message) { // if a path to a channel group is given we must init // that group instead of last played/selected group - m_channelGroupPath = message.GetStringParam(0); + if (path.GetGroupName() == "*") // all channels + { + // Replace wildcard with real group name + const auto group = + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAll(path.IsRadio()); + m_channelGroupPath = group->GetPath(); + } + else + { + m_channelGroupPath = message.GetStringParam(0); + } } break; } From cc555f1165bff85c7710c729d5b837d3d7185d92 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Mon, 28 Aug 2023 17:33:20 +0200 Subject: [PATCH 099/811] [webOS][Wayland] Rework shell support to use webos shell Shell supported mainly relied on still using the wayland shell surface. However, the wayland shell surface is managed by webos shell and thus operations should be handled through that protocol. This also allows to fix the hardcoded resolutions by listening to the exposed callback. Technically, the assumption of only 1080p displays was incorrect as there are 720p UI layers and in future there might 4k UI layers. This also allows to properly support relaunches which would previously have been broken. Also note that maximizing and unsetting maximized is not really a thing on webOS as there can only be one window, however at least the protocol allows for maximizing which is probably the same as fullscreen --- .../wayland/ShellSurfaceWebOSShell.cpp | 59 +++++++++++-------- .../wayland/ShellSurfaceWebOSShell.h | 3 +- 2 files changed, 37 insertions(+), 25 deletions(-) diff --git a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp index 95c8c37a98540..a4b9ab869d3ca 100644 --- a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp +++ b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp @@ -18,15 +18,6 @@ using namespace std::placeholders; using namespace wayland; -namespace -{ -/* - WebOS always is 1920x1080 -*/ -constexpr int WEBOS_UI_WIDTH = 1920; -constexpr int WEBOS_UI_HEIGHT = 1080; -} // unnamed namespace - CShellSurfaceWebOSShell::CShellSurfaceWebOSShell(IShellSurfaceHandler& handler, CConnection& connection, const wayland::surface_t& surface, @@ -45,21 +36,42 @@ CShellSurfaceWebOSShell::CShellSurfaceWebOSShell(IShellSurfaceHandler& handler, m_webos_shellSurface = m_webos_shell.get_shell_surface(surface); + m_webos_shellSurface.on_exposed() = [this](const std::vector& rect) { + if (rect.size() >= 4) + m_windowSize = {rect[2], rect[3]}; + }; + m_webos_shellSurface.on_state_changed() = [this](std::uint32_t state) { - switch (state) + switch (static_cast(state)) { - case static_cast(webos_shell_surface_state::fullscreen): - CLog::Log(LOGDEBUG, "webOS notification - Changed to full screen"); - m_handler.OnConfigure(0, {WEBOS_UI_WIDTH, WEBOS_UI_HEIGHT}, m_surfaceState); + case webos_shell_surface_state::fullscreen: + CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to full screen"); + m_surfaceState.reset(); + m_surfaceState.set(STATE_ACTIVATED); + m_surfaceState.set(STATE_FULLSCREEN); + m_handler.OnConfigure(0, m_windowSize, m_surfaceState); break; - case static_cast(webos_shell_surface_state::minimized): - CLog::Log(LOGDEBUG, "webOS notification - Changed to minimized"); + case webos_shell_surface_state::maximized: + CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to maximized"); + m_surfaceState.reset(); + m_surfaceState.set(STATE_ACTIVATED); + m_surfaceState.set(STATE_MAXIMIZED); + m_handler.OnConfigure(0, m_windowSize, m_surfaceState); + break; + case webos_shell_surface_state::minimized: + CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to minimized"); + m_surfaceState.reset(); + break; + case webos_shell_surface_state::_default: + CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to default (windowed)"); + m_surfaceState.reset(); + m_surfaceState.set(STATE_ACTIVATED); break; } }; m_webos_shellSurface.on_close() = [this]() { - CLog::Log(LOGDEBUG, "webOS notification - Close notification received"); + CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: Close notification received"); m_handler.OnClose(); }; @@ -83,33 +95,32 @@ CShellSurfaceWebOSShell::CShellSurfaceWebOSShell(IShellSurfaceHandler& handler, // Allow the back button the LG remote to be passed to Kodi and not intercepted m_webos_shellSurface.set_property("_WEBOS_ACCESS_POLICY_KEYS_BACK", "true"); - m_surfaceState.set(STATE_ACTIVATED); m_shellSurface.set_class(className); m_shellSurface.set_title(title); } void CShellSurfaceWebOSShell::SetFullScreen(const wayland::output_t& output, float refreshRate) { - m_shellSurface.set_fullscreen(wayland::shell_surface_fullscreen_method::driver, - std::round(refreshRate * 1000.0f), output); - m_surfaceState.set(STATE_FULLSCREEN); + m_webos_shellSurface.set_state(static_cast(webos_shell_surface_state::fullscreen)); } void CShellSurfaceWebOSShell::SetWindowed() { m_shellSurface.set_toplevel(); - m_surfaceState.reset(STATE_FULLSCREEN); } void CShellSurfaceWebOSShell::SetMaximized() { - m_shellSurface.set_maximized(wayland::output_t()); - m_surfaceState.set(STATE_MAXIMIZED); + m_webos_shellSurface.set_state(static_cast(webos_shell_surface_state::maximized)); } void CShellSurfaceWebOSShell::UnsetMaximized() { - m_surfaceState.reset(STATE_MAXIMIZED); +} + +void CShellSurfaceWebOSShell::SetMinimized() +{ + m_webos_shellSurface.set_state(static_cast(webos_shell_surface_state::minimized)); } void CShellSurfaceWebOSShell::StartMove(const wayland::seat_t& seat, std::uint32_t serial) diff --git a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.h b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.h index 2020672560003..a5c40590fdfda 100644 --- a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.h +++ b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.h @@ -44,7 +44,7 @@ class CShellSurfaceWebOSShell : public IShellSurface void SetWindowed() override; void SetMaximized() override; void UnsetMaximized() override; - void SetMinimized() override{}; + void SetMinimized() override; void SetWindowGeometry(CRectInt geometry) override{}; void AckConfigure(std::uint32_t serial) override{}; @@ -63,6 +63,7 @@ class CShellSurfaceWebOSShell : public IShellSurface StateBitset m_surfaceState; wayland::webos_shell_t m_webos_shell; wayland::webos_shell_surface_t m_webos_shellSurface; + CSizeInt m_windowSize; }; } // namespace WAYLAND From 722474671bf6b646514851d2160ed9ac2d2394c1 Mon Sep 17 00:00:00 2001 From: smp79 Date: Mon, 28 Aug 2023 23:41:17 +0300 Subject: [PATCH 100/811] VideoPlayer: VAAPI - fix VP9 Profile 2 playback --- xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp index f9b6f17824eb8..4335105e8026d 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/VAAPI.cpp @@ -1159,7 +1159,7 @@ bool CDecoder::ConfigVAAPI() unsigned int format = VA_RT_FORMAT_YUV420; std::int32_t pixelFormat = VA_FOURCC_NV12; - if ((m_vaapiConfig.profile == VAProfileHEVCMain10 + if ((m_vaapiConfig.profile == VAProfileHEVCMain10 || m_vaapiConfig.profile == VAProfileVP9Profile2 #if VA_CHECK_VERSION(1, 8, 0) || m_vaapiConfig.profile == VAProfileAV1Profile0 #endif From d264316ec718a713a400de4004a75f92d596bebe Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 28 Aug 2023 14:03:06 +0100 Subject: [PATCH 101/811] [GUI] Move network infos to sysinfo --- xbmc/guilib/guiinfo/SystemGUIInfo.cpp | 67 ++------------------------ xbmc/utils/SystemInfo.cpp | 68 +++++++++++++++++++++++++++ xbmc/utils/SystemInfo.h | 12 +++++ 3 files changed, 85 insertions(+), 62 deletions(-) diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp index 898a0e46fbaaf..899e4ff98a049 100644 --- a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp @@ -176,7 +176,12 @@ bool CSystemGUIInfo::GetLabel(std::string& value, const CFileItem *item, int con value = GetSystemHeatInfo(info.m_info); return true; case SYSTEM_VIDEO_ENCODER_INFO: + case NETWORK_IP_ADDRESS: case NETWORK_MAC_ADDRESS: + case NETWORK_SUBNET_MASK: + case NETWORK_GATEWAY_ADDRESS: + case NETWORK_DNS1_ADDRESS: + case NETWORK_DNS2_ADDRESS: case SYSTEM_OS_VERSION_INFO: case SYSTEM_CPUFREQUENCY: case SYSTEM_INTERNET_STATE: @@ -364,56 +369,6 @@ bool CSystemGUIInfo::GetLabel(std::string& value, const CFileItem *item, int con /////////////////////////////////////////////////////////////////////////////////////////////// // NETWORK_* /////////////////////////////////////////////////////////////////////////////////////////////// - case NETWORK_IP_ADDRESS: - { - CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); - if (iface) - { - value = iface->GetCurrentIPAddress(); - return true; - } - break; - } - case NETWORK_SUBNET_MASK: - { - CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); - if (iface) - { - value = iface->GetCurrentNetmask(); - return true; - } - break; - } - case NETWORK_GATEWAY_ADDRESS: - { - CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); - if (iface) - { - value = iface->GetCurrentDefaultGateway(); - return true; - } - break; - } - case NETWORK_DNS1_ADDRESS: - { - const std::vector nss = CServiceBroker::GetNetwork().GetNameServers(); - if (nss.size() >= 1) - { - value = nss[0]; - return true; - } - break; - } - case NETWORK_DNS2_ADDRESS: - { - const std::vector nss = CServiceBroker::GetNetwork().GetNameServers(); - if (nss.size() >= 2) - { - value = nss[1]; - return true; - } - break; - } case NETWORK_DHCP_ADDRESS: { // wtf? @@ -421,18 +376,6 @@ bool CSystemGUIInfo::GetLabel(std::string& value, const CFileItem *item, int con value = dhcpserver; return true; } - case NETWORK_LINK_STATE: - { - std::string linkStatus = g_localizeStrings.Get(151); - linkStatus += " "; - CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); - if (iface && iface->IsConnected()) - linkStatus += g_localizeStrings.Get(15207); - else - linkStatus += g_localizeStrings.Get(15208); - value = linkStatus; - return true; - } } return false; diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp index e3bda61db4f43..22f2e7a851529 100644 --- a/xbmc/utils/SystemInfo.cpp +++ b/xbmc/utils/SystemInfo.cpp @@ -288,6 +288,11 @@ bool CSysInfoJob::DoWork() m_info.osVersionInfo = CSysInfo::GetOsPrettyNameWithVersion() + " (kernel: " + CSysInfo::GetKernelName() + " " + CSysInfo::GetKernelVersionFull() + ")"; m_info.macAddress = GetMACAddress(); m_info.batteryLevel = GetBatteryLevel(); + m_info.ipAddress = GetIPAddress(); + m_info.netMask = GetNetMask(); + m_info.dnsServers = GetDNSServers(); + m_info.gatewayAddress = GetGatewayAddress(); + m_info.networkLinkState = GetNetworkLinkState(); return true; } @@ -314,6 +319,57 @@ std::string CSysInfoJob::GetMACAddress() return ""; } +std::string CSysInfoJob::GetIPAddress() +{ + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface) + { + return iface->GetCurrentIPAddress(); + } + return {}; +} + +std::string CSysInfoJob::GetNetMask() +{ + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface) + { + return iface->GetCurrentNetmask(); + } + return {}; +} + +std::string CSysInfoJob::GetGatewayAddress() +{ + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface) + { + return iface->GetCurrentDefaultGateway(); + } + return {}; +} + +std::string CSysInfoJob::GetNetworkLinkState() +{ + std::string linkStatus = g_localizeStrings.Get(151); + linkStatus += " "; + CNetworkInterface* iface = CServiceBroker::GetNetwork().GetFirstConnectedInterface(); + if (iface && iface->IsConnected()) + { + linkStatus += g_localizeStrings.Get(15207); + } + else + { + linkStatus += g_localizeStrings.Get(15208); + } + return linkStatus; +} + +std::vector CSysInfoJob::GetDNSServers() +{ + return CServiceBroker::GetNetwork().GetNameServers(); +} + std::string CSysInfoJob::GetVideoEncoder() { return "GPU: " + CServiceBroker::GetRenderSystem()->GetRenderRenderer(); @@ -387,6 +443,18 @@ std::string CSysInfo::TranslateInfo(int info) const return m_info.videoEncoder; case NETWORK_MAC_ADDRESS: return m_info.macAddress; + case NETWORK_IP_ADDRESS: + return m_info.ipAddress; + case NETWORK_SUBNET_MASK: + return m_info.netMask; + case NETWORK_GATEWAY_ADDRESS: + return m_info.gatewayAddress; + case NETWORK_DNS1_ADDRESS: + return m_info.dnsServers.size() > 0 ? m_info.dnsServers.at(0) : ""; + case NETWORK_DNS2_ADDRESS: + return m_info.dnsServers.size() > 1 ? m_info.dnsServers.at(1) : ""; + case NETWORK_LINK_STATE: + return m_info.networkLinkState; case SYSTEM_OS_VERSION_INFO: return m_info.osVersionInfo; case SYSTEM_CPUFREQUENCY: diff --git a/xbmc/utils/SystemInfo.h b/xbmc/utils/SystemInfo.h index 9d9e8200a40d0..eed3851924343 100644 --- a/xbmc/utils/SystemInfo.h +++ b/xbmc/utils/SystemInfo.h @@ -10,8 +10,10 @@ #include "InfoLoader.h" #include "settings/ISubSettings.h" +#include "utils/Job.h" #include +#include #define KB (1024) // 1 KiloByte (1KB) 1024 Byte (2^10 Byte) #define MB (1024*KB) // 1 MegaByte (1MB) 1024 KB (2^10 KB) @@ -44,6 +46,11 @@ class CSysData std::string cpuFrequency; std::string osVersionInfo; std::string macAddress; + std::string ipAddress; + std::string netMask; + std::string gatewayAddress; + std::string networkLinkState; + std::vector dnsServers; std::string batteryLevel; }; @@ -60,8 +67,13 @@ class CSysInfoJob : public CJob static bool SystemUpTime(int iInputMinutes, int &iMinutes, int &iHours, int &iDays); static std::string GetSystemUpTime(bool bTotalUptime); static std::string GetMACAddress(); + static std::string GetIPAddress(); + static std::string GetNetMask(); + static std::string GetGatewayAddress(); + static std::string GetNetworkLinkState(); static std::string GetVideoEncoder(); static std::string GetBatteryLevel(); + static std::vector GetDNSServers(); CSysData m_info; }; From 9220310a9b63c041f1c1a152a6b39ada7af8c958 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 28 Aug 2023 14:11:01 +0100 Subject: [PATCH 102/811] [Info] Nuke Network.DHCPAddress --- xbmc/GUIInfoManager.cpp | 25 ++++++++++--------------- xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 - xbmc/guilib/guiinfo/SystemGUIInfo.cpp | 11 ----------- 3 files changed, 10 insertions(+), 27 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index f344b50a8122e..025ef8dd1d6fc 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -2068,24 +2068,14 @@ const infomap system_param[] = {{ "hasalarm", SYSTEM_HAS_ALARM }, /// @return The network DNS 2 address. ///

/// } -/// \table_row3{ `Network.DHCPAddress`, -/// \anchor Network_DHCPAddress -/// _string_, -/// @return The DHCP IP address. -///

-/// } /// \table_end /// /// ----------------------------------------------------------------------------- -const infomap network_labels[] = {{ "isdhcp", NETWORK_IS_DHCP }, - { "ipaddress", NETWORK_IP_ADDRESS }, //labels from here - { "linkstate", NETWORK_LINK_STATE }, - { "macaddress", NETWORK_MAC_ADDRESS }, - { "subnetmask", NETWORK_SUBNET_MASK }, - { "gatewayaddress", NETWORK_GATEWAY_ADDRESS }, - { "dns1address", NETWORK_DNS1_ADDRESS }, - { "dns2address", NETWORK_DNS2_ADDRESS }, - { "dhcpaddress", NETWORK_DHCP_ADDRESS }}; +const infomap network_labels[] = { + {"isdhcp", NETWORK_IS_DHCP}, {"ipaddress", NETWORK_IP_ADDRESS}, //labels from here + {"linkstate", NETWORK_LINK_STATE}, {"macaddress", NETWORK_MAC_ADDRESS}, + {"subnetmask", NETWORK_SUBNET_MASK}, {"gatewayaddress", NETWORK_GATEWAY_ADDRESS}, + {"dns1address", NETWORK_DNS1_ADDRESS}, {"dns2address", NETWORK_DNS2_ADDRESS}}; /// \page modules__infolabels_boolean_conditions /// \subsection modules__infolabels_boolean_conditions_musicpartymode Music party mode @@ -9715,6 +9705,11 @@ const infomap slideshow[] = {{ "ispaused", SLIDESHOW_ISPAUSED /// \page modules__infolabels_boolean_conditions /// \section modules_rm_infolabels_booleans Additional revision history for Infolabels and Boolean Conditions ///


+/// \subsection modules_rm_infolabels_booleans_v21 Kodi v21 (Omega) +/// @skinning_v21 **[Removed Infolabels]** The following infolabels have been removed: +/// - `Network.DHCPAddress` - this info did not return any meaningful value (always an empty string) +/// +///
/// \subsection modules_rm_infolabels_booleans_v20 Kodi v20 (Nexus) /// @skinning_v20 **[Removed Boolean conditions]** The following boolean conditions have been removed: /// - `Player.DisplayAfterSeek` - use \link Player_HasPerformedSeek `Player.HasPerformedSeek(interval)`\endlink instead diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 3e2b163fdc74f..5d186bc2efff7 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -167,7 +167,6 @@ #define NETWORK_GATEWAY_ADDRESS 195 #define NETWORK_DNS1_ADDRESS 196 #define NETWORK_DNS2_ADDRESS 197 -#define NETWORK_DHCP_ADDRESS 198 // Keep musicplayer infolabels that work with offset and position together #define MUSICPLAYER_TITLE 200 diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp index 899e4ff98a049..408522ea3e5fa 100644 --- a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp @@ -365,17 +365,6 @@ bool CSystemGUIInfo::GetLabel(std::string& value, const CFileItem *item, int con return true; } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // NETWORK_* - /////////////////////////////////////////////////////////////////////////////////////////////// - case NETWORK_DHCP_ADDRESS: - { - // wtf? - std::string dhcpserver; - value = dhcpserver; - return true; - } } return false; From 1ee604427ed53a22d1d1a56a13972f45afa1b5e2 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Mon, 28 Aug 2023 14:20:21 +0100 Subject: [PATCH 103/811] [info] Disable clang-format for network_labels map --- xbmc/GUIInfoManager.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 025ef8dd1d6fc..0c689a6cbe9b5 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -2071,11 +2071,18 @@ const infomap system_param[] = {{ "hasalarm", SYSTEM_HAS_ALARM }, /// \table_end /// /// ----------------------------------------------------------------------------- +// clang-format off const infomap network_labels[] = { - {"isdhcp", NETWORK_IS_DHCP}, {"ipaddress", NETWORK_IP_ADDRESS}, //labels from here - {"linkstate", NETWORK_LINK_STATE}, {"macaddress", NETWORK_MAC_ADDRESS}, - {"subnetmask", NETWORK_SUBNET_MASK}, {"gatewayaddress", NETWORK_GATEWAY_ADDRESS}, - {"dns1address", NETWORK_DNS1_ADDRESS}, {"dns2address", NETWORK_DNS2_ADDRESS}}; + {"isdhcp", NETWORK_IS_DHCP}, + {"ipaddress", NETWORK_IP_ADDRESS}, //labels from here + {"linkstate", NETWORK_LINK_STATE}, + {"macaddress", NETWORK_MAC_ADDRESS}, + {"subnetmask", NETWORK_SUBNET_MASK}, + {"gatewayaddress", NETWORK_GATEWAY_ADDRESS}, + {"dns1address", NETWORK_DNS1_ADDRESS}, + {"dns2address", NETWORK_DNS2_ADDRESS} +}; +// clang-format on /// \page modules__infolabels_boolean_conditions /// \subsection modules__infolabels_boolean_conditions_musicpartymode Music party mode From efaa02c9ac9883f8fdc1c20e54a2cb0e964e0d7a Mon Sep 17 00:00:00 2001 From: fritsch Date: Mon, 28 Aug 2023 20:14:46 +0200 Subject: [PATCH 104/811] ActiveAESink: Use AddPause for DD+ again in RAW mode This workaround, primarily tested on Shield sadly caused a regression on FireTV 4K. --- xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 53759c8c52d64..01a204c852039 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -1151,11 +1151,10 @@ void CActiveAESink::SetSilenceTimer() m_extSilenceTimeout = XbmcThreads::EndTime::Max(); else if (m_extAppFocused) // handles no playback/GUI and playback in pause and seek { - // only true with AudioTrack RAW + passthrough + TrueHD or EAC3 (DD+) + // only true with AudioTrack RAW + passthrough + TrueHD const bool noSilenceOnPause = !m_needIecPack && m_requestedFormat.m_dataFormat == AE_FMT_RAW && - (m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD || - m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_EAC3); + m_sinkFormat.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD; m_extSilenceTimeout = (noSilenceOnPause) ? 0ms : m_silenceTimeOut; } From ea670796c3a5fff8c7487e2758842b73675cfb7b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 24 Aug 2023 10:24:45 +0200 Subject: [PATCH 105/811] [PVR] CPVRChannelsPath: fix typo. --- xbmc/pvr/channels/PVRChannelsPath.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/pvr/channels/PVRChannelsPath.h b/xbmc/pvr/channels/PVRChannelsPath.h index 27991e66039a5..d5545148a83b2 100644 --- a/xbmc/pvr/channels/PVRChannelsPath.h +++ b/xbmc/pvr/channels/PVRChannelsPath.h @@ -21,7 +21,7 @@ namespace PVR static const std::string PATH_RADIO_CHANNELS; explicit CPVRChannelsPath(const std::string& strPath); - CPVRChannelsPath(bool bRadio, const std::string& strGroupName, int iGrouupClientID); + CPVRChannelsPath(bool bRadio, const std::string& strGroupName, int iGroupClientID); CPVRChannelsPath(bool bRadio, bool bHidden, const std::string& strGroupName, From 4e48e7499f4434aa243601fe33340dc6ac4be349 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:40:06 +0200 Subject: [PATCH 106/811] [video] Fix movie set overview not updated when refreshing data. --- xbmc/video/VideoDatabase.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 6690659d842fc..b286bcadb9962 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -1710,6 +1710,11 @@ int CVideoDatabase::AddSet(const std::string& strSet, const std::string& strOver { int id = m_pDS->fv("idSet").get_asInt(); m_pDS->close(); + + // update set data + strSQL = PrepareSQL("UPDATE sets SET strOverview = '%s' WHERE idSet = %i", + strOverview.c_str(), id); + m_pDS->exec(strSQL); return id; } } From 596dd3d31fb0d64ad178014b45f2fd081cd47a50 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 20 Aug 2023 13:17:06 +0200 Subject: [PATCH 107/811] [video] Improve library refresh progress dialog content a bit. --- xbmc/dialogs/GUIDialogBoxBase.cpp | 12 ++++++++++++ xbmc/dialogs/GUIDialogBoxBase.h | 4 +++- xbmc/utils/ProgressJob.cpp | 8 ++++++-- xbmc/video/VideoInfoScanner.cpp | 19 ++++++++++++------- xbmc/video/jobs/VideoLibraryRefreshingJob.cpp | 2 +- 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/xbmc/dialogs/GUIDialogBoxBase.cpp b/xbmc/dialogs/GUIDialogBoxBase.cpp index 1d87949a256fe..0bbd5799cf41e 100644 --- a/xbmc/dialogs/GUIDialogBoxBase.cpp +++ b/xbmc/dialogs/GUIDialogBoxBase.cpp @@ -60,6 +60,12 @@ void CGUIDialogBoxBase::SetHeading(const CVariant& heading) } } +bool CGUIDialogBoxBase::HasHeading() const +{ + std::unique_lock lock(m_section); + return !m_strHeading.empty(); +} + void CGUIDialogBoxBase::SetLine(unsigned int iLine, const CVariant& line) { std::string label = GetLocalized(line); @@ -84,6 +90,12 @@ void CGUIDialogBoxBase::SetText(const CVariant& text) } } +bool CGUIDialogBoxBase::HasText() const +{ + std::unique_lock lock(m_section); + return !m_text.empty(); +} + void CGUIDialogBoxBase::SetChoice(int iButton, const CVariant &choice) // iButton == 0 for no, 1 for yes { if (iButton < 0 || iButton >= DIALOG_MAX_CHOICES) diff --git a/xbmc/dialogs/GUIDialogBoxBase.h b/xbmc/dialogs/GUIDialogBoxBase.h index 41ab31b2aad23..3947e6e953778 100644 --- a/xbmc/dialogs/GUIDialogBoxBase.h +++ b/xbmc/dialogs/GUIDialogBoxBase.h @@ -34,7 +34,9 @@ class CGUIDialogBoxBase : bool IsConfirmed() const; void SetLine(unsigned int iLine, const CVariant& line); void SetText(const CVariant& text); + bool HasText() const; void SetHeading(const CVariant& heading); + bool HasHeading() const; void SetChoice(int iButton, const CVariant &choice); protected: std::string GetDefaultLabel(int controlId) const; @@ -54,7 +56,7 @@ class CGUIDialogBoxBase : bool m_hasTextbox; // actual strings - CCriticalSection m_section; + mutable CCriticalSection m_section; std::string m_strHeading; std::string m_text; std::string m_strChoices[DIALOG_MAX_CHOICES]; diff --git a/xbmc/utils/ProgressJob.cpp b/xbmc/utils/ProgressJob.cpp index 6ef1f24a72bce..56667a69b5000 100644 --- a/xbmc/utils/ProgressJob.cpp +++ b/xbmc/utils/ProgressJob.cpp @@ -103,7 +103,9 @@ void CProgressJob::SetTitle(const std::string &title) { m_progressDialog->SetHeading(CVariant{title}); - ShowProgressDialog(); + // Prevent displaying the progress dialog without any heading and/or text. + if (m_progressDialog->HasHeading() && m_progressDialog->HasText()) + ShowProgressDialog(); } } @@ -118,7 +120,9 @@ void CProgressJob::SetText(const std::string &text) { m_progressDialog->SetText(CVariant{text}); - ShowProgressDialog(); + // Prevent displaying the progress dialog without any heading and/or text. + if (m_progressDialog->HasText() && m_progressDialog->HasHeading()) + ShowProgressDialog(); } } diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 7b8c760d5fba9..6b7ab281e5220 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -576,7 +576,9 @@ namespace VIDEO return ret; } - if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, pItem->GetLabel())) + if (ProgressCancelled(pDlgProgress, pItem->m_bIsFolder ? 20353 : 20361, + pItem->m_bIsFolder ? pItem->GetVideoInfoTag()->m_strShowTitle + : pItem->GetVideoInfoTag()->m_strTitle)) return INFO_CANCELLED; if (m_handle) @@ -1729,8 +1731,7 @@ namespace VIDEO { if (pDlgProgress) { - pDlgProgress->SetLine(1, CVariant{showInfo.m_strTitle}); - pDlgProgress->SetLine(2, CVariant{20361}); + pDlgProgress->SetLine(1, CVariant{20361}); // Loading episode details pDlgProgress->SetPercentage(0); pDlgProgress->ShowProgressBar(true); pDlgProgress->Progress(); @@ -1745,7 +1746,11 @@ namespace VIDEO { if (pDlgProgress) { - pDlgProgress->SetLine(2, CVariant{20361}); + pDlgProgress->SetLine(1, CVariant{20361}); // Loading episode details + pDlgProgress->SetLine(2, StringUtils::Format("{} {}", g_localizeStrings.Get(20373), + file->iSeason)); // Season x + pDlgProgress->SetLine(3, StringUtils::Format("{} {}", g_localizeStrings.Get(20359), + file->iEpisode)); // Episode y pDlgProgress->SetPercentage((int)((float)(iCurr++)/iMax*100)); pDlgProgress->Progress(); } @@ -1808,7 +1813,7 @@ namespace VIDEO if (pDlgProgress) { - pDlgProgress->SetLine(2, CVariant{20354}); + pDlgProgress->SetLine(1, CVariant{20354}); // Fetching episode guide pDlgProgress->Progress(); } @@ -1978,7 +1983,8 @@ namespace VIDEO if (pDialog) { - pDialog->SetLine(1, CVariant{movieDetails.m_strTitle}); + if (!pDialog->HasText()) + pDialog->SetLine(0, CVariant{movieDetails.m_strTitle}); pDialog->Progress(); } @@ -2237,7 +2243,6 @@ namespace VIDEO { progress->SetHeading(CVariant{heading}); progress->SetLine(0, CVariant{line1}); - progress->SetLine(2, CVariant{""}); progress->Progress(); return progress->IsCanceled(); } diff --git a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp index fc8c9b68cbd65..9f300720b2507 100644 --- a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp +++ b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp @@ -305,7 +305,7 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) // prepare the progress dialog for downloading all the necessary information SetTitle(g_localizeStrings.Get(headingLabel)); - SetText(scraperUrl.GetTitle()); + SetText(itemTitle); SetProgress(0); // remove any existing data for the item we're going to refresh From 331ebc3c3f0aeec251d43b58334dd600a02a9c7f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 27 Aug 2023 22:40:26 +0200 Subject: [PATCH 108/811] [video] VIDEO_UTILS::GetFolderItemResumeInformation : Remove pointless error logging. --- xbmc/video/VideoUtils.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index a32c02a073ef6..3d98f200e9e99 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -677,7 +677,6 @@ ResumeInformation GetFolderItemResumeInformation(const CFileItem& item) return resumeInfo; } - CLog::LogF(LOGERROR, "Cannot obtain inprogress state for {}", folderItem.GetPath()); return {}; } From f17f5eee7e92b70c83585a4e104ce145a257c8fe Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 28 Aug 2023 19:50:39 +0200 Subject: [PATCH 109/811] [fileitem] Remove CFileItem::IsResumable(). Only called from one place and name is highly misleading. In our code base, 'isresumable' denotes something that actually has a resume point set, nothing that could be resumed generally, based on its kind. --- xbmc/FileItem.cpp | 5 ----- xbmc/FileItem.h | 6 ------ xbmc/video/VideoUtils.cpp | 7 ++++++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 2eb5eb072c34d..561b34a233490 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -4029,8 +4029,3 @@ bool CFileItem::GetCurrentResumeTimeAndPartNumber(int64_t& startOffset, int& par } return false; } - -bool CFileItem::IsResumable() const -{ - return (!IsNFO() && !IsPlayList()) || IsType(".strm"); -} diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index e0adc28e4dda5..e176c111ed292 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -367,12 +367,6 @@ class CFileItem : */ bool GetCurrentResumeTimeAndPartNumber(int64_t& startOffset, int& partNumber) const; - /*! - * \brief Test if this item type can be resumed. - * \return True if this item can be resumed, false otherwise. - */ - bool IsResumable() const; - /*! * \brief Get the offset where start the playback. * \return The offset value as ms. diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index 3d98f200e9e99..c04b3629bc56b 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -682,7 +682,12 @@ ResumeInformation GetFolderItemResumeInformation(const CFileItem& item) ResumeInformation GetNonFolderItemResumeInformation(const CFileItem& item) { - if (!item.IsResumable()) + // do not resume nfo files + if (item.IsNFO()) + return {}; + + // do not resume playlists, except strm files + if (!item.IsType("strm") && item.IsPlayList()) return {}; // do not resume Live TV and 'deleted' items (e.g. trashed pvr recordings) From f9f748f4ef73bbdbf66ccae6e323b2fceeeca7fd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 27 Aug 2023 23:23:40 +0200 Subject: [PATCH 110/811] [guilib][guiinfo] LISTITEM_IS_RESUMABLE: Added support for folders. --- xbmc/FileItem.cpp | 30 ++++++++++++++++++++++++++++ xbmc/FileItem.h | 8 ++++++++ xbmc/guilib/guiinfo/VideoGUIInfo.cpp | 6 +++--- xbmc/video/VideoUtils.cpp | 20 +------------------ 4 files changed, 42 insertions(+), 22 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 561b34a233490..77ad297a7a36a 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -4029,3 +4029,33 @@ bool CFileItem::GetCurrentResumeTimeAndPartNumber(int64_t& startOffset, int& par } return false; } + +bool CFileItem::IsResumable() const +{ + if (m_bIsFolder) + { + int64_t watched = 0; + int64_t inprogress = 0; + int64_t total = 0; + if (HasProperty("inprogressepisodes")) + { + // show/season + watched = GetProperty("watchedepisodes").asInteger(); + inprogress = GetProperty("inprogressepisodes").asInteger(); + total = GetProperty("totalepisodes").asInteger(); + } + else if (HasProperty("inprogress")) + { + // movie set + watched = GetProperty("watched").asInteger(); + inprogress = GetProperty("inprogress").asInteger(); + total = GetProperty("total").asInteger(); + } + + return ((total != watched) && (inprogress > 0 || watched != 0)); + } + else + { + return HasVideoInfoTag() && GetVideoInfoTag()->GetResumePoint().IsPartWay(); + } +} diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index e176c111ed292..464ce10a3548a 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -367,6 +367,14 @@ class CFileItem : */ bool GetCurrentResumeTimeAndPartNumber(int64_t& startOffset, int& partNumber) const; + /*! + * \brief Test if this item type can be resumed. + * \return True if this item is a folder and has at least one child with a partway resume bookmark + * or at least one unwatched child or if it is not a folder, if it has a partway resume bookmark, + * false otherwise. + */ + bool IsResumable() const; + /*! * \brief Get the offset where start the playback. * \return The offset value as ms. diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp index 794434f94ad31..3a6b0daf5a621 100644 --- a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp @@ -748,9 +748,6 @@ bool CVideoGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextW ///////////////////////////////////////////////////////////////////////////////////////////// // LISTITEM_* ///////////////////////////////////////////////////////////////////////////////////////////// - case LISTITEM_IS_RESUMABLE: - value = tag->GetResumePoint().timeInSeconds > 0; - return true; case LISTITEM_IS_COLLECTION: value = tag->m_type == MediaTypeVideoCollection; return true; @@ -803,6 +800,9 @@ bool CVideoGUIInfo::GetBool(bool& value, const CGUIListItem *gitem, int contextW /////////////////////////////////////////////////////////////////////////////////////////////// // LISTITEM_* /////////////////////////////////////////////////////////////////////////////////////////////// + case LISTITEM_IS_RESUMABLE: + value = item->IsResumable(); + return true; case LISTITEM_IS_STEREOSCOPIC: { std::string stereoMode = item->GetProperty("stereomode").asString(); diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index c04b3629bc56b..ab23d6a0e50be 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -652,25 +652,7 @@ ResumeInformation GetFolderItemResumeInformation(const CFileItem& item) } } - int64_t watched = 0; - int64_t inprogress = 0; - int64_t total = 0; - if (folderItem.HasProperty("inprogressepisodes")) - { - // show/season - watched = folderItem.GetProperty("watchedepisodes").asInteger(); - inprogress = folderItem.GetProperty("inprogressepisodes").asInteger(); - total = folderItem.GetProperty("totalepisodes").asInteger(); - } - else if (folderItem.HasProperty("inprogress")) - { - // movie set - watched = folderItem.GetProperty("watched").asInteger(); - inprogress = folderItem.GetProperty("inprogress").asInteger(); - total = folderItem.GetProperty("total").asInteger(); - } - - if ((total != watched) && (inprogress > 0 || watched != 0)) + if (folderItem.IsResumable()) { ResumeInformation resumeInfo; resumeInfo.isResumable = true; From 36b43aafd6d43f26c1be5adc05b571c55eaf2d72 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 28 Aug 2023 00:28:58 +0200 Subject: [PATCH 111/811] [Estuary] ListWatchedIconVar: movie sets shall not display 'resumable' status. --- addons/skin.estuary/xml/Variables.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 7541e834af212..f3787b1f0526d 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -452,9 +452,9 @@
windows/pvr/record.png + overlays/set.png overlays/watched/OverlayPlaying-List.png overlays/watched/resume.png - overlays/set.png overlays/folder.png $INFO[ListItem.Overlay] OverlayUnwatched.png From a8d3d40b16fa896ae19c720a1f79ff6a8cbf8f8b Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Thu, 24 Aug 2023 11:44:06 -0700 Subject: [PATCH 112/811] Games: Fix clang-formatting after tinyxml2 update --- xbmc/games/ports/input/PortManager.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/xbmc/games/ports/input/PortManager.cpp b/xbmc/games/ports/input/PortManager.cpp index b9c582a92e676..dc2ec762895ea 100644 --- a/xbmc/games/ports/input/PortManager.cpp +++ b/xbmc/games/ports/input/PortManager.cpp @@ -105,21 +105,19 @@ void CPortManager::SaveXMLAsync() m_saveFutures.end()); // Save async - std::future task = std::async(std::launch::async, - [this, ports = std::move(ports)]() - { - CXBMCTinyXML2 doc; - auto* node = doc.NewElement(XML_ROOT_PORTS); - if (node == nullptr) - return; + std::future task = std::async(std::launch::async, [this, ports = std::move(ports)]() { + CXBMCTinyXML2 doc; + auto* node = doc.NewElement(XML_ROOT_PORTS); + if (node == nullptr) + return; - SerializePorts(*node, ports); + SerializePorts(*node, ports); - doc.InsertEndChild(node); + doc.InsertEndChild(node); - std::lock_guard lock(m_saveMutex); - doc.SaveFile(m_xmlPath); - }); + std::lock_guard lock(m_saveMutex); + doc.SaveFile(m_xmlPath); + }); m_saveFutures.emplace_back(std::move(task)); } From 926d7fce757d89a04bfb2982f963d49110b506bc Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 30 Aug 2023 15:37:54 +0200 Subject: [PATCH 113/811] bump Gradle Wrapper to 8.3 --- .../gradle/wrapper/gradle-wrapper.jar | Bin 54208 -> 61574 bytes .../gradle/wrapper/gradle-wrapper.properties | 4 +- tools/android/packaging/gradlew | 296 +++++++++++------- 3 files changed, 186 insertions(+), 114 deletions(-) diff --git a/tools/android/packaging/gradle/wrapper/gradle-wrapper.jar b/tools/android/packaging/gradle/wrapper/gradle-wrapper.jar index 5c7999dd1ea6a74dfa7d40228905e7a97a2c8e39..943f0cbfa754578e88a3dae77fce6e3dea56edbf 100644 GIT binary patch literal 61574 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 ScNgau(U?ep-K_E5zy1%ZQTdPn literal 54208 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,84 +140,105 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save ( ) { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" - -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" From 4878e796c2e245417b0eacfa4ba1150b10f90746 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 29 Aug 2023 12:20:00 +0100 Subject: [PATCH 114/811] [macOS] Fix unique DNS addresses. Use -F for grep. --- xbmc/platform/darwin/osx/network/NetworkOsx.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp b/xbmc/platform/darwin/osx/network/NetworkOsx.cpp index c0d33e2267deb..4d92dc8ecdea3 100644 --- a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp +++ b/xbmc/platform/darwin/osx/network/NetworkOsx.cpp @@ -180,7 +180,7 @@ std::vector CNetworkOsx::GetNameServers() { std::vector result; - FILE* pipe = popen("scutil --dns | grep \"nameserver\" | tail -n2", "r"); + FILE* pipe = popen("scutil --dns | grep \"nameserver\" | uniq | tail -n2", "r"); usleep(100000); if (pipe) { From 1abdd7e674ad7c506647d6a6e8ea8dcfb0ee59d6 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 29 Aug 2023 12:39:07 +0100 Subject: [PATCH 115/811] [MacOS] Use poll with timeout in GetNameServers() avoiding blind sleep --- .../darwin/osx/network/NetworkOsx.cpp | 37 ++++++++++++++----- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp b/xbmc/platform/darwin/osx/network/NetworkOsx.cpp index 4d92dc8ecdea3..61b74e5f711f7 100644 --- a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp +++ b/xbmc/platform/darwin/osx/network/NetworkOsx.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -180,27 +181,43 @@ std::vector CNetworkOsx::GetNameServers() { std::vector result; - FILE* pipe = popen("scutil --dns | grep \"nameserver\" | uniq | tail -n2", "r"); - usleep(100000); + FILE* pipe = popen("scutil --dns | grep -F \"nameserver\" | uniq | tail -n2", "r"); + if (pipe) { - std::vector tmpStr; - char buffer[256] = {'\0'}; - if (fread(buffer, sizeof(char), sizeof(buffer), pipe) > 0 && !ferror(pipe)) + int fineNoPipe = ::fileno(pipe); + + struct pollfd pollFd; + pollFd.fd = fineNoPipe; + pollFd.events = POLLIN; + pollFd.revents = 0; + + int pollResult = poll(&pollFd, 1, 1000); // Poll for 1 second + if (pollResult > 0 && (pollFd.revents & POLLIN)) { - tmpStr = StringUtils::Split(buffer, "\n"); - for (unsigned int i = 0; i < tmpStr.size(); i++) + char buffer[256] = {'\0'}; + if (fgets(buffer, sizeof(buffer), pipe)) { // result looks like this - > ' nameserver[0] : 192.168.1.1' // 2 blank spaces + 13 in 'nameserver[0]' + blank + ':' + blank == 18 :) - if (tmpStr[i].length() >= 18) - result.push_back(tmpStr[i].substr(18)); + const unsigned int nameserverLineSize = 18; + std::vector tmpStr = StringUtils::Split(buffer, "\n"); + for (const std::string& str : tmpStr) + { + if (str.length() >= nameserverLineSize) + { + result.push_back(str.substr(nameserverLineSize)); + } + } } } pclose(pipe); } + if (result.empty()) - CLog::Log(LOGWARNING, "Unable to determine nameserver"); + { + CLog::Log(LOGWARNING, "Unable to determine nameservers"); + } return result; } From 8ddbbcbce6b54d85a6512cd08ae392b4816f61c4 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 29 Aug 2023 12:49:03 +0100 Subject: [PATCH 116/811] [GUI] Return 'None' if DNS entry is not found --- addons/resource.language.en_gb/resources/strings.po | 1 + xbmc/utils/SystemInfo.cpp | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 5298264734441..8c2dcfa3a1a68 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -1091,6 +1091,7 @@ msgstr "" #: xbmc/playlists/SmartPlaylist.cpp #: system/settings/settings.xml #: xbmc/LangInfo.cpp +#: xbmc/utils/Systeminfo.cpp #: ###this documented usage does not cover all usecases yet### msgctxt "#231" msgid "None" diff --git a/xbmc/utils/SystemInfo.cpp b/xbmc/utils/SystemInfo.cpp index 22f2e7a851529..47a0776a23ac4 100644 --- a/xbmc/utils/SystemInfo.cpp +++ b/xbmc/utils/SystemInfo.cpp @@ -450,9 +450,9 @@ std::string CSysInfo::TranslateInfo(int info) const case NETWORK_GATEWAY_ADDRESS: return m_info.gatewayAddress; case NETWORK_DNS1_ADDRESS: - return m_info.dnsServers.size() > 0 ? m_info.dnsServers.at(0) : ""; + return m_info.dnsServers.size() > 0 ? m_info.dnsServers.at(0) : g_localizeStrings.Get(231); case NETWORK_DNS2_ADDRESS: - return m_info.dnsServers.size() > 1 ? m_info.dnsServers.at(1) : ""; + return m_info.dnsServers.size() > 1 ? m_info.dnsServers.at(1) : g_localizeStrings.Get(231); case NETWORK_LINK_STATE: return m_info.networkLinkState; case SYSTEM_OS_VERSION_INFO: From 6872a2de87e664272272377fd4e6b3ed2713036c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 27 Aug 2023 17:44:32 +0200 Subject: [PATCH 117/811] [video] VIDEO_UTILS::CAsyncGetItemsForPlaylist::GetItemsForPlaylist: If sort by date,year,episodenum enforce sort order ascending for playlist items. --- xbmc/video/VideoUtils.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index a32c02a073ef6..56edaad2093b8 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -176,7 +176,14 @@ void CAsyncGetItemsForPlaylist::GetItemsForPlaylist(const std::shared_ptrGetWindowManager().GetActiveWindow() == viewStateWindowId) + { sortDesc = state->GetSortMethod(); + + // It makes no sense to play from younger to older. + if (sortDesc.sortBy == SortByDate || sortDesc.sortBy == SortByYear || + sortDesc.sortBy == SortByEpisodeNumber) + sortDesc.sortOrder = SortOrderAscending; + } else sortDesc = GetSortDescription(*state, items); From eb56ea3003ab5658c4b8c0a3f3653646fc56628a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 17:58:36 +1000 Subject: [PATCH 118/811] [cmake] FindFFMPEG fix ffmpeg-link-wrapper for apple app compilation For osx we converted from an old style .bin executable to the modern app style. This change fixes script to actually function correctly. *Kodi* was too generic, therefore tne preceding folder is added. --- cmake/modules/FindFFMPEG.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index e53a121ecf16a..f46fae040886a 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -105,7 +105,7 @@ macro(buildFFMPEG) endif() file(WRITE ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ffmpeg/ffmpeg-link-wrapper "#!${BASH_COMMAND} -if [[ $@ == *${APP_NAME_LC}.bin* || $@ == *${APP_NAME_LC}${APP_BINARY_SUFFIX}* || $@ == *${APP_NAME_LC}.so* || $@ == *${APP_NAME_LC}-test* ]] +if [[ $@ == *${APP_NAME_LC}.bin* || $@ == *${APP_NAME_LC}${APP_BINARY_SUFFIX}* || $@ == *${APP_NAME_LC}.so* || $@ == *${APP_NAME_LC}-test* || $@ == *MacOS/Kodi* ]] then avcodec=`PKG_CONFIG_PATH=${DEPENDS_PATH}/lib/pkgconfig ${PKG_CONFIG_EXECUTABLE} --libs --static libavcodec` avformat=`PKG_CONFIG_PATH=${DEPENDS_PATH}/lib/pkgconfig ${PKG_CONFIG_EXECUTABLE} --libs --static libavformat` From 4f7458200b7455e08ca1d95f3f3291e679d1b0b4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 18:02:22 +1000 Subject: [PATCH 119/811] [cmake] FindFFMPEG fix definitions differences for target use --- cmake/modules/FindFFMPEG.cmake | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index f46fae040886a..882c8332a4df4 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -127,6 +127,7 @@ fi") set(FFMPEG_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) set(FFMPEG_DEFINITIONS -DUSE_STATIC_FFMPEG=1) set(FFMPEG_FOUND 1) + set(FFMPEG_VERSION ${FFMPEG_VER}) set_target_properties(ffmpeg PROPERTIES FOLDER "External Projects") endmacro() @@ -289,7 +290,8 @@ endif() if(FFMPEG_FOUND) - list(APPEND FFMPEG_DEFINITIONS -DFFMPEG_VER_SHA=\"${FFMPEG_VERSION}\") + set(_ffmpeg_definitions FFMPEG_VER_SHA=${FFMPEG_VERSION}) + list(APPEND FFMPEG_DEFINITIONS -D${_ffmpeg_definitions}) if(NOT TARGET ffmpeg) add_library(ffmpeg ${FFMPEG_LIB_TYPE} IMPORTED) @@ -298,7 +300,7 @@ if(FFMPEG_FOUND) IMPORTED_LOCATION "${FFMPEG_LIBRARIES}" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" INTERFACE_LINK_LIBRARIES "${FFMPEG_LDFLAGS}" - INTERFACE_COMPILE_DEFINITIONS "${FFMPEG_DEFINITIONS}") + INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definitions}") endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ffmpeg) From 4c11ae5c5e667babf42ebdb4a4db5d51caa03ff3 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 18:04:38 +1000 Subject: [PATCH 120/811] [cmake] FindFFMPEG use namespaced target and change to INTERFACE The ffmpeg::ffmpeg target doesnt have a single on disk artifact, so change to INTERFACE in preparation for additional lib targets --- cmake/modules/FindFFMPEG.cmake | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 882c8332a4df4..9c99eba07bd24 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -91,8 +91,6 @@ macro(buildFFMPEG) set(FFMPEG_GENERATOR CMAKE_GENERATOR "Unix Makefiles") endif() - set(FFMPEG_LIB_TYPE STATIC) - BUILD_DEP_TARGET() if(ENABLE_INTERNAL_DAV1D) @@ -269,14 +267,6 @@ else() ${FFMPEG_LIBSWSCALE} ${FFMPEG_LIBSWRESAMPLE} ${FFMPEG_LIBPOSTPROC} ${FFMPEG_LDFLAGS}) - # check if ffmpeg libs are statically linked - set(FFMPEG_LIB_TYPE SHARED) - foreach(_fflib IN LISTS FFMPEG_LIBRARIES) - if(${_fflib} MATCHES ".+\.a$" AND PC_FFMPEG_STATIC_LDFLAGS) - set(FFMPEG_LIB_TYPE STATIC) - break() - endif() - endforeach() endif() else() if(FFMPEG_PATH) @@ -289,21 +279,23 @@ else() endif() if(FFMPEG_FOUND) - set(_ffmpeg_definitions FFMPEG_VER_SHA=${FFMPEG_VERSION}) list(APPEND FFMPEG_DEFINITIONS -D${_ffmpeg_definitions}) - if(NOT TARGET ffmpeg) - add_library(ffmpeg ${FFMPEG_LIB_TYPE} IMPORTED) - set_target_properties(ffmpeg PROPERTIES - FOLDER "External Projects" - IMPORTED_LOCATION "${FFMPEG_LIBRARIES}" - INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${FFMPEG_LDFLAGS}" - INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definitions}") + if(NOT TARGET ffmpeg::ffmpeg) + add_library(ffmpeg::ffmpeg INTERFACE IMPORTED) + set_target_properties(ffmpeg::ffmpeg PROPERTIES + FOLDER "External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" + INTERFACE_LINK_LIBRARIES "${FFMPEG_LDFLAGS}" + INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definition}") + endif() + + if(TARGET ffmpeg) + add_dependencies(ffmpeg::ffmpeg ffmpeg) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ffmpeg) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ffmpeg::ffmpeg) endif() mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_LDFLAGS FFMPEG_DEFINITIONS FFMPEG_FOUND) From 77d79c1e3fcaf5ca84997054760fccec30c42e40 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 18:12:50 +1000 Subject: [PATCH 121/811] [cmake] FindFFMPEG remove uneccessary vars for internal build When building internal, we never build shared, so drop FFMPEG_CREATE_SHARED_LIBRARY and the static definition isnt used anywhere --- cmake/modules/FindFFMPEG.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 9c99eba07bd24..619830b0758b5 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -121,9 +121,7 @@ fi") FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) set(FFMPEG_LINK_EXECUTABLE "${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ffmpeg-link-wrapper -o " PARENT_SCOPE) - set(FFMPEG_CREATE_SHARED_LIBRARY "${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/ffmpeg-link-wrapper -o " PARENT_SCOPE) set(FFMPEG_INCLUDE_DIRS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include) - set(FFMPEG_DEFINITIONS -DUSE_STATIC_FFMPEG=1) set(FFMPEG_FOUND 1) set(FFMPEG_VERSION ${FFMPEG_VER}) From 8be2792144d6c84570611600cc892153846c3e79 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 18:15:00 +1000 Subject: [PATCH 122/811] [cmake] FindFFMPEG remove unnecessary CMAKE_ARG INSTALL_PREFIX is handled as part of the BUILD_DEPS macro --- cmake/modules/FindFFMPEG.cmake | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 619830b0758b5..755b4d5b2611b 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -67,8 +67,7 @@ macro(buildFFMPEG) string(REPLACE ";" "|" FFMPEG_MODULE_PATH "${CMAKE_MODULE_PATH}") set(FFMPEG_LIST_SEPARATOR LIST_SEPARATOR |) - set(CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} - -DCMAKE_MODULE_PATH=${FFMPEG_MODULE_PATH} + set(CMAKE_ARGS -DCMAKE_MODULE_PATH=${FFMPEG_MODULE_PATH} -DFFMPEG_VER=${FFMPEG_VER} -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} -DCORE_PLATFORM_NAME=${CORE_PLATFORM_NAME_LC} From 2490894838a5114440e813e0bf6ebfe93638d87e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 18:24:39 +1000 Subject: [PATCH 123/811] [cmake] FindFFMPEG ENABLE_INTERNAL_FFMPEG dav1d dep fixups --- cmake/modules/FindFFMPEG.cmake | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 755b4d5b2611b..5f0cfbca11bbd 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -37,14 +37,17 @@ macro(buildFFMPEG) include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC ffmpeg) - - SETUP_BUILD_VARS() - + # Check for dependencies - Must be done before SETUP_BUILD_VARS + get_libversion_data("dav1d" "target") + find_package(Dav1d ${LIB_DAV1D_VER} MODULE) if(NOT DAV1D_FOUND) message(STATUS "dav1d not found, internal ffmpeg build will be missing AV1 support!") endif() + set(MODULE_LC ffmpeg) + + SETUP_BUILD_VARS() + set(FFMPEG_OPTIONS -DENABLE_CCACHE=${ENABLE_CCACHE} -DCCACHE_PROGRAM=${CCACHE_PROGRAM} -DENABLE_VAAPI=${ENABLE_VAAPI} @@ -92,7 +95,7 @@ macro(buildFFMPEG) BUILD_DEP_TARGET() - if(ENABLE_INTERNAL_DAV1D) + if(TARGET dav1d) add_dependencies(ffmpeg dav1d) endif() From 0d3efe17617088ec9ebc2e525781b70c1e35701a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 14:47:55 +1000 Subject: [PATCH 124/811] [cmake] FindNFS fix incorrect lib on target --- cmake/modules/FindNFS.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index 11767fc563a07..71e4d8fc12f30 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -104,7 +104,7 @@ if(NFS_FOUND) add_library(NFS::NFS UNKNOWN IMPORTED) set_target_properties(NFS::NFS PROPERTIES - IMPORTED_LOCATION "${NFS_LIBRARY_RELEASE}" + IMPORTED_LOCATION "${NFS_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${NFS_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS "${NFS_DEFINITIONS}") if(TARGET libnfs) From 65cb1423a91ea50e408a6c4d33ad16e48a8f2acf Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 20:41:44 +1000 Subject: [PATCH 125/811] [cmake] FindCrossGUID fix correct IMPORTED_LOCATION_ --- cmake/modules/FindCrossGUID.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 4aceb84dfcd49..f88d3fec61392 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -92,12 +92,12 @@ if(CROSSGUID_FOUND) if(CROSSGUID_LIBRARY_RELEASE) set_target_properties(CrossGUID::CrossGUID PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${CROSSGUID_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${CROSSGUID_LIBRARY_RELEASE}") endif() if(CROSSGUID_LIBRARY_DEBUG) set_target_properties(CrossGUID::CrossGUID PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${CROSSGUID_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${CROSSGUID_LIBRARY_DEBUG}") endif() set_target_properties(CrossGUID::CrossGUID PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${CROSSGUID_INCLUDE_DIRS}") From efb7a656a82783c1c03536cf1629bc2b0b3753b5 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 13 Aug 2023 17:07:06 +1000 Subject: [PATCH 126/811] [cmake] FindSpdlog fix proper import location config type --- cmake/modules/FindSpdlog.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index 0274d140e16ed..dfedcb4e15261 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -108,12 +108,12 @@ if(SPDLOG_FOUND) if(SPDLOG_LIBRARY_RELEASE) set_target_properties(spdlog::spdlog PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${SPDLOG_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${SPDLOG_LIBRARY_RELEASE}") endif() if(SPDLOG_LIBRARY_DEBUG) set_target_properties(spdlog::spdlog PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${SPDLOG_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${SPDLOG_LIBRARY_DEBUG}") endif() set_target_properties(spdlog::spdlog PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}" From 241102716b083c6bdef68306e21a1bcc6280fcc6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 19:55:56 +1000 Subject: [PATCH 127/811] [cmake] FindLibZip workaround config link targets we dont handle right now --- cmake/modules/FindLibZip.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/modules/FindLibZip.cmake b/cmake/modules/FindLibZip.cmake index df2d0428cea6a..6423c29be118e 100644 --- a/cmake/modules/FindLibZip.cmake +++ b/cmake/modules/FindLibZip.cmake @@ -63,6 +63,12 @@ if(LIBZIP_FOUND) if(TARGET libzip) add_dependencies(libzip::zip libzip) endif() + else() + # ToDo: When we correctly import dependencies cmake targets for the following + # BZip2::BZip2, LibLZMA::LibLZMA, GnuTLS::GnuTLS, Nettle::Nettle,ZLIB::ZLIB + # For now, we just override + set_target_properties(libzip::zip PROPERTIES + INTERFACE_LINK_LIBRARIES "") endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libzip::zip) else() From f8573a7247e41cb5c1c7d738a898f5a88c40d348 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 13 Aug 2023 16:13:36 +1000 Subject: [PATCH 128/811] [cmake] wayland protocols generation explicit dependency of lib${APP_NAME_LC} --- cmake/platform/linux/wayland.cmake | 1 - cmake/platform/linux/webos.cmake | 1 - cmake/scripts/linux/ExtraTargets.cmake | 3 +++ cmake/scripts/webos/ExtraTargets.cmake | 3 +++ 4 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmake/platform/linux/wayland.cmake b/cmake/platform/linux/wayland.cmake index 1adcd8fe77626..be00c12bbadfc 100644 --- a/cmake/platform/linux/wayland.cmake +++ b/cmake/platform/linux/wayland.cmake @@ -7,7 +7,6 @@ elseif(APP_RENDER_SYSTEM STREQUAL "gles") list(APPEND PLATFORM_REQUIRED_DEPS OpenGLES EGL) endif() -set(PLATFORM_GLOBAL_TARGET_DEPS generate-wayland-extra-protocols) set(WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR "${CMAKE_CURRENT_BINARY_DIR}") # for wayland-extra-protocols.hpp diff --git a/cmake/platform/linux/webos.cmake b/cmake/platform/linux/webos.cmake index aa6c5d1a276e8..c740ecd27739b 100644 --- a/cmake/platform/linux/webos.cmake +++ b/cmake/platform/linux/webos.cmake @@ -5,7 +5,6 @@ include(${CMAKE_SOURCE_DIR}/cmake/platform/${CORE_SYSTEM_NAME}/wayland.cmake) list(APPEND CORE_PLATFORM_NAME_LC wayland) list(APPEND PLATFORM_REQUIRED_DEPS WaylandProtocolsWebOS PlayerAPIs PlayerFactory WebOSHelpers) -list(APPEND PLATFORM_GLOBAL_TARGET_DEPS generate-wayland-webos-protocols) list(APPEND ARCH_DEFINES -DTARGET_WEBOS) set(ENABLE_PULSEAUDIO OFF CACHE BOOL "" FORCE) set(TARGET_WEBOS TRUE) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 9be92f19d4f83..62c6a400b136b 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -34,4 +34,7 @@ if("wayland" IN_LIST CORE_PLATFORM_NAME_LC) # Dummy target for dependencies add_custom_target(generate-wayland-extra-protocols DEPENDS wayland-extra-protocols.hpp) + # ToDo: turn this into a TARGET OBJECT. For now, a custum target doesnt play nice with + # our PLATFORM_GLOBAL_TARGET_DEPS usage in macros + add_dependencies(lib${APP_NAME_LC} generate-wayland-extra-protocols) endif() diff --git a/cmake/scripts/webos/ExtraTargets.cmake b/cmake/scripts/webos/ExtraTargets.cmake index 6eec8967819cd..3380c6a3be656 100644 --- a/cmake/scripts/webos/ExtraTargets.cmake +++ b/cmake/scripts/webos/ExtraTargets.cmake @@ -7,3 +7,6 @@ add_custom_command(OUTPUT "${WAYLAND_EXTRA_PROTOCOL_GENERATED_DIR}/wayland-webos DEPENDS "${WAYLANDPP_SCANNER}" ${WEBOS_PROTOCOL_XMLS} COMMENT "Generating wayland-webos C++ wrappers") add_custom_target(generate-wayland-webos-protocols DEPENDS wayland-webos-protocols.hpp) +# ToDo: turn this into a TARGET OBJECT. For now, a custum target doesnt play nice with +# our PLATFORM_GLOBAL_TARGET_DEPS usage in macros +add_dependencies(lib${APP_NAME_LC} generate-wayland-webos-protocols) From 8353970be0992421ed4b8efaacbe71cc5d09f2a0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 13 Aug 2023 20:35:58 +1000 Subject: [PATCH 129/811] [cmake] Link TARGETS from property to lib correctly --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ca211a874378..7dbbc53a8983e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -385,7 +385,7 @@ core_add_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/common/*.txt core_add_optional_subdirs_from_filelist(${CMAKE_SOURCE_DIR}/cmake/treedata/optional/common/*.txt ${CMAKE_SOURCE_DIR}/cmake/treedata/optional/${CORE_SYSTEM_NAME}/*.txt) -target_link_libraries(lib${APP_NAME_LC} PUBLIC ${core_DEPENDS} ${SYSTEM_LDFLAGS} ${DEPLIBS} ${CMAKE_DL_LIBS}) +target_link_libraries(lib${APP_NAME_LC} PUBLIC ${core_DEPENDS} ${SYSTEM_LDFLAGS} ${DEPLIBS} ${CMAKE_DL_LIBS} ${GLOBAL_TARGET_DEPS}) set_target_properties(lib${APP_NAME_LC} PROPERTIES PROJECT_LABEL "xbmc") source_group_by_folder(lib${APP_NAME_LC} RELATIVE ${CMAKE_SOURCE_DIR}/xbmc) if(WIN32) From cecee18b552bcf9f7dbb2eec4ff237871d9d5468 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 12 Aug 2023 14:52:02 +1000 Subject: [PATCH 130/811] [cmake] core_add_library enable using TARGETS to lib In particular, this propogates dependency lib include dirs provided from a target to be used as a system include path. This also propagates definitions provided by a target in the same manner [cmake] FindFlatbuffers change to INTERFACE IMPORTED target [cmake] FindRapidJSON implement INTERFACE IMPORTED target [cmake] FindFFMPEG move to targets for ffmpeg libs [cmake] FindTagLib move to full TARGET usage [cmake] Findlibandroidjni add IMPORTED TARGET [cmake] FindNFS Change to using IMPORTED TARGET fully [cmake] FindLibDVD* move to full TARGET use [cmake] FindUUID change to target usage [cmake] FindCrossGUID change to target usage [cmake] FindFmt move to full TARGET usage [cmake] FindSqlite3 move to full TARGET usage [cmake] FindSpdlog move fully to modern TARGET usage [cmake] FindTinyXML2 fixups [cmake] FindPCRE convert to full TARGET usage [cmake] fstrcmp TARGET usage --- cmake/modules/FindCrossGUID.cmake | 129 ++++++-------- cmake/modules/FindFFMPEG.cmake | 167 ++++++++++++++--- cmake/modules/FindFlatBuffers.cmake | 72 ++++---- cmake/modules/FindFmt.cmake | 148 ++++++++-------- cmake/modules/FindFstrcmp.cmake | 75 ++++---- cmake/modules/FindLibAndroidJNI.cmake | 50 +++--- cmake/modules/FindLibDvdCSS.cmake | 6 +- cmake/modules/FindLibDvdNav.cmake | 15 +- cmake/modules/FindLibDvdRead.cmake | 13 +- cmake/modules/FindNFS.cmake | 192 +++++++++++--------- cmake/modules/FindPCRE.cmake | 246 ++++++++++++++------------ cmake/modules/FindRapidJSON.cmake | 87 +++++---- cmake/modules/FindSpdlog.cmake | 181 +++++++++++-------- cmake/modules/FindSqlite3.cmake | 44 ++--- cmake/modules/FindTagLib.cmake | 131 ++++++++------ cmake/modules/FindTinyXML2.cmake | 173 +++++++++--------- cmake/modules/FindUUID.cmake | 44 ++--- cmake/scripts/common/Macros.cmake | 4 + xbmc/filesystem/CMakeLists.txt | 2 +- xbmc/filesystem/test/CMakeLists.txt | 2 +- xbmc/interfaces/swig/CMakeLists.txt | 2 + 21 files changed, 979 insertions(+), 804 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index f88d3fec61392..59d0cebc6a6e5 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -2,110 +2,97 @@ # ------- # Finds the CrossGUID library # -# This will define the following variables:: -# -# CROSSGUID_FOUND_FOUND - system has CrossGUID -# CROSSGUID_INCLUDE_DIRS - the CrossGUID include directory -# CROSSGUID_LIBRARIES - the CrossGUID libraries -# CROSSGUID_DEFINITIONS - cmake definitions required -# -# and the following imported targets:: +# This will define the following target: # # CrossGUID::CrossGUID - The CrossGUID library -if(ENABLE_INTERNAL_CROSSGUID) - include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT TARGET CrossGUID::CrossGUID) + if(ENABLE_INTERNAL_CROSSGUID) + include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC crossguid) + set(MODULE_LC crossguid) - SETUP_BUILD_VARS() + SETUP_BUILD_VARS() - set(CROSSGUID_VERSION ${${MODULE}_VER}) - set(CROSSGUID_DEFINITIONS -DHAVE_NEW_CROSSGUID) - set(CROSSGUID_DEBUG_POSTFIX "-dgb") + set(CROSSGUID_VERSION ${${MODULE}_VER}) + set(CROSSGUID_DEBUG_POSTFIX "-dgb") - if(ANDROID) - list(APPEND CROSSGUID_DEFINITIONS -DGUID_ANDROID) - endif() + set(_crossguid_definitions HAVE_NEW_CROSSGUID) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/001-fix-unused-function.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/002-disable-Wall-error.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/003-add-cstdint-include.patch") + if(ANDROID) + list(APPEND _crossguid_definitions GUID_ANDROID) + endif() - generate_patchcommand("${patches}") + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/001-fix-unused-function.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/002-disable-Wall-error.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/003-add-cstdint-include.patch") - set(CMAKE_ARGS -DCROSSGUID_TESTS=OFF - -DDISABLE_WALL=ON) + generate_patchcommand("${patches}") - BUILD_DEP_TARGET() + set(CMAKE_ARGS -DCROSSGUID_TESTS=OFF + -DDISABLE_WALL=ON) -else() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_CROSSGUID crossguid QUIET) - set(CROSSGUID_VERSION ${PC_CROSSGUID_VERSION}) - endif() + BUILD_DEP_TARGET() - if(CROSSGUID_FOUND) - find_path(CROSSGUID_INCLUDE_DIR NAMES crossguid/guid.hpp guid.h - PATHS ${PC_CROSSGUID_INCLUDEDIR}) + else() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CROSSGUID crossguid QUIET) + set(CROSSGUID_VERSION ${PC_CROSSGUID_VERSION}) + endif() + find_path(CROSSGUID_INCLUDE_DIR NAMES crossguid/guid.hpp guid.h + PATHS ${PC_CROSSGUID_INCLUDEDIR}) find_library(CROSSGUID_LIBRARY_RELEASE NAMES crossguid - PATHS ${PC_CROSSGUID_LIBDIR}) + HINTS ${PC_CROSSGUID_LIBDIR}) find_library(CROSSGUID_LIBRARY_DEBUG NAMES crossguidd crossguid-dgb - PATHS ${PC_CROSSGUID_LIBDIR}) - else() - find_path(CROSSGUID_INCLUDE_DIR NAMES crossguid/guid.hpp guid.h) - find_library(CROSSGUID_LIBRARY_RELEASE NAMES crossguid) - find_library(CROSSGUID_LIBRARY_DEBUG NAMES crossguidd) - endif() -endif() + HINTS ${PC_CROSSGUID_LIBDIR}) -# Select relevant lib build type (ie CROSSGUID_LIBRARY_RELEASE or CROSSGUID_LIBRARY_DEBUG) -include(SelectLibraryConfigurations) -select_library_configurations(CROSSGUID) + # NEW_CROSSGUID >= 0.2.0 release + if(EXISTS "${CROSSGUID_INCLUDE_DIR}/crossguid/guid.hpp") + list(APPEND _crossguid_definitions HAVE_NEW_CROSSGUID) + endif() + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CrossGUID - REQUIRED_VARS CROSSGUID_LIBRARY CROSSGUID_INCLUDE_DIR - VERSION_VAR CROSSGUID_VERSION) + # Select relevant lib build type (ie CROSSGUID_LIBRARY_RELEASE or CROSSGUID_LIBRARY_DEBUG) + include(SelectLibraryConfigurations) + select_library_configurations(CROSSGUID) -if(CROSSGUID_FOUND) - set(CROSSGUID_LIBRARIES ${CROSSGUID_LIBRARY}) - set(CROSSGUID_INCLUDE_DIRS ${CROSSGUID_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CrossGUID + REQUIRED_VARS CROSSGUID_LIBRARY CROSSGUID_INCLUDE_DIR + VERSION_VAR CROSSGUID_VERSION) - # NEW_CROSSGUID >= 0.2.0 release - if(EXISTS "${CROSSGUID_INCLUDE_DIR}/crossguid/guid.hpp") - list(APPEND CROSSGUID_DEFINITIONS -DHAVE_NEW_CROSSGUID) + add_library(CrossGUID::CrossGUID UNKNOWN IMPORTED) + if(CROSSGUID_LIBRARY_RELEASE) + set_target_properties(CrossGUID::CrossGUID PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${CROSSGUID_LIBRARY_RELEASE}") endif() + if(CROSSGUID_LIBRARY_DEBUG) + set_target_properties(CrossGUID::CrossGUID PROPERTIES + IMPORTED_CONFIGURATIONS DEBUG + IMPORTED_LOCATION_DEBUG "${CROSSGUID_LIBRARY_DEBUG}") + endif() + set_target_properties(CrossGUID::CrossGUID PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${CROSSGUID_INCLUDE_DIRS}" + INTERFACE_COMPILE_DEFINITIONS "${_crossguid_definitions}") if(UNIX AND NOT (APPLE OR ANDROID)) # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html set(FPHSA_NAME_MISMATCHED 1) find_package(UUID REQUIRED) unset(FPHSA_NAME_MISMATCHED) - list(APPEND CROSSGUID_INCLUDE_DIRS ${UUID_INCLUDE_DIRS}) - list(APPEND CROSSGUID_LIBRARIES ${UUID_LIBRARIES}) - endif() - if(NOT TARGET CrossGUID::CrossGUID) - add_library(CrossGUID::CrossGUID UNKNOWN IMPORTED) - if(CROSSGUID_LIBRARY_RELEASE) - set_target_properties(CrossGUID::CrossGUID PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${CROSSGUID_LIBRARY_RELEASE}") + if(TARGET UUID::UUID) + add_dependencies(CrossGUID::CrossGUID UUID::UUID) + target_link_libraries(CrossGUID::CrossGUID INTERFACE UUID::UUID) endif() - if(CROSSGUID_LIBRARY_DEBUG) - set_target_properties(CrossGUID::CrossGUID PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${CROSSGUID_LIBRARY_DEBUG}") - endif() - set_target_properties(CrossGUID::CrossGUID PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${CROSSGUID_INCLUDE_DIRS}") endif() + if(TARGET crossguid) add_dependencies(CrossGUID::CrossGUID crossguid) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CrossGUID::CrossGUID) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CrossGUID::CrossGUID) endif() mark_as_advanced(CROSSGUID_INCLUDE_DIR CROSSGUID_LIBRARY) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 5f0cfbca11bbd..96d9533406ae4 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -18,17 +18,9 @@ # usage: -DWITH_FFMPEG=/path/to/ffmpeg_install_prefix # # -------- -# This module will define the following variables: +# This will define the following target: # -# FFMPEG_FOUND - system has FFmpeg -# FFMPEG_INCLUDE_DIRS - FFmpeg include directory -# FFMPEG_LIBRARIES - FFmpeg libraries -# FFMPEG_DEFINITIONS - pre-processor definitions -# FFMPEG_LDFLAGS - linker flags -# -# and the following imported targets:: -# -# ffmpeg - The FFmpeg libraries +# ffmpeg::ffmpeg - The FFmpeg interface target # -------- # @@ -127,7 +119,45 @@ fi") set(FFMPEG_FOUND 1) set(FFMPEG_VERSION ${FFMPEG_VER}) - set_target_properties(ffmpeg PROPERTIES FOLDER "External Projects") + # Whilst we use ffmpeg-link-wrapper, we only need INTERFACE at most, and possibly + # just not at all. However this gives target consistency with external FFMPEG usage + # The benefit and reason to continue to use the wrapper is to automate the collection + # of the actual linker flags from pkg-config lookup + + add_library(ffmpeg::libavcodec INTERFACE IMPORTED) + set_target_properties(ffmpeg::libavcodec PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libavfilter INTERFACE IMPORTED) + set_target_properties(ffmpeg::libavfilter PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libavformat INTERFACE IMPORTED) + set_target_properties(ffmpeg::libavformat PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libavutil INTERFACE IMPORTED) + set_target_properties(ffmpeg::libavutil PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libswscale INTERFACE IMPORTED) + set_target_properties(ffmpeg::libswscale PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libswresample INTERFACE IMPORTED) + set_target_properties(ffmpeg::libswresample PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") + + add_library(ffmpeg::libpostproc INTERFACE IMPORTED) + set_target_properties(ffmpeg::libpostproc PROPERTIES + FOLDER "FFMPEG - External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") endmacro() @@ -173,7 +203,6 @@ else() if(PKG_CONFIG_FOUND) pkg_check_modules(PC_FFMPEG ${FFMPEG_PKGS} QUIET) - string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LDFLAGS "${PC_FFMPEG_LDFLAGS}") endif() find_path(FFMPEG_INCLUDE_DIRS libavcodec/avcodec.h libavfilter/avfilter.h libavformat/avformat.h @@ -191,6 +220,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBAVCODEC NAMES avcodec libavcodec PATH_SUFFIXES ffmpeg/libavcodec) + if(FFMPEG_LIBAVCODEC) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libavcodec${_avcodec_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libavcodec UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libavcodec PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBAVCODEC}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBAVFILTER NAMES avfilter libavfilter PATH_SUFFIXES ffmpeg/libavfilter @@ -198,6 +240,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBAVFILTER NAMES avfilter libavfilter PATH_SUFFIXES ffmpeg/libavfilter) + if(FFMPEG_LIBAVFILTER) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libavfilter${_avfilter_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libavfilter UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libavfilter PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBAVFILTER}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBAVFORMAT NAMES avformat libavformat PATH_SUFFIXES ffmpeg/libavformat @@ -205,6 +260,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBAVFORMAT NAMES avformat libavformat PATH_SUFFIXES ffmpeg/libavformat) + if(FFMPEG_LIBAVFORMAT) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libavformat${_avformat_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libavformat UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libavformat PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBAVFORMAT}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBAVUTIL NAMES avutil libavutil PATH_SUFFIXES ffmpeg/libavutil @@ -212,6 +280,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBAVUTIL NAMES avutil libavutil PATH_SUFFIXES ffmpeg/libavutil) + if(FFMPEG_LIBAVUTIL) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libavutil${_avutil_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libavutil UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libavutil PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBAVUTIL}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBSWSCALE NAMES swscale libswscale PATH_SUFFIXES ffmpeg/libswscale @@ -219,6 +300,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBSWSCALE NAMES swscale libswscale PATH_SUFFIXES ffmpeg/libswscale) + if(FFMPEG_LIBSWSCALE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libswscale${_swscale_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libswscale UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libswscale PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBSWSCALE}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBSWRESAMPLE NAMES swresample libswresample PATH_SUFFIXES ffmpeg/libswresample @@ -226,6 +320,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBSWRESAMPLE NAMES NAMES swresample libswresample PATH_SUFFIXES ffmpeg/libswresample) + if(FFMPEG_LIBSWRESAMPLE) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libswresample${_swresample_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libswresample UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libswresample PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBSWRESAMPLE}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + find_library(FFMPEG_LIBPOSTPROC NAMES postproc libpostproc PATH_SUFFIXES ffmpeg/libpostproc @@ -233,6 +340,19 @@ else() NO_DEFAULT_PATH) find_library(FFMPEG_LIBPOSTPROC NAMES postproc libpostproc PATH_SUFFIXES ffmpeg/libpostproc) + if(FFMPEG_LIBPOSTPROC) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FFMPEG_LIB libpostproc${_postproc_ver} QUIET) + string(REGEX REPLACE "framework;" "framework " PC_FFMPEG_LIB_LDFLAGS "${PC_FFMPEG_LIB_LDFLAGS}") + endif() + add_library(ffmpeg::libpostproc UNKNOWN IMPORTED) + set_target_properties(ffmpeg::libpostproc PROPERTIES + FOLDER "FFMPEG - External Projects" + IMPORTED_LOCATION "${FFMPEG_LIBPOSTPROC}" + INTERFACE_LINK_LIBRARIES "${PC_FFMPEG_LIB_LDFLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}") + endif() + if((PC_FFMPEG_FOUND AND PC_FFMPEG_libavcodec_VERSION AND PC_FFMPEG_libavfilter_VERSION @@ -259,15 +379,6 @@ else() FFMPEG_VERSION FAIL_MESSAGE "FFmpeg ${REQUIRED_FFMPEG_VERSION} not found, please consider using -DENABLE_INTERNAL_FFMPEG=ON") - if(FFMPEG_FOUND) - set(FFMPEG_LDFLAGS ${PC_FFMPEG_LDFLAGS} CACHE STRING "ffmpeg linker flags") - - set(FFMPEG_LIBRARIES ${FFMPEG_LIBAVCODEC} ${FFMPEG_LIBAVFILTER} - ${FFMPEG_LIBAVFORMAT} ${FFMPEG_LIBAVUTIL} - ${FFMPEG_LIBSWSCALE} ${FFMPEG_LIBSWRESAMPLE} - ${FFMPEG_LIBPOSTPROC} ${FFMPEG_LDFLAGS}) - - endif() else() if(FFMPEG_PATH) message(FATAL_ERROR "FFmpeg not found, please consider using -DENABLE_INTERNAL_FFMPEG=ON") @@ -280,17 +391,23 @@ endif() if(FFMPEG_FOUND) set(_ffmpeg_definitions FFMPEG_VER_SHA=${FFMPEG_VERSION}) - list(APPEND FFMPEG_DEFINITIONS -D${_ffmpeg_definitions}) if(NOT TARGET ffmpeg::ffmpeg) add_library(ffmpeg::ffmpeg INTERFACE IMPORTED) set_target_properties(ffmpeg::ffmpeg PROPERTIES FOLDER "External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" - INTERFACE_LINK_LIBRARIES "${FFMPEG_LDFLAGS}" - INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definition}") + INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definitions}") endif() + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavcodec) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavfilter) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavformat) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libavutil) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libswscale) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libswresample) + target_link_libraries(ffmpeg::ffmpeg INTERFACE ffmpeg::libpostproc) + if(TARGET ffmpeg) add_dependencies(ffmpeg::ffmpeg ffmpeg) endif() @@ -298,4 +415,4 @@ if(FFMPEG_FOUND) set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ffmpeg::ffmpeg) endif() -mark_as_advanced(FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES FFMPEG_LDFLAGS FFMPEG_DEFINITIONS FFMPEG_FOUND) +mark_as_advanced(FFMPEG_FOUND) diff --git a/cmake/modules/FindFlatBuffers.cmake b/cmake/modules/FindFlatBuffers.cmake index fffac4fae7390..4c0a28b63581e 100644 --- a/cmake/modules/FindFlatBuffers.cmake +++ b/cmake/modules/FindFlatBuffers.cmake @@ -10,47 +10,44 @@ find_package(FlatC REQUIRED) -if(ENABLE_INTERNAL_FLATBUFFERS) - include(cmake/scripts/common/ModuleHelpers.cmake) - - set(MODULE_LC flatbuffers) - - SETUP_BUILD_VARS() - - # Override build type detection and always build as release - set(FLATBUFFERS_BUILD_TYPE Release) - - set(CMAKE_ARGS -DFLATBUFFERS_CODE_COVERAGE=OFF - -DFLATBUFFERS_BUILD_TESTS=OFF - -DFLATBUFFERS_INSTALL=ON - -DFLATBUFFERS_BUILD_FLATLIB=OFF - -DFLATBUFFERS_BUILD_FLATC=OFF - -DFLATBUFFERS_BUILD_FLATHASH=OFF - -DFLATBUFFERS_BUILD_GRPCTEST=OFF - -DFLATBUFFERS_BUILD_SHAREDLIB=OFF - "${EXTRA_ARGS}") - set(BUILD_BYPRODUCTS ${DEPENDS_PATH}/include/flatbuffers/flatbuffers.h) - - BUILD_DEP_TARGET() -else() - find_path(FLATBUFFERS_INCLUDE_DIR NAMES flatbuffers/flatbuffers.h) -endif() +if(NOT TARGET flatbuffers::flatbuffers) + if(ENABLE_INTERNAL_FLATBUFFERS) + include(cmake/scripts/common/ModuleHelpers.cmake) + + set(MODULE_LC flatbuffers) + + SETUP_BUILD_VARS() + + # Override build type detection and always build as release + set(FLATBUFFERS_BUILD_TYPE Release) + + set(CMAKE_ARGS -DFLATBUFFERS_CODE_COVERAGE=OFF + -DFLATBUFFERS_BUILD_TESTS=OFF + -DFLATBUFFERS_INSTALL=ON + -DFLATBUFFERS_BUILD_FLATLIB=OFF + -DFLATBUFFERS_BUILD_FLATC=OFF + -DFLATBUFFERS_BUILD_FLATHASH=OFF + -DFLATBUFFERS_BUILD_GRPCTEST=OFF + -DFLATBUFFERS_BUILD_SHAREDLIB=OFF + "${EXTRA_ARGS}") + set(BUILD_BYPRODUCTS ${DEPENDS_PATH}/include/flatbuffers/flatbuffers.h) + + BUILD_DEP_TARGET() + else() + find_path(FLATBUFFERS_INCLUDE_DIR NAMES flatbuffers/flatbuffers.h) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(FlatBuffers - REQUIRED_VARS FLATBUFFERS_INCLUDE_DIR - VERSION_VAR FLATBUFFERS_VER) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(FlatBuffers + REQUIRED_VARS FLATBUFFERS_INCLUDE_DIR + VERSION_VAR FLATBUFFERS_VER) -if(FLATBUFFERS_FOUND) set(FLATBUFFERS_MESSAGES_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/cores/RetroPlayer/messages CACHE INTERNAL "Generated FlatBuffer headers") - set(FLATBUFFERS_INCLUDE_DIRS ${FLATBUFFERS_INCLUDE_DIR} ${FLATBUFFERS_MESSAGES_INCLUDE_DIR}) - if(NOT TARGET flatbuffers::flatbuffers) - add_library(flatbuffers::flatbuffers UNKNOWN IMPORTED) - set_target_properties(flatbuffers::flatbuffers PROPERTIES - FOLDER "External Projects" - INTERFACE_INCLUDE_DIRECTORIES ${FLATBUFFERS_INCLUDE_DIR}) - endif() + add_library(flatbuffers::flatbuffers INTERFACE IMPORTED) + set_target_properties(flatbuffers::flatbuffers PROPERTIES + FOLDER "External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR};${FLATBUFFERS_MESSAGES_INCLUDE_DIR}") add_dependencies(flatbuffers::flatbuffers flatbuffers::flatc) @@ -58,6 +55,7 @@ if(FLATBUFFERS_FOUND) add_dependencies(flatbuffers::flatbuffers flatbuffers) endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP flatbuffers::flatbuffers) + endif() mark_as_advanced(FLATBUFFERS_INCLUDE_DIR) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index 7f0023be0512a..4c50291efb9e5 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -2,13 +2,7 @@ # ------- # Finds the Fmt library # -# This will define the following variables:: -# -# FMT_FOUND - system has Fmt -# FMT_INCLUDE_DIRS - the Fmt include directory -# FMT_LIBRARIES - the Fmt libraries -# -# and the following imported targets:: +# This will define the following target: # # fmt::fmt - The Fmt library @@ -21,27 +15,27 @@ set(FORCE_BUILD OFF) # If target exists, no need to rerun find # Allows a module that may be a dependency for multiple libraries to just be executed # once to populate all required variables/targets -if((NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) AND NOT TARGET fmt) +if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) - # Build if ENABLE_INTERNAL_FMT, or if required version in find_package call is greater - # than already found FMT_VERSION from a previous find_package call - if(ENABLE_INTERNAL_FMT OR (Fmt_FIND_REQUIRED AND FMT_VERSION VERSION_LESS Fmt_FIND_VERSION)) + include(cmake/scripts/common/ModuleHelpers.cmake) - include(cmake/scripts/common/ModuleHelpers.cmake) + set(MODULE_LC fmt) - set(MODULE_LC fmt) + SETUP_BUILD_VARS() - SETUP_BUILD_VARS() + # Check for existing FMT. If version >= FMT-VERSION file version, dont build + find_package(FMT CONFIG QUIET) - # Check for existing FMT. If version >= FMT-VERSION file version, dont build - find_package(FMT CONFIG QUIET) + # Build if ENABLE_INTERNAL_FMT, or if required version in find_package call is greater + # than already found FMT_VERSION from a previous find_package call + if((Fmt_FIND_REQUIRED AND FMT_VERSION VERSION_LESS Fmt_FIND_VERSION AND ENABLE_INTERNAL_FMT) OR + (FMT_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_FMT) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_FMT)) if(Fmt_FIND_VERSION) if(FMT_VERSION VERSION_LESS ${Fmt_FIND_VERSION}) set(FORCE_BUILD ON) endif() - else() - set(FORCE_BUILD ON) endif() if(${FORCE_BUILD} OR FMT_VERSION VERSION_LESS ${${MODULE}_VER}) @@ -71,31 +65,76 @@ if((NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) AND NOT TARGET fmt) BUILD_DEP_TARGET() else() - # Populate paths for find_package_handle_standard_args - find_path(FMT_INCLUDE_DIR NAMES fmt/format.h) - find_library(FMT_LIBRARY_RELEASE NAMES fmt) - find_library(FMT_LIBRARY_DEBUG NAMES fmtd) + if(NOT TARGET fmt::fmt) + set(FMT_PKGCONFIG_CHECK ON) + endif() endif() else() - find_package(FMT 6.1.2 CONFIG REQUIRED QUIET) + if(NOT TARGET fmt::fmt) + set(FMT_PKGCONFIG_CHECK ON) + endif() + endif() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_FMT libfmt QUIET) - if(PC_FMT_VERSION AND NOT FMT_VERSION) + if(NOT TARGET fmt::fmt) + if(FMT_PKGCONFIG_CHECK) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FMT libfmt QUIET) set(FMT_VERSION ${PC_FMT_VERSION}) endif() + + find_path(FMT_INCLUDE_DIR NAMES fmt/format.h + PATHS ${PC_FMT_INCLUDEDIR}) + + find_library(FMT_LIBRARY_RELEASE NAMES fmt + PATHS ${PC_FMT_LIBDIR}) + find_library(FMT_LIBRARY_DEBUG NAMES fmtd + PATHS ${PC_FMT_LIBDIR}) endif() - find_path(FMT_INCLUDE_DIR NAMES fmt/format.h - PATHS ${PC_FMT_INCLUDEDIR}) + add_library(fmt::fmt UNKNOWN IMPORTED) + if(FMT_LIBRARY_RELEASE) + set_target_properties(fmt::fmt PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${FMT_LIBRARY_RELEASE}") + endif() + if(FMT_LIBRARY_DEBUG) + set_target_properties(fmt::fmt PROPERTIES + IMPORTED_CONFIGURATIONS DEBUG + IMPORTED_LOCATION_DEBUG "${FMT_LIBRARY_DEBUG}") + endif() + set_target_properties(fmt::fmt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") - find_library(FMT_LIBRARY_RELEASE NAMES fmt - PATHS ${PC_FMT_LIBDIR}) - find_library(FMT_LIBRARY_DEBUG NAMES fmtd - PATHS ${PC_FMT_LIBDIR}) + if(TARGET fmt) + add_dependencies(fmt::fmt fmt) + endif() + endif() + # If a force build is done, let any calling packages know they may want to rebuild + if(FORCE_BUILD) + set_target_properties(fmt::fmt PROPERTIES LIB_BUILD ON) endif() + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is fmtConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_FMT_CONFIGURATIONS fmt::fmt IMPORTED_CONFIGURATIONS) + foreach(_fmt_config IN LISTS _FMT_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_fmt_config} _fmt_config_UPPER) + if((NOT ${_fmt_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_fmt_config_UPPER} STREQUAL "DEBUG")) + get_target_property(FMT_LIBRARY_RELEASE fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) + else() + get_target_property(FMT_LIBRARY_${_fmt_config_UPPER} fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) + endif() + endforeach() + + get_target_property(FMT_INCLUDE_DIR fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) + include(SelectLibraryConfigurations) select_library_configurations(FMT) @@ -104,46 +143,13 @@ if((NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) AND NOT TARGET fmt) REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR VERSION_VAR FMT_VERSION) - if(FMT_FOUND) - set(FMT_LIBRARIES ${FMT_LIBRARY}) - set(FMT_INCLUDE_DIRS ${FMT_INCLUDE_DIR}) - - # Reorder this to allow handling of FMT_FORCE_BUILD and not duplicate in property - if(NOT TARGET fmt::fmt) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fmt::fmt) - endif() - - if(NOT TARGET fmt::fmt OR FORCE_BUILD) - if(NOT TARGET fmt::fmt) - add_library(fmt::fmt UNKNOWN IMPORTED) - endif() - - if(FMT_LIBRARY_RELEASE) - set_target_properties(fmt::fmt PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${FMT_LIBRARY_RELEASE}") - endif() - if(FMT_LIBRARY_DEBUG) - set_target_properties(fmt::fmt PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${FMT_LIBRARY_DEBUG}") - endif() - set_target_properties(fmt::fmt PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") + # Check whether we already have tinyxml2::tinyxml2 target added to dep property list + get_property(CHECK_INTERNAL_DEPS GLOBAL PROPERTY INTERNAL_DEPS_PROP) + list(FIND CHECK_INTERNAL_DEPS "fmt::fmt" FMT_PROP_FOUND) - # If a force build is done, let any calling packages know they may want to rebuild - if(FORCE_BUILD) - set_target_properties(fmt::fmt PROPERTIES LIB_BUILD ON) - endif() - endif() - if(TARGET fmt) - add_dependencies(fmt::fmt fmt) - endif() - else() - if(FMT_FIND_REQUIRED) - message(FATAL_ERROR "Fmt lib not found. Maybe use -DENABLE_INTERNAL_FMT=ON") - endif() + # list(FIND) returns -1 if search item not found + if(FMT_PROP_FOUND STREQUAL "-1") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fmt::fmt) endif() - mark_as_advanced(FMT_INCLUDE_DIR FMT_LIBRARY) endif() diff --git a/cmake/modules/FindFstrcmp.cmake b/cmake/modules/FindFstrcmp.cmake index d2625f1020eb3..2bf412c2ddb9e 100644 --- a/cmake/modules/FindFstrcmp.cmake +++ b/cmake/modules/FindFstrcmp.cmake @@ -3,61 +3,58 @@ # -------- # Finds the fstrcmp library # -# This will define the following variables:: +# This will define the following target: # -# FSTRCMP_FOUND - system has libfstrcmp -# FSTRCMP_INCLUDE_DIRS - the libfstrcmp include directory -# FSTRCMP_LIBRARIES - the libfstrcmp libraries +# fstrcmp::fstrcmp - The fstrcmp library # -if(ENABLE_INTERNAL_FSTRCMP) - find_program(LIBTOOL libtool REQUIRED) - include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT TARGET fstrcmp::fstrcmp) + if(ENABLE_INTERNAL_FSTRCMP) + find_program(LIBTOOL libtool REQUIRED) + include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC fstrcmp) + set(MODULE_LC fstrcmp) - SETUP_BUILD_VARS() + SETUP_BUILD_VARS() - find_program(AUTORECONF autoreconf REQUIRED) + find_program(AUTORECONF autoreconf REQUIRED) - set(CONFIGURE_COMMAND ${AUTORECONF} -vif - COMMAND ./configure --prefix ${DEPENDS_PATH}) - set(BUILD_COMMAND make lib/libfstrcmp.la) - set(BUILD_IN_SOURCE 1) - set(INSTALL_COMMAND make install-libdir install-include) + set(CONFIGURE_COMMAND ${AUTORECONF} -vif + COMMAND ./configure --prefix ${DEPENDS_PATH}) + set(BUILD_COMMAND make lib/libfstrcmp.la) + set(BUILD_IN_SOURCE 1) + set(INSTALL_COMMAND make install-libdir install-include) - BUILD_DEP_TARGET() -else() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_FSTRCMP fstrcmp QUIET) - endif() + BUILD_DEP_TARGET() + else() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FSTRCMP fstrcmp QUIET) + endif() - find_path(FSTRCMP_INCLUDE_DIR NAMES fstrcmp.h - PATHS ${PC_FSTRCMP_INCLUDEDIR}) + find_path(FSTRCMP_INCLUDE_DIR NAMES fstrcmp.h + PATHS ${PC_FSTRCMP_INCLUDEDIR}) - find_library(FSTRCMP_LIBRARY NAMES fstrcmp - PATHS ${PC_FSTRCMP_LIBDIR}) + find_library(FSTRCMP_LIBRARY NAMES fstrcmp + PATHS ${PC_FSTRCMP_LIBDIR}) - set(FSTRCMP_VER ${PC_FSTRCMP_VERSION}) -endif() + set(FSTRCMP_VER ${PC_FSTRCMP_VERSION}) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Fstrcmp - REQUIRED_VARS FSTRCMP_LIBRARY FSTRCMP_INCLUDE_DIR - VERSION_VAR FSTRCMP_VER) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fstrcmp + REQUIRED_VARS FSTRCMP_LIBRARY FSTRCMP_INCLUDE_DIR + VERSION_VAR FSTRCMP_VER) -if(FSTRCMP_FOUND) - set(FSTRCMP_INCLUDE_DIRS ${FSTRCMP_INCLUDE_DIR}) - set(FSTRCMP_LIBRARIES ${FSTRCMP_LIBRARY}) + add_library(fstrcmp::fstrcmp UNKNOWN IMPORTED) + set_target_properties(fstrcmp::fstrcmp PROPERTIES + IMPORTED_LOCATION "${FSTRCMP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FSTRCMP_INCLUDE_DIR}") - if(NOT TARGET fstrcmp) - add_library(fstrcmp UNKNOWN IMPORTED) - set_target_properties(fstrcmp PROPERTIES - IMPORTED_LOCATION "${FSTRCMP_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${FSTRCMP_INCLUDE_DIR}") + if(TARGET fstrcmp) + add_dependencies(fstrcmp::fstrcmp fstrcmp) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fstrcmp) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fstrcmp::fstrcmp) endif() mark_as_advanced(FSTRCMP_INCLUDE_DIR FSTRCMP_LIBRARY) diff --git a/cmake/modules/FindLibAndroidJNI.cmake b/cmake/modules/FindLibAndroidJNI.cmake index 506e70fe095d6..8d240b7e2a193 100644 --- a/cmake/modules/FindLibAndroidJNI.cmake +++ b/cmake/modules/FindLibAndroidJNI.cmake @@ -2,39 +2,39 @@ # ------- # Finds the LibAndroidJNI library # -# This will define the following variables:: +# This will define the following target: # -# LIBANDROIDJNI_FOUND - system has LibAndroidJNI -# LIBANDROIDJNI_INCLUDE_DIRS - the LibAndroidJNI include directory -# LIBANDROIDJNI_LIBRARIES - the LibAndroidJNI libraries -# -# and the following imported targets:: -# -# libandroidjni - The LibAndroidJNI library +# libandroidjni::libandroidjni - The LibAndroidJNI library + +if(NOT TARGET libandroidjni::libandroidjni) + include(cmake/scripts/common/ModuleHelpers.cmake) + + set(MODULE_LC libandroidjni) -include(cmake/scripts/common/ModuleHelpers.cmake) + SETUP_BUILD_VARS() -set(MODULE_LC libandroidjni) + set(LIBANDROIDJNI_BUILD_TYPE Release) -SETUP_BUILD_VARS() + # We still need to supply SOMETHING to CMAKE_ARGS to initiate a cmake BUILD_DEP_TARGET + # Setting cmake_build_type twice wont cause issues + set(CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release) -set(LIBANDROIDJNI_BUILD_TYPE Release) + BUILD_DEP_TARGET() -# We still need to supply SOMETHING to CMAKE_ARGS to initiate a cmake BUILD_DEP_TARGET -# Setting cmake_build_type twice wont cause issues -set(CMAKE_ARGS -DCMAKE_BUILD_TYPE=Release) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibAndroidJNI + REQUIRED_VARS LIBANDROIDJNI_LIBRARY LIBANDROIDJNI_INCLUDE_DIR + VERSION_VAR LIBANDROIDJNI_VER) -BUILD_DEP_TARGET() + add_library(libandroidjni::libandroidjni STATIC IMPORTED) + set_target_properties(libandroidjni::libandroidjni PROPERTIES + FOLDER "External Projects" + IMPORTED_LOCATION "${LIBANDROIDJNI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBANDROIDJNI_INCLUDE_DIR}") -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibAndroidJNI - REQUIRED_VARS LIBANDROIDJNI_LIBRARY LIBANDROIDJNI_INCLUDE_DIR - VERSION_VAR LIBANDROIDJNI_VER) + add_dependencies(libandroidjni::libandroidjni libandroidjni) -if(LIBANDROIDJNI_FOUND) - set(LIBANDROIDJNI_LIBRARIES ${LIBANDROIDJNI_LIBRARY}) - set(LIBANDROIDJNI_INCLUDE_DIRS ${LIBANDROIDJNI_INCLUDE_DIR}) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libandroidjni::libandroidjni) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libandroidjni) + mark_as_advanced(LIBANDROIDJNI_INCLUDE_DIR LIBANDROIDJNI_LIBRARY) endif() -mark_as_advanced(LIBANDROIDJNI_INCLUDE_DIR LIBANDROIDJNI_LIBRARY) diff --git a/cmake/modules/FindLibDvdCSS.cmake b/cmake/modules/FindLibDvdCSS.cmake index ee3b7536beadf..df7ec6ad1f1b4 100644 --- a/cmake/modules/FindLibDvdCSS.cmake +++ b/cmake/modules/FindLibDvdCSS.cmake @@ -100,16 +100,12 @@ find_package_handle_standard_args(LibDvdCSS VERSION_VAR LIBDVDCSS_VERSION) if(LIBDVDCSS_FOUND) - set(LIBDVDCSS_INCLUDE_DIRS ${LIBDVDCSS_INCLUDE_DIR}) - set(LIBDVDCSS_LIBRARIES ${LIBDVDCSS_LIBRARY}) - set(LIBDVDCSS_DEFINITIONS -DHAVE_DVDCSS_DVDCSS_H) - if(NOT TARGET LibDvdCSS::LibDvdCSS) add_library(LibDvdCSS::LibDvdCSS UNKNOWN IMPORTED) set_target_properties(LibDvdCSS::LibDvdCSS PROPERTIES IMPORTED_LOCATION "${LIBDVDCSS_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "${LIBDVDCSS_DEFINITIONS}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_DVDCSS_DVDCSS_H" INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDCSS_INCLUDE_DIR}") if(TARGET libdvdcss) diff --git a/cmake/modules/FindLibDvdNav.cmake b/cmake/modules/FindLibDvdNav.cmake index 56f9537995353..4a07d3ca56e57 100644 --- a/cmake/modules/FindLibDvdNav.cmake +++ b/cmake/modules/FindLibDvdNav.cmake @@ -73,7 +73,11 @@ if(NOT TARGET LibDvdNav::LibDvdNav) ${LIBDVD_ADDITIONAL_ARGS}) else() - string(APPEND LIBDVDNAV_CFLAGS " -I$ $") + string(APPEND LIBDVDNAV_CFLAGS " -I$") + + if(TARGET LibDvdCSS::LibDvdCSS) + string(APPEND LIBDVDNAV_CFLAGS " -I$ $<$:-D$>") + endif() find_program(AUTORECONF autoreconf REQUIRED) if (CMAKE_HOST_SYSTEM_NAME MATCHES "(Free|Net|Open)BSD") @@ -116,20 +120,11 @@ find_package_handle_standard_args(LibDvdNav VERSION_VAR LIBDVDNAV_VERSION) if(LIBDVDNAV_FOUND) - set(LIBDVDNAV_INCLUDE_DIRS ${LIBDVDNAV_INCLUDE_DIR}) - set(LIBDVDNAV_LIBRARIES ${LIBDVDNAV_LIBRARY}) - set(LIBDVDNAV_DEFINITIONS -D_XBMC) - - if(APPLE) - string(APPEND LIBDVDNAV_DEFINITIONS " -D__DARWIN__") - endif() - if(NOT TARGET LibDvdNav::LibDvdNav) add_library(LibDvdNav::LibDvdNav UNKNOWN IMPORTED) set_target_properties(LibDvdNav::LibDvdNav PROPERTIES IMPORTED_LOCATION "${LIBDVDNAV_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "${LIBDVDNAV_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDNAV_INCLUDE_DIR}") if(TARGET libdvdnav) diff --git a/cmake/modules/FindLibDvdRead.cmake b/cmake/modules/FindLibDvdRead.cmake index eeca81c238ce4..c9ffb3cc12ab4 100644 --- a/cmake/modules/FindLibDvdRead.cmake +++ b/cmake/modules/FindLibDvdRead.cmake @@ -76,7 +76,7 @@ if(NOT TARGET LibDvdRead::LibDvdRead) else() if(TARGET LibDvdCSS::LibDvdCSS) - string(APPEND LIBDVDREAD_CFLAGS " -I$ $") + string(APPEND LIBDVDREAD_CFLAGS " -I$ $<$:-D$>") string(APPEND with-css "--with-libdvdcss") endif() @@ -122,20 +122,11 @@ find_package_handle_standard_args(LibDvdRead VERSION_VAR LIBDVDREAD_VERSION) if(LIBDVDREAD_FOUND) - set(LIBDVDREAD_INCLUDE_DIRS ${LIBDVDREAD_INCLUDE_DIR}) - set(LIBDVDREAD_LIBRARIES ${LIBDVDREAD_LIBRARY}) - set(LIBDVDREAD_DEFINITIONS -D_XBMC) - - if(APPLE) - string(APPEND LIBDVDREAD_DEFINITIONS " -D__DARWIN__") - endif() - if(NOT TARGET LibDvdRead::LibDvdRead) add_library(LibDvdRead::LibDvdRead UNKNOWN IMPORTED) set_target_properties(LibDvdRead::LibDvdRead PROPERTIES IMPORTED_LOCATION "${LIBDVDREAD_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "${LIBDVDREAD_DEFINITIONS}" INTERFACE_INCLUDE_DIRECTORIES "${LIBDVDREAD_INCLUDE_DIR}") if(TARGET libdvdread) @@ -144,7 +135,7 @@ if(LIBDVDREAD_FOUND) if(TARGET LibDvdCSS::LibDvdCSS) add_dependencies(LibDvdRead::LibDvdRead LibDvdCSS::LibDvdCSS) set_target_properties(LibDvdRead::LibDvdRead PROPERTIES - INTERFACE_LINK_LIBRARIES "-ldvdcss") + INTERFACE_LINK_LIBRARIES "dvdcss") endif() endif() diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index 71e4d8fc12f30..b2233a170d1b8 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -3,28 +3,26 @@ # ------- # Finds the libnfs library # -# This will define the following variables:: -# -# NFS_FOUND - system has libnfs -# NFS_INCLUDE_DIRS - the libnfs include directory -# NFS_LIBRARIES - the libnfs libraries -# NFS_DEFINITIONS - the libnfs compile definitions -# -# and the following imported targets:: +# This will define the following target: # # NFS::NFS - The libnfs library -include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT TARGET libnfs::nfs) + + include(cmake/scripts/common/ModuleHelpers.cmake) -set(MODULE_LC libnfs) + set(MODULE_LC libnfs) -SETUP_BUILD_VARS() + SETUP_BUILD_VARS() -# Search for cmake config. Suitable for all platforms including windows -find_package(LIBNFS CONFIG QUIET) + # Search for cmake config. Suitable for all platforms including windows + find_package(libnfs CONFIG QUIET) + + # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build + # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway + if((libnfs_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_NFS) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_NFS)) -if(NOT LIBNFS_FOUND) - if(ENABLE_INTERNAL_NFS) set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF -DENABLE_TESTS=OFF -DENABLE_DOCUMENTATION=OFF @@ -33,85 +31,111 @@ if(NOT LIBNFS_FOUND) BUILD_DEP_TARGET() - set(NFS_LIBRARY ${${MODULE}_LIBRARY}) - set(NFS_INCLUDE_DIR ${${MODULE}_INCLUDE_DIR}) + set(_nfs_definitions HAS_NFS_SET_TIMEOUT + HAS_NFS_MOUNT_GETEXPORTS_TIMEOUT) else() - # Try pkgconfig based search. Linux may not have a version with cmake config installed - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_NFS libnfs>=3.0.0 QUIET) + if(NOT TARGET libnfs::nfs) + # Try pkgconfig based search as last resort + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBNFS libnfs>=3.0.0 QUIET) + endif() + + find_library(LIBNFS_LIBRARY_RELEASE NAMES nfs libnfs + HINTS ${DEPENDS_PATH}/lib + ${PC_LIBNFS_LIBDIR}) + set(LIBNFS_VERSION ${PC_LIBNFS_VERSION}) endif() - find_path(NFS_INCLUDE_DIR nfsc/libnfs.h - PATHS ${PC_NFS_INCLUDEDIR}) - - set(LIBNFS_VERSION ${PC_NFS_VERSION}) - - find_library(NFS_LIBRARY NAMES nfs libnfs - PATHS ${PC_NFS_LIBDIR}) - endif() -else() - # Find lib and path as we cant easily rely on cmake-config - find_library(NFS_LIBRARY NAMES nfs libnfs - PATHS ${DEPENDS_PATH}/lib) - find_path(NFS_INCLUDE_DIR nfsc/libnfs.h PATHS ${DEPENDS_PATH}/include) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(NFS - REQUIRED_VARS NFS_LIBRARY NFS_INCLUDE_DIR - VERSION_VAR LIBNFS_VERSION) - -if(NFS_FOUND) - set(NFS_LIBRARIES ${NFS_LIBRARY}) - set(NFS_INCLUDE_DIRS ${NFS_INCLUDE_DIR}) - set(NFS_DEFINITIONS -DHAS_FILESYSTEM_NFS=1) - - set(CMAKE_REQUIRED_INCLUDES "${NFS_INCLUDE_DIR}") - set(CMAKE_REQUIRED_LIBRARIES ${NFS_LIBRARY}) - - # Check for nfs_set_timeout - check_cxx_source_compiles(" - ${NFS_CXX_INCLUDE} - #include - int main() - { - nfs_set_timeout(NULL, 0); - } - " NFS_SET_TIMEOUT) - - if(NFS_SET_TIMEOUT) - list(APPEND NFS_DEFINITIONS -DHAS_NFS_SET_TIMEOUT) + find_path(LIBNFS_INCLUDE_DIR nfsc/libnfs.h HINTS ${PC_LIBNFS_INCLUDEDIR} + ${DEPENDS_PATH}/include) endif() - # Check for mount_getexports_timeout - check_cxx_source_compiles(" - ${NFS_CXX_INCLUDE} - #include - int main() - { - mount_getexports_timeout(NULL, 0); - } - " NFS_MOUNT_GETEXPORTS_TIMEOUT) - - if(NFS_MOUNT_GETEXPORTS_TIMEOUT) - list(APPEND NFS_DEFINITIONS -DHAS_NFS_MOUNT_GETEXPORTS_TIMEOUT) + if(TARGET libnfs::nfs) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_LIBNFS_CONFIGURATIONS libnfs::nfs IMPORTED_CONFIGURATIONS) + foreach(_libnfs_config IN LISTS _LIBNFS_CONFIGURATIONS) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_libnfs_config} _libnfs_config_UPPER) + if((NOT ${_libnfs_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_libnfs_config_UPPER} STREQUAL "DEBUG")) + get_target_property(LIBNFS_LIBRARY_RELEASE libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) + else() + get_target_property(LIBNFS_LIBRARY_${_libnfs_config_UPPER} libnfs::nfs IMPORTED_LOCATION_${_libnfs_config_UPPER}) + endif() + endforeach() endif() - unset(CMAKE_REQUIRED_INCLUDES) - unset(CMAKE_REQUIRED_LIBRARIES) + include(SelectLibraryConfigurations) + select_library_configurations(LIBNFS) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NFS + REQUIRED_VARS LIBNFS_LIBRARY LIBNFS_INCLUDE_DIR + VERSION_VAR LIBNFS_VERSION) + + if(NFS_FOUND) + # Pre existing lib, so we can run checks + if(NOT TARGET libnfs) + set(CMAKE_REQUIRED_INCLUDES "${LIBNFS_INCLUDE_DIR}") + set(CMAKE_REQUIRED_LIBRARIES ${LIBNFS_LIBRARY}) + + # Check for nfs_set_timeout + check_cxx_source_compiles(" + ${LIBNFS_CXX_INCLUDE} + #include + int main() + { + nfs_set_timeout(NULL, 0); + } + " NFS_SET_TIMEOUT) + + if(NFS_SET_TIMEOUT) + list(APPEND _nfs_definitions HAS_NFS_SET_TIMEOUT) + endif() + + # Check for mount_getexports_timeout + check_cxx_source_compiles(" + ${LIBNFS_CXX_INCLUDE} + #include + int main() + { + mount_getexports_timeout(NULL, 0); + } + " NFS_MOUNT_GETEXPORTS_TIMEOUT) + + if(NFS_MOUNT_GETEXPORTS_TIMEOUT) + list(APPEND _nfs_definitions HAS_NFS_MOUNT_GETEXPORTS_TIMEOUT) + endif() + + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) + endif() - if(NOT TARGET NFS::NFS) - add_library(NFS::NFS UNKNOWN IMPORTED) + if(NOT TARGET libnfs::nfs) + add_library(libnfs::nfs UNKNOWN IMPORTED) + set_target_properties(libnfs::nfs PROPERTIES + IMPORTED_LOCATION "${LIBNFS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBNFS_INCLUDE_DIR}") + endif() - set_target_properties(NFS::NFS PROPERTIES - IMPORTED_LOCATION "${NFS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${NFS_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS "${NFS_DEFINITIONS}") if(TARGET libnfs) - add_dependencies(NFS::NFS libnfs) + add_dependencies(libnfs::nfs libnfs) endif() + + list(APPEND _nfs_definitions HAS_FILESYSTEM_NFS) + + # We need to append in case the cmake config already has definitions + set_property(TARGET libnfs::nfs APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS ${_nfs_definitions}) + + # Need to manually set this, as libnfs cmake config does not provide INTERFACE_INCLUDE_DIRECTORIES + set_target_properties(libnfs::nfs PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBNFS_INCLUDE_DIR}) + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libnfs::nfs) + + mark_as_advanced(LIBNFS_INCLUDE_DIR LIBNFS_LIBRARY) endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP NFS::NFS) endif() - -mark_as_advanced(NFS_INCLUDE_DIR NFS_LIBRARY) diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index b2707ab0c21e3..f4c54aea8a300 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -3,106 +3,127 @@ # -------- # Finds the PCRECPP library # -# This will define the following variables:: -# -# PCRE_FOUND - system has libpcrecpp -# PCRE_INCLUDE_DIRS - the libpcrecpp include directory -# PCRE_LIBRARIES - the libpcrecpp libraries -# PCRE_DEFINITIONS - the libpcrecpp definitions -# -# and the following imported targets:: +# This will define the following targets: # # PCRE::PCRECPP - The PCRECPP library # PCRE::PCRE - The PCRE library -if(NOT PCRE::PCRE) - if(ENABLE_INTERNAL_PCRE) - include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT PCRE::pcre) - set(MODULE_LC pcre) + include(cmake/scripts/common/ModuleHelpers.cmake) - SETUP_BUILD_VARS() + set(MODULE_LC pcre) - # Check for existing PCRE. If version >= PCRE-VERSION file version, dont build - find_package(PCRE CONFIG QUIET) + SETUP_BUILD_VARS() - if(PCRE_VERSION VERSION_LESS ${${MODULE}_VER}) + # Check for existing PCRE. If version >= PCRE-VERSION file version, dont build + find_package(PCRE CONFIG QUIET) - set(PCRE_VERSION ${${MODULE}_VER}) - set(PCRE_DEBUG_POSTFIX d) + if((PCRE_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_PCRE)) - set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-cmakeconfig.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-enable_docs_pc.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-postfix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-win-pdb.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/jit_aarch64.patch") + set(PCRE_VERSION ${${MODULE}_VER}) + set(PCRE_DEBUG_POSTFIX d) - if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - list(APPEND patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/tvos-bitcode-fix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/ios-clear_cache.patch") - endif() - - generate_patchcommand("${patches}") - - set(CMAKE_ARGS -DPCRE_NEWLINE=ANYCRLF - -DPCRE_NO_RECURSE=ON - -DPCRE_MATCH_LIMIT_RECURSION=1500 - -DPCRE_SUPPORT_JIT=ON - -DPCRE_SUPPORT_PCREGREP_JIT=ON - -DPCRE_SUPPORT_UTF=ON - -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON - -DPCRE_SUPPORT_LIBZ=OFF - -DPCRE_SUPPORT_LIBBZ2=OFF - -DPCRE_BUILD_PCREGREP=OFF - -DPCRE_BUILD_TESTS=OFF) - - if(WIN32 OR WINDOWS_STORE) - list(APPEND CMAKE_ARGS -DINSTALL_MSVC_PDB=ON) - elseif(CORE_SYSTEM_NAME STREQUAL android) - # CMake CheckFunctionExists incorrectly detects strtoq for android - list(APPEND CMAKE_ARGS -DHAVE_STRTOQ=0) - endif() + set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-cmakeconfig.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-enable_docs_pc.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-postfix.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-win-pdb.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/jit_aarch64.patch") - # populate PCRECPP lib without a separate module - if(NOT CORE_SYSTEM_NAME MATCHES windows) - # Non windows platforms have a lib prefix for the lib artifact - set(_libprefix "lib") - endif() - # regex used to get platform extension (eg lib for windows, .a for unix) - string(REGEX REPLACE "^.*\\." "" _LIBEXT ${${MODULE}_BYPRODUCT}) - set(PCRECPP_LIBRARY_DEBUG ${DEP_LOCATION}/lib/${_libprefix}pcrecpp${${MODULE}_DEBUG_POSTFIX}.${_LIBEXT}) - set(PCRECPP_LIBRARY_RELEASE ${DEP_LOCATION}/lib/${_libprefix}pcrecpp.${_LIBEXT}) + if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) + list(APPEND patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/tvos-bitcode-fix.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/ios-clear_cache.patch") + endif() - BUILD_DEP_TARGET() + generate_patchcommand("${patches}") + + set(CMAKE_ARGS -DPCRE_NEWLINE=ANYCRLF + -DPCRE_NO_RECURSE=ON + -DPCRE_MATCH_LIMIT_RECURSION=1500 + -DPCRE_SUPPORT_JIT=ON + -DPCRE_SUPPORT_PCREGREP_JIT=ON + -DPCRE_SUPPORT_UTF=ON + -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON + -DPCRE_SUPPORT_LIBZ=OFF + -DPCRE_SUPPORT_LIBBZ2=OFF + -DPCRE_BUILD_PCREGREP=OFF + -DPCRE_BUILD_TESTS=OFF) + + if(WIN32 OR WINDOWS_STORE) + list(APPEND CMAKE_ARGS -DINSTALL_MSVC_PDB=ON) + elseif(CORE_SYSTEM_NAME STREQUAL android) + # CMake CheckFunctionExists incorrectly detects strtoq for android + list(APPEND CMAKE_ARGS -DHAVE_STRTOQ=0) + endif() - else() - # Populate paths for find_package_handle_standard_args - find_path(PCRE_INCLUDE_DIR pcre.h) + # populate PCRECPP lib without a separate module + if(NOT CORE_SYSTEM_NAME MATCHES windows) + # Non windows platforms have a lib prefix for the lib artifact + set(_libprefix "lib") + endif() + # regex used to get platform extension (eg lib for windows, .a for unix) + string(REGEX REPLACE "^.*\\." "" _LIBEXT ${${MODULE}_BYPRODUCT}) + set(PCRECPP_LIBRARY_DEBUG ${DEP_LOCATION}/lib/${_libprefix}pcrecpp${${MODULE}_DEBUG_POSTFIX}.${_LIBEXT}) + set(PCRECPP_LIBRARY_RELEASE ${DEP_LOCATION}/lib/${_libprefix}pcrecpp.${_LIBEXT}) - find_library(PCRECPP_LIBRARY_RELEASE NAMES pcrecpp) - find_library(PCRECPP_LIBRARY_DEBUG NAMES pcrecppd) + BUILD_DEP_TARGET() - find_library(PCRE_LIBRARY_RELEASE NAMES pcre) - find_library(PCRE_LIBRARY_DEBUG NAMES pcred) - endif() else() + if(NOT TARGET PCRE::pcre) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PCRE pcre pcrecpp QUIET) + endif() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PCRE libpcrecpp QUIET) - endif() - - find_path(PCRE_INCLUDE_DIR pcrecpp.h - PATHS ${PC_PCRE_INCLUDEDIR}) - find_library(PCRECPP_LIBRARY_RELEASE NAMES pcrecpp + find_path(PCRE_INCLUDE_DIR pcrecpp.h + PATHS ${PC_PCRE_INCLUDEDIR}) + find_library(PCRECPP_LIBRARY_RELEASE NAMES pcrecpp + PATHS ${PC_PCRE_LIBDIR}) + find_library(PCRE_LIBRARY_RELEASE NAMES pcre + PATHS ${PC_PCRE_LIBDIR}) + find_library(PCRECPP_LIBRARY_DEBUG NAMES pcrecppd PATHS ${PC_PCRE_LIBDIR}) - find_library(PCRE_LIBRARY_RELEASE NAMES pcre + find_library(PCRE_LIBRARY_DEBUG NAMES pcred PATHS ${PC_PCRE_LIBDIR}) - find_library(PCRECPP_LIBRARY_DEBUG NAMES pcrecppd - PATHS ${PC_PCRE_LIBDIR}) - find_library(PCRE_LIBRARY_DEBUG NAMES pcred - PATHS ${PC_PCRE_LIBDIR}) - set(PCRE_VERSION ${PC_PCRE_VERSION}) + set(PCRE_VERSION ${PC_PCRE_VERSION}) + else() + + # Populate variables for find_package_handle_standard_args usage + get_target_property(_PCRE_CONFIGURATIONS PCRE::pcre IMPORTED_CONFIGURATIONS) + foreach(_pcre_config IN LISTS _PCRE_CONFIGURATIONS) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + if((NOT ${_pcre_config} STREQUAL "RELEASE") AND + (NOT ${_pcre_config} STREQUAL "DEBUG")) + get_target_property(PCRE_LIBRARY_RELEASE PCRE::pcre IMPORTED_LOCATION_${_pcre_config}) + else() + get_target_property(PCRE_LIBRARY_${_pcre_config} PCRE::pcre IMPORTED_LOCATION_${_pcre_config}) + endif() + endforeach() + + get_target_property(_PCRECPP_CONFIGURATIONS PCRE::pcrecpp IMPORTED_CONFIGURATIONS) + foreach(_pcrecpp_config IN LISTS _PCRECPP_CONFIGURATIONS) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + if((NOT ${_pcrecpp_config} STREQUAL "RELEASE") AND + (NOT ${_pcrecpp_config} STREQUAL "DEBUG")) + get_target_property(PCRECPP_LIBRARY_RELEASE PCRE::pcrecpp IMPORTED_LOCATION_${_pcrecpp_config}) + else() + get_target_property(PCRECPP_LIBRARY_${_pcrecpp_config} PCRE::pcrecpp IMPORTED_LOCATION_${_pcrecpp_config}) + endif() + endforeach() + + # ToDo: patch PCRE cmake to include includedir in config file + find_path(PCRE_INCLUDE_DIR pcrecpp.h + PATHS ${PC_PCRE_INCLUDEDIR}) + + set_target_properties(PCRE::pcre PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") + set_target_properties(PCRE::pcrecpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") + endif() + endif() + if(TARGET PCRE::pcre) + get_target_property(PCRE_INCLUDE_DIR PCRE::pcre INTERFACE_INCLUDE_DIRECTORIES) endif() include(SelectLibraryConfigurations) @@ -115,56 +136,51 @@ if(NOT PCRE::PCRE) VERSION_VAR PCRE_VERSION) if(PCRE_FOUND) - set(PCRE_LIBRARIES ${PCRECPP_LIBRARY} ${PCRE_LIBRARY}) - set(PCRE_INCLUDE_DIRS ${PCRE_INCLUDE_DIR}) - if(WIN32) - set(PCRE_DEFINITIONS -DPCRE_STATIC=1) - endif() - - if(NOT TARGET PCRE::PCRE) - add_library(PCRE::PCRE UNKNOWN IMPORTED) + if(NOT TARGET PCRE::pcre) + add_library(PCRE::pcre UNKNOWN IMPORTED) if(PCRE_LIBRARY_RELEASE) - set_target_properties(PCRE::PCRE PROPERTIES + set_target_properties(PCRE::pcre PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${PCRE_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${PCRE_LIBRARY_RELEASE}") endif() if(PCRE_LIBRARY_DEBUG) - set_target_properties(PCRE::PCRE PROPERTIES + set_target_properties(PCRE::pcre PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${PCRE_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${PCRE_LIBRARY_DEBUG}") endif() - set_target_properties(PCRE::PCRE PROPERTIES + set_target_properties(PCRE::pcre PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") - if(WIN32) - set_target_properties(PCRE::PCRE PROPERTIES - INTERFACE_COMPILE_DEFINITIONS PCRE_STATIC=1) - endif() - endif() - if(NOT TARGET PCRE::PCRECPP) - add_library(PCRE::PCRECPP UNKNOWN IMPORTED) - if(PCRE_LIBRARY_RELEASE) - set_target_properties(PCRE::PCRECPP PROPERTIES + if(NOT TARGET PCRE::pcrecpp) + add_library(PCRE::pcrecpp UNKNOWN IMPORTED) + if(PCRECPP_LIBRARY_RELEASE) + set_target_properties(PCRE::pcrecpp PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${PCRECPP_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${PCRECPP_LIBRARY_RELEASE}") endif() - if(PCRE_LIBRARY_DEBUG) - set_target_properties(PCRE::PCRECPP PROPERTIES + if(PCRECPP_LIBRARY_DEBUG) + set_target_properties(PCRE::pcrecpp PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${PCRECPP_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${PCRECPP_LIBRARY_DEBUG}") endif() - set_target_properties(PCRE::PCRECPP PROPERTIES - INTERFACE_LINK_LIBRARIES PCRE::PCRE) + set_target_properties(PCRE::pcrecpp PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") endif() - if(TARGET pcre) - add_dependencies(PCRE::PCRE pcre) + + # Wee need to explicitly add this define. The cmake config does not propagate this info + if(WIN32) + set_property(TARGET PCRE::pcre APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS "PCRE_STATIC=1") + set_property(TARGET PCRE::pcrecpp APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS "PCRE_STATIC=1") endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::PCRE) - else() - if(PCRE_FIND_REQUIRED) - message(FATAL_ERROR "PCRE not found. Possibly use -DENABLE_INTERNAL_PCRE=ON to build PCRE") + + if(TARGET pcre) + add_dependencies(PCRE::pcre pcre) + add_dependencies(PCRE::pcrecpp pcre) endif() - endif() - mark_as_advanced(PCRE_INCLUDE_DIR PCRECPP_LIBRARY PCRE_LIBRARY) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcre) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcrecpp) + endif() endif() diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 2db5e687c21d1..4a84365748ce7 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -8,60 +8,71 @@ # RapidJSON_FOUND - system has RapidJSON parser # RapidJSON_INCLUDE_DIRS - the RapidJSON parser include directory # -if(ENABLE_INTERNAL_RapidJSON) - include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC rapidjson) +if(NOT TARGET RapidJSON::RapidJSON) + if(ENABLE_INTERNAL_RapidJSON) + include(cmake/scripts/common/ModuleHelpers.cmake) - SETUP_BUILD_VARS() + set(MODULE_LC rapidjson) - set(RapidJSON_VERSION ${${MODULE}_VER}) + SETUP_BUILD_VARS() - set(patches "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/001-remove_custom_cxx_flags.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/002-cmake-removedocs-examples.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/003-win-arm64.patch") + set(RapidJSON_VERSION ${${MODULE}_VER}) - generate_patchcommand("${patches}") + set(patches "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/001-remove_custom_cxx_flags.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/002-cmake-removedocs-examples.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/003-win-arm64.patch") - set(CMAKE_ARGS -DRAPIDJSON_BUILD_DOC=OFF - -DRAPIDJSON_BUILD_EXAMPLES=OFF - -DRAPIDJSON_BUILD_TESTS=OFF - -DRAPIDJSON_BUILD_THIRDPARTY_GTEST=OFF) + generate_patchcommand("${patches}") - set(BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include/rapidjson/rapidjson.h) + set(CMAKE_ARGS -DRAPIDJSON_BUILD_DOC=OFF + -DRAPIDJSON_BUILD_EXAMPLES=OFF + -DRAPIDJSON_BUILD_TESTS=OFF + -DRAPIDJSON_BUILD_THIRDPARTY_GTEST=OFF) - BUILD_DEP_TARGET() + set(BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include/rapidjson/rapidjson.h) - set(RapidJSON_INCLUDE_DIR ${${MODULE}_INCLUDE_DIR}) + BUILD_DEP_TARGET() - # Add dependency to libkodi to build - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP rapidjson) -else() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_RapidJSON RapidJSON>=1.0.2 QUIET) - endif() + set(RAPIDJSON_INCLUDE_DIRS ${${MODULE}_INCLUDE_DIR}) - if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) - set(RapidJSON_VERSION 1.1.0) else() - if(PC_RapidJSON_VERSION) - set(RapidJSON_VERSION ${PC_RapidJSON_VERSION}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_RapidJSON RapidJSON>=1.0.2 QUIET) + endif() + + if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) + set(RapidJSON_VERSION 1.1.0) else() - find_package(RapidJSON 1.1.0 CONFIG REQUIRED QUIET) + if(PC_RapidJSON_VERSION) + set(RapidJSON_VERSION ${PC_RapidJSON_VERSION}) + else() + find_package(RapidJSON 1.1.0 CONFIG REQUIRED QUIET) + endif() endif() + + find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h + PATHS ${PC_RapidJSON_INCLUDEDIR}) endif() - find_path(RapidJSON_INCLUDE_DIR NAMES rapidjson/rapidjson.h - PATHS ${PC_RapidJSON_INCLUDEDIR}) -endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(RapidJSON + REQUIRED_VARS RAPIDJSON_INCLUDE_DIRS RapidJSON_VERSION + VERSION_VAR RapidJSON_VERSION) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(RapidJSON - REQUIRED_VARS RapidJSON_INCLUDE_DIR RapidJSON_VERSION - VERSION_VAR RapidJSON_VERSION) + if(RAPIDJSON_FOUND) + if(NOT TARGET RapidJSON::RapidJSON) + add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) -if(RAPIDJSON_FOUND) - set(RAPIDJSON_INCLUDE_DIRS ${RapidJSON_INCLUDE_DIR}) -endif() + set_target_properties(RapidJSON::RapidJSON PROPERTIES + FOLDER "External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") + endif() + if(TARGET rapidjson) + add_dependencies(RapidJSON::RapidJSON rapidjson) + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP RapidJSON::RapidJSON) + endif() -mark_as_advanced(RapidJSON_INCLUDE_DIR) + mark_as_advanced(RapidJSON_INCLUDE_DIR) +endif() diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index dfedcb4e15261..520c8abba757e 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -2,34 +2,41 @@ # ------- # Finds the Spdlog library # -# This will define the following variables: +# This will define the following target: # -# SPDLOG_FOUND - system has Spdlog -# SPDLOG_INCLUDE_DIRS - the Spdlog include directory -# SPDLOG_LIBRARIES - the Spdlog libraries -# SPDLOG_DEFINITIONS - the Spdlog compile definitions -# -# and the following imported targets: -# -# Spdlog::Spdlog - The Spdlog library +# spdlog::spdlog - The Spdlog library -if(ENABLE_INTERNAL_SPDLOG) +if(NOT TARGET spdlog::spdlog) include(cmake/scripts/common/ModuleHelpers.cmake) # Check for dependencies - Must be done before SETUP_BUILD_VARS - get_libversion_data("fmt" "target") - find_package(Fmt ${LIB_FMT_VER} MODULE REQUIRED) + # Todo: We might need a way to do this after SETUP_BUILD_VARS... + if(ENABLE_INTERNAL_SPDLOG) + get_libversion_data("fmt" "target") + find_package(Fmt ${LIB_FMT_VER} MODULE REQUIRED) + endif() - # Check if we want to force a build due to a dependency rebuild - get_property(LIB_FORCE_REBUILD TARGET fmt::fmt PROPERTY LIB_BUILD) + if(TARGET fmt::fmt) + # Check if we want to force a build due to a dependency rebuild + get_property(LIB_FORCE_REBUILD TARGET fmt::fmt PROPERTY LIB_BUILD) + endif() set(MODULE_LC spdlog) SETUP_BUILD_VARS() + # Darwin systems we want to avoid system packages. We are entirely self sufficient + # Avoids homebrew populating rubbish we cant control + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(_spdlog_find_option NO_SYSTEM_ENVIRONMENT_PATH) + endif() + # Check for existing SPDLOG. If version >= SPDLOG-VERSION file version, dont build - find_package(SPDLOG CONFIG QUIET) + find_package(SPDLOG ${_spdlog_find_option} CONFIG QUIET) + + if((SPDLOG_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_SPDLOG) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_SPDLOG) OR + LIB_FORCE_REBUILD) - if(SPDLOG_VERSION VERSION_LESS ${${MODULE}_VER} OR LIB_FORCE_REBUILD) if(APPLE) set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") endif() @@ -41,6 +48,8 @@ if(ENABLE_INTERNAL_SPDLOG) set(EXTRA_ARGS -DSPDLOG_WCHAR_SUPPORT=ON -DSPDLOG_WCHAR_FILENAMES=ON) + set(EXTRA_DEFINITIONS SPDLOG_WCHAR_FILENAMES + SPDLOG_WCHAR_TO_UTF8_SUPPORT) endif() set(SPDLOG_VERSION ${${MODULE}_VER}) @@ -55,74 +64,100 @@ if(ENABLE_INTERNAL_SPDLOG) -DSPDLOG_FMT_EXTERNAL=ON ${EXTRA_ARGS}) + # Set definitions that will be set in the built cmake config file + # We dont import the config file if we build internal (chicken/egg scenario) + set(_spdlog_definitions SPDLOG_COMPILED_LIB + SPDLOG_FMT_EXTERNAL + ${EXTRA_DEFINITIONS}) + BUILD_DEP_TARGET() add_dependencies(${MODULE_LC} fmt::fmt) else() - # Populate paths for find_package_handle_standard_args - find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h) - find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog) - find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd) + if(NOT TARGET spdlog::spdlog) + # Fallback to pkg-config and individual lib/include file search + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_SPDLOG spdlog QUIET) + set(SPDLOG_VERSION ${PC_SPDLOG_VERSION}) + endif() + + find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h + PATHS ${PC_SPDLOG_INCLUDEDIR}) + + find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog + PATHS ${PC_SPDLOG_LIBDIR}) + find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd + PATHS ${PC_SPDLOG_LIBDIR}) + + # Only add -D definitions. Skip -I include as we do a find_path for the header anyway + foreach(_spdlog_cflag IN LISTS PC_SPDLOG_CFLAGS) + if(${_spdlog_cflag} MATCHES "^-D(.*)") + list(APPEND _spdlog_definitions ${CMAKE_MATCH_1}) + endif() + endforeach() + endif() endif() -else() - find_package(spdlog 1.5.0 CONFIG REQUIRED QUIET) - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_SPDLOG spdlog QUIET) - set(SPDLOG_VERSION ${PC_SPDLOG_VERSION}) + if(TARGET spdlog::spdlog) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is spdlogConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_SPDLOG_CONFIGURATIONS spdlog::spdlog IMPORTED_CONFIGURATIONS) + foreach(_spdlog_config IN LISTS _SPDLOG_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_spdlog_config} _spdlog_config_UPPER) + if((NOT ${_spdlog_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_spdlog_config_UPPER} STREQUAL "DEBUG")) + get_target_property(SPDLOG_LIBRARY_RELEASE spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) + else() + get_target_property(SPDLOG_LIBRARY_${_spdlog_config_UPPER} spdlog::spdlog IMPORTED_LOCATION_${_spdlog_config_UPPER}) + endif() + endforeach() + + get_target_property(SPDLOG_INCLUDE_DIR spdlog::spdlog INTERFACE_INCLUDE_DIRECTORIES) endif() - find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h - PATHS ${PC_SPDLOG_INCLUDEDIR}) - - find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog - PATHS ${PC_SPDLOG_LIBDIR}) - find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd - PATHS ${PC_SPDLOG_LIBDIR}) -endif() - -include(SelectLibraryConfigurations) -select_library_configurations(SPDLOG) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Spdlog - REQUIRED_VARS SPDLOG_LIBRARY SPDLOG_INCLUDE_DIR - VERSION_VAR SPDLOG_VERSION) - -if(SPDLOG_FOUND) - set(SPDLOG_LIBRARIES ${SPDLOG_LIBRARY}) - set(SPDLOG_INCLUDE_DIRS ${SPDLOG_INCLUDE_DIR}) - set(SPDLOG_DEFINITIONS -DSPDLOG_FMT_EXTERNAL - -DSPDLOG_DEBUG_ON - -DSPDLOG_NO_ATOMIC_LEVELS - -DSPDLOG_ENABLE_PATTERN_PADDING - -DSPDLOG_COMPILED_LIB - ${PC_SPDLOG_CFLAGS}) - if(WIN32) - list(APPEND SPDLOG_DEFINITIONS -DSPDLOG_WCHAR_FILENAMES - -DSPDLOG_WCHAR_TO_UTF8_SUPPORT) - endif() - - if(NOT TARGET spdlog::spdlog) - add_library(spdlog::spdlog UNKNOWN IMPORTED) - if(SPDLOG_LIBRARY_RELEASE) + include(SelectLibraryConfigurations) + select_library_configurations(SPDLOG) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Spdlog + REQUIRED_VARS SPDLOG_LIBRARY SPDLOG_INCLUDE_DIR + VERSION_VAR SPDLOG_VERSION) + + if(Spdlog_FOUND) + if(NOT TARGET spdlog::spdlog) + # Ideally we probably shouldnt be overriding these. We should trust the cmake config file + list(APPEND _spdlog_definitions SPDLOG_DEBUG_ON + SPDLOG_NO_ATOMIC_LEVELS) + + add_library(spdlog::spdlog UNKNOWN IMPORTED) + if(SPDLOG_LIBRARY_RELEASE) + set_target_properties(spdlog::spdlog PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${SPDLOG_LIBRARY_RELEASE}") + endif() + if(SPDLOG_LIBRARY_DEBUG) + set_target_properties(spdlog::spdlog PROPERTIES + IMPORTED_CONFIGURATIONS DEBUG + IMPORTED_LOCATION_DEBUG "${SPDLOG_LIBRARY_DEBUG}") + endif() set_target_properties(spdlog::spdlog PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${SPDLOG_LIBRARY_RELEASE}") + INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}") + + # We need to append in case the cmake config already has definitions + set_property(TARGET spdlog::spdlog APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS "${_spdlog_definitions}") endif() - if(SPDLOG_LIBRARY_DEBUG) - set_target_properties(spdlog::spdlog PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${SPDLOG_LIBRARY_DEBUG}") + + if(TARGET spdlog) + add_dependencies(spdlog::spdlog spdlog) endif() - set_target_properties(spdlog::spdlog PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${SPDLOG_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS "${SPDLOG_DEFINITIONS}") - endif() - if(TARGET spdlog) - add_dependencies(spdlog::spdlog spdlog) endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP spdlog::spdlog) -endif() -mark_as_advanced(SPDLOG_INCLUDE_DIR SPDLOG_LIBRARY) +endif() diff --git a/cmake/modules/FindSqlite3.cmake b/cmake/modules/FindSqlite3.cmake index 8fd9719ec7dbc..9b85e34f41a71 100644 --- a/cmake/modules/FindSqlite3.cmake +++ b/cmake/modules/FindSqlite3.cmake @@ -3,42 +3,36 @@ # ----------- # Finds the SQLite3 library # -# This will define the following variables:: -# -# SQLITE3_FOUND - system has SQLite3 -# SQLITE3_INCLUDE_DIRS - the SQLite3 include directory -# SQLITE3_LIBRARIES - the SQLite3 libraries -# -# and the following imported targets:: +# This will define the following target: # # SQLite3::SQLite3 - The SQLite3 library +# -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_SQLITE3 sqlite3 QUIET) -endif() +if(NOT TARGET SQLite3::SQLite3) -find_path(SQLITE3_INCLUDE_DIR NAMES sqlite3.h - PATHS ${PC_SQLITE3_INCLUDEDIR}) -find_library(SQLITE3_LIBRARY NAMES sqlite3 - PATHS ${PC_SQLITE3_LIBDIR}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_SQLITE3 sqlite3 QUIET) + endif() -set(SQLITE3_VERSION ${PC_SQLITE3_VERSION}) + find_path(SQLITE3_INCLUDE_DIR NAMES sqlite3.h + PATHS ${PC_SQLITE3_INCLUDEDIR}) + find_library(SQLITE3_LIBRARY NAMES sqlite3 + PATHS ${PC_SQLITE3_LIBDIR}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Sqlite3 - REQUIRED_VARS SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR - VERSION_VAR SQLITE3_VERSION) + set(SQLITE3_VERSION ${PC_SQLITE3_VERSION}) -if(SQLITE3_FOUND) - set(SQLITE3_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIR}) - set(SQLITE3_LIBRARIES ${SQLITE3_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Sqlite3 + REQUIRED_VARS SQLITE3_LIBRARY SQLITE3_INCLUDE_DIR + VERSION_VAR SQLITE3_VERSION) - if(NOT TARGET SQLite3::SQLite3) + if(Sqlite3_FOUND) add_library(SQLite3::SQLite3 UNKNOWN IMPORTED) set_target_properties(SQLite3::SQLite3 PROPERTIES IMPORTED_LOCATION "${SQLITE3_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SQLITE3_INCLUDE_DIR}") + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP SQLite3::SQLite3) endif() + mark_as_advanced(SQLITE3_INCLUDE_DIR SQLITE3_LIBRARY) endif() - -mark_as_advanced(SQLITE3_INCLUDE_DIR SQLITE3_LIBRARY) diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 9caafaff03c89..e1b8745e4a749 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -3,97 +3,114 @@ # ---------- # Finds the TagLib library # -# This will define the following variables:: -# -# TAGLIB_FOUND - system has TagLib -# TAGLIB_INCLUDE_DIRS - the TagLib include directory -# TAGLIB_LIBRARIES - the TagLib libraries -# -# and the following imported targets:: +# This will define the following target: # # TagLib::TagLib - The TagLib library +# -if(ENABLE_INTERNAL_TAGLIB) - include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT TARGET TagLib::TagLib) + if(ENABLE_INTERNAL_TAGLIB) - set(MODULE_LC taglib) + # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html + set(FPHSA_NAME_MISMATCHED 1) - SETUP_BUILD_VARS() + # Darwin systems use a system tbd that isnt found as a static lib + # Other platforms when using ENABLE_INTERNAL_TAGLIB, we want the static lib + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # Requires cmake 3.24 for ZLIB_USE_STATIC_LIBS to actually do something + set(ZLIB_USE_STATIC_LIBS ON) + endif() + find_package(ZLIB REQUIRED) + unset(FPHSA_NAME_MISMATCHED) - set(TAGLIB_VERSION ${${MODULE}_VER}) + include(cmake/scripts/common/ModuleHelpers.cmake) - if(WIN32 OR WINDOWS_STORE) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-cmake-pdb-debug.patch") - generate_patchcommand("${patches}") + set(MODULE_LC taglib) - if(WINDOWS_STORE) - set(EXTRA_ARGS -DPLATFORM_WINRT=ON) - endif() - endif() + SETUP_BUILD_VARS() - # Debug postfix only used for windows - if(WIN32 OR WINDOWS_STORE) - set(TAGLIB_DEBUG_POSTFIX "d") - endif() + set(TAGLIB_VERSION ${${MODULE}_VER}) - set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - -DBUILD_EXAMPLES=OFF - -DBUILD_TESTING=OFF - -DBUILD_BINDINGS=OFF - ${EXTRA_ARGS}) + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-cmake-pdb-debug.patch") + generate_patchcommand("${patches}") - BUILD_DEP_TARGET() + if(WINDOWS_STORE) + set(EXTRA_ARGS -DPLATFORM_WINRT=ON) + endif() + endif() -else() + # Debug postfix only used for windows + if(WIN32 OR WINDOWS_STORE) + set(TAGLIB_DEBUG_POSTFIX "d") + endif() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_TAGLIB taglib>=1.9.0 QUIET) - endif() + set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + -DBUILD_EXAMPLES=OFF + -DBUILD_TESTING=OFF + -DBUILD_BINDINGS=OFF + ${EXTRA_ARGS}) - find_path(TAGLIB_INCLUDE_DIR taglib/tag.h - PATHS ${PC_TAGLIB_INCLUDEDIR}) - find_library(TAGLIB_LIBRARY_RELEASE NAMES tag - PATHS ${PC_TAGLIB_LIBDIR}) - find_library(TAGLIB_LIBRARY_DEBUG NAMES tagd - PATHS ${PC_TAGLIB_LIBDIR}) - set(TAGLIB_VERSION ${PC_TAGLIB_VERSION}) + BUILD_DEP_TARGET() -endif() + add_dependencies(${MODULE_LC} ZLIB::ZLIB) + + else() + + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_TAGLIB taglib>=1.9.0 QUIET) + endif() -include(SelectLibraryConfigurations) -select_library_configurations(TAGLIB) + find_path(TAGLIB_INCLUDE_DIR taglib/tag.h PATHS ${PC_TAGLIB_INCLUDEDIR}) + find_library(TAGLIB_LIBRARY_RELEASE NAMES tag + PATHS ${PC_TAGLIB_LIBDIR}) + find_library(TAGLIB_LIBRARY_DEBUG NAMES tagd + PATHS ${PC_TAGLIB_LIBDIR}) + set(TAGLIB_VERSION ${PC_TAGLIB_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(TagLib - REQUIRED_VARS TAGLIB_LIBRARY TAGLIB_INCLUDE_DIR - VERSION_VAR TAGLIB_VERSION) + set(TAGLIB_LINK_LIBS ${PC_TAGLIB_LIBRARIES}) + endif() -if(TAGLIB_FOUND) - set(TAGLIB_INCLUDE_DIRS ${TAGLIB_INCLUDE_DIR}) - set(TAGLIB_LIBRARIES ${TAGLIB_LIBRARY}) + include(SelectLibraryConfigurations) + select_library_configurations(TAGLIB) - # Workaround broken .pc file - list(APPEND TAGLIB_LIBRARIES ${PC_TAGLIB_ZLIB_LIBRARIES}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(TagLib + REQUIRED_VARS TAGLIB_LIBRARY TAGLIB_INCLUDE_DIR + VERSION_VAR TAGLIB_VERSION) - if(NOT TARGET TagLib::TagLib) + if(TagLib_FOUND) add_library(TagLib::TagLib UNKNOWN IMPORTED) if(TAGLIB_LIBRARY_RELEASE) set_target_properties(TagLib::TagLib PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${TAGLIB_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${TAGLIB_LIBRARY_RELEASE}") endif() if(TAGLIB_LIBRARY_DEBUG) set_target_properties(TagLib::TagLib PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${TAGLIB_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${TAGLIB_LIBRARY_DEBUG}") endif() set_target_properties(TagLib::TagLib PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${TAGLIB_INCLUDE_DIR}") + + # if pkg-config returns link libs at to TARGET. For internal build, we use ZLIB::Zlib + # dependency explicitly + if(TAGLIB_LINK_LIBS) + set_target_properties(TagLib::TagLib PROPERTIES + INTERFACE_LINK_LIBRARIES "${TAGLIB_LINK_LIBS}") + endif() + if(TARGET taglib) add_dependencies(TagLib::TagLib taglib) endif() + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP TagLib::TagLib) + else() + if(TagLib_FIND_REQUIRED) + message(FATAL_ERROR "TagLib not found.") + endif() endif() - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP TagLib::TagLib) + mark_as_advanced(TAGLIB_INCLUDE_DIR TAGLIB_LIBRARY) endif() -mark_as_advanced(TAGLIB_INCLUDE_DIR TAGLIB_LIBRARY) diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index 891a2e7b82e2e..d95a7343b6a67 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -3,7 +3,6 @@ # ----------- # Finds the TinyXML2 library # -# # This will define the following target: # # tinyxml2::tinyxml2 - The TinyXML2 library @@ -15,111 +14,91 @@ if(NOT TARGET tinyxml2::tinyxml2) SETUP_BUILD_VARS() - # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build - # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, skip the - # config search and act like tinyxml2 doesnt exist on system - if(NOT ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_TINYXML2)) - - # Darwin systems we want to avoid system packages. We are entirely self sufficient - # Avoids homebrew populating rubbish we cant control - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(_tinyxml2_find_option NO_SYSTEM_ENVIRONMENT_PATH) - endif() - - find_package(TINYXML2 ${_tinyxml2_find_option} CONFIG QUIET) + # Darwin systems we want to avoid system packages. We are entirely self sufficient + # Avoids homebrew populating rubbish we cant control + # Do we want to set this for all except LINUX/FREEBSD possibly? + if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(_tinyxml2_find_option NO_SYSTEM_ENVIRONMENT_PATH) endif() - # Some linux distro's dont package cmake config files for TinyXML2 - # This means that they will fall into the below and we will run a pkg_check_modules - # for one last search - if(TINYXML2_VERSION VERSION_LESS ${${MODULE}_VER}) - - if(ENABLE_INTERNAL_TINYXML2) - set(TINYXML2_VERSION ${${MODULE}_VER}) - set(TINYXML2_DEBUG_POSTFIX d) - - find_package(Patch MODULE REQUIRED) - - if(UNIX) - # ancient patch (Apple/freebsd) fails to patch tinyxml2 CMakeLists.txt file due to it being crlf encoded - # Strip crlf before applying patches. - # Freebsd fails even harder and requires both .patch and CMakeLists.txt to be crlf stripped - # possibly add requirement for freebsd on gpatch? Wouldnt need to copy/strip the patch file then - set(PATCH_COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch - COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch - COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch) - else() - set(PATCH_COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch) - endif() + find_package(TINYXML2 ${_tinyxml2_find_option} CONFIG QUIET) - if(CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR STREQUAL Xcode) - # Multiconfig generators fail due to file(GENERATE tinyxml.pc) command. - # This patch makes it generate a distinct named pc file for each build type and rename - # pc file on install - list(APPEND PATCH_COMMAND COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/002-multiconfig-gen-pkgconfig.patch) - endif() - - set(CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} - -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} - -Dtinyxml2_BUILD_TESTING=OFF) - - BUILD_DEP_TARGET() + # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build + # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway + if((TINYXML2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_TINYXML2) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_TINYXML2)) + + set(TINYXML2_VERSION ${${MODULE}_VER}) + set(TINYXML2_DEBUG_POSTFIX d) + + find_package(Patch MODULE REQUIRED) + + if(UNIX) + # ancient patch (Apple/freebsd) fails to patch tinyxml2 CMakeLists.txt file due to it being crlf encoded + # Strip crlf before applying patches. + # Freebsd fails even harder and requires both .patch and CMakeLists.txt to be crlf stripped + # possibly add requirement for freebsd on gpatch? Wouldnt need to copy/strip the patch file then + set(PATCH_COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch + COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch + COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch) else() - # This is the fallback case where linux distro's dont ship cmake config files - # use the old find_library way. Only do this if we didnt find a cmake config - # in the event of the version < depends version - if(NOT TARGET tinyxml2::tinyxml2) - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_TINYXML2 tinyxml2 QUIET) - endif() - - find_path(TINYXML2_INCLUDE_DIR tinyxml2.h - PATHS ${PC_TINYXML2_INCLUDEDIR}) - find_library(TINYXML2_LIBRARY_RELEASE NAMES tinyxml2 - PATHS ${PC_TINYXML2_LIBDIR}) - find_library(TINYXML2_LIBRARY_DEBUG NAMES tinyxml2d - PATHS ${PC_TINYXML2_LIBDIR}) + set(PATCH_COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch) + endif() - set(TINYXML2_VERSION ${PC_TINYXML2_VERSION}) - endif() + if(CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR STREQUAL Xcode) + # Multiconfig generators fail due to file(GENERATE tinyxml.pc) command. + # This patch makes it generate a distinct named pc file for each build type and rename + # pc file on install + list(APPEND PATCH_COMMAND COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/002-multiconfig-gen-pkgconfig.patch) endif() + set(CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -Dtinyxml2_BUILD_TESTING=OFF) + + BUILD_DEP_TARGET() + else() + # This is the fallback case where linux distro's dont ship cmake config files + # use the old find_library way. Only do this if we didnt find a cmake config + # in the event of the version < depends version if(NOT TARGET tinyxml2::tinyxml2) - add_library(tinyxml2::tinyxml2 UNKNOWN IMPORTED) - if(TINYXML2_LIBRARY_RELEASE) - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${TINYXML2_LIBRARY_RELEASE}") + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_TINYXML2 tinyxml2 QUIET) endif() - if(TINYXML2_LIBRARY_DEBUG) - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${TINYXML2_LIBRARY_DEBUG}") - endif() - set_target_properties(tinyxml2::tinyxml2 PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${TINYXML2_INCLUDE_DIR}") - endif() - if(TARGET tinyxml2) - add_dependencies(tinyxml2::tinyxml2 tinyxml2) + find_path(TINYXML2_INCLUDE_DIR tinyxml2.h + PATHS ${PC_TINYXML2_INCLUDEDIR}) + find_library(TINYXML2_LIBRARY_RELEASE NAMES tinyxml2 + PATHS ${PC_TINYXML2_LIBDIR}) + find_library(TINYXML2_LIBRARY_DEBUG NAMES tinyxml2d + PATHS ${PC_TINYXML2_LIBDIR}) + + set(TINYXML2_VERSION ${PC_TINYXML2_VERSION}) endif() endif() if(TARGET tinyxml2::tinyxml2) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is tinyxml2ConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info get_target_property(_TINYXML2_CONFIGURATIONS tinyxml2::tinyxml2 IMPORTED_CONFIGURATIONS) foreach(_tinyxml2_config IN LISTS _TINYXML2_CONFIGURATIONS) # Some non standard config (eg None on Debian) # Just set to RELEASE var so select_library_configurations can continue to work its magic - if((NOT ${_tinyxml2_config} STREQUAL "RELEASE") AND - (NOT ${_tinyxml2_config} STREQUAL "DEBUG")) - get_target_property(TINYXML2_LIBRARY_RELEASE tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config}) + string(TOUPPER ${_tinyxml2_config} _tinyxml2_config_UPPER) + if((NOT ${_tinyxml2_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_tinyxml2_config_UPPER} STREQUAL "DEBUG")) + get_target_property(TINYXML2_LIBRARY_RELEASE tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) else() - get_target_property(TINYXML2_LIBRARY_${_tinyxml2_config} tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config}) + get_target_property(TINYXML2_LIBRARY_${_tinyxml2_config_UPPER} tinyxml2::tinyxml2 IMPORTED_LOCATION_${_tinyxml2_config_UPPER}) endif() endforeach() + # Need this, as we may only get the existing TARGET from system and not build or use pkg-config get_target_property(TINYXML2_INCLUDE_DIR tinyxml2::tinyxml2 INTERFACE_INCLUDE_DIRECTORIES) endif() @@ -131,13 +110,27 @@ if(NOT TARGET tinyxml2::tinyxml2) REQUIRED_VARS TINYXML2_LIBRARY TINYXML2_INCLUDE_DIR VERSION_VAR TINYXML2_VERSION) - # Check whether we already have tinyxml2::tinyxml2 target added to dep property list - get_property(CHECK_INTERNAL_DEPS GLOBAL PROPERTY INTERNAL_DEPS_PROP) - list(FIND CHECK_INTERNAL_DEPS "tinyxml2::tinyxml2" TINYXML2_PROP_FOUND) + if(TinyXML2_FOUND) + if(NOT TARGET tinyxml2::tinyxml2) + add_library(tinyxml2::tinyxml2 UNKNOWN IMPORTED) + if(TINYXML2_LIBRARY_RELEASE) + set_target_properties(tinyxml2::tinyxml2 PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${TINYXML2_LIBRARY_RELEASE}") + endif() + if(TINYXML2_LIBRARY_DEBUG) + set_target_properties(tinyxml2::tinyxml2 PROPERTIES + IMPORTED_CONFIGURATIONS DEBUG + IMPORTED_LOCATION_DEBUG "${TINYXML2_LIBRARY_DEBUG}") + endif() + set_target_properties(tinyxml2::tinyxml2 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${TINYXML2_INCLUDE_DIR}") + endif() - # list(FIND) returns -1 if search item not found - if(TINYXML2_PROP_FOUND STREQUAL "-1") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP tinyxml2::tinyxml2) + if(TARGET tinyxml2) + add_dependencies(tinyxml2::tinyxml2 tinyxml2) + endif() endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP tinyxml2::tinyxml2) endif() diff --git a/cmake/modules/FindUUID.cmake b/cmake/modules/FindUUID.cmake index 0c2ff5aa225f4..49fc5b28ce566 100644 --- a/cmake/modules/FindUUID.cmake +++ b/cmake/modules/FindUUID.cmake @@ -3,41 +3,33 @@ # -------- # Finds the libuuid library # -# This will define the following variables:: -# -# UUID_FOUND - system has libuuid -# UUID_INCLUDE_DIRS - the libuuid include directory -# UUID_LIBRARIES - the libuuid libraries -# -# and the following imported targets:: +# This will define the following target: # # UUID::UUID - The libuuid library +# -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_UUID uuid QUIET) -endif() - -find_path(UUID_INCLUDE_DIR uuid/uuid.h - PATHS ${PC_UUID_INCLUDEDIR}) -find_library(UUID_LIBRARY uuid - PATHS ${PC_UUID_LIBRARY}) -set(UUID_VERSION ${PC_UUID_VERSION}) +if(NOT TARGET UUID::UUID) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_UUID uuid QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(UUID - REQUIRED_VARS UUID_LIBRARY UUID_INCLUDE_DIR - VERSION_VAR UUID_VERSION) + find_path(UUID_INCLUDE_DIR uuid/uuid.h + PATHS ${PC_UUID_INCLUDEDIR}) + find_library(UUID_LIBRARY uuid + PATHS ${PC_UUID_LIBRARY}) + set(UUID_VERSION ${PC_UUID_VERSION}) -if(UUID_FOUND) - set(UUID_LIBRARIES ${UUID_LIBRARY}) - set(UUID_INCLUDE_DIRS ${UUID_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(UUID + REQUIRED_VARS UUID_LIBRARY UUID_INCLUDE_DIR + VERSION_VAR UUID_VERSION) - if(NOT TARGET UUID::UUID) + if(UUID_FOUND) add_library(UUID::UUID UNKNOWN IMPORTED) set_target_properties(UUID::UUID PROPERTIES IMPORTED_LOCATION "${UUID_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${UUID_INCLUDE_DIR}") endif() -endif() -mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) + mark_as_advanced(UUID_INCLUDE_DIR UUID_LIBRARY) +endif() diff --git a/cmake/scripts/common/Macros.cmake b/cmake/scripts/common/Macros.cmake index b6863960b7f0c..e98ef7564f00a 100644 --- a/cmake/scripts/common/Macros.cmake +++ b/cmake/scripts/common/Macros.cmake @@ -73,6 +73,10 @@ function(core_add_library name) set_target_properties(${name} PROPERTIES PREFIX "") set(core_DEPENDS ${name} ${core_DEPENDS} CACHE STRING "" FORCE) add_dependencies(${name} ${GLOBAL_TARGET_DEPS}) + + # Adds global target to library. This propagates dep lib info (eg include_dir locations) + target_link_libraries(${name} PRIVATE ${GLOBAL_TARGET_DEPS}) + set(CORE_LIBRARY ${name} PARENT_SCOPE) if(NOT MSVC) diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index 8f80f8b08b165..a88132b3bc95a 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -158,7 +158,7 @@ if(ENABLE_OPTICAL) DVDDirectory.h) endif() -if(NFS_FOUND) +if(TARGET libnfs::nfs) list(APPEND SOURCES NFSDirectory.cpp NFSFile.cpp) list(APPEND HEADERS NFSDirectory.h diff --git a/xbmc/filesystem/test/CMakeLists.txt b/xbmc/filesystem/test/CMakeLists.txt index 9572459cf198e..0020050598e8b 100644 --- a/xbmc/filesystem/test/CMakeLists.txt +++ b/xbmc/filesystem/test/CMakeLists.txt @@ -8,7 +8,7 @@ if(MICROHTTPD_FOUND) list(APPEND SOURCES TestHTTPDirectory.cpp) endif() -if(NFS_FOUND) +if(TARGET libnfs::nfs) list(APPEND SOURCES TestNfsFile.cpp) endif() diff --git a/xbmc/interfaces/swig/CMakeLists.txt b/xbmc/interfaces/swig/CMakeLists.txt index b1748fd87a757..a7c683e7e7682 100644 --- a/xbmc/interfaces/swig/CMakeLists.txt +++ b/xbmc/interfaces/swig/CMakeLists.txt @@ -59,6 +59,8 @@ 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}) +# This propagates target options from dependencies (eg spdlog definitions) +target_link_libraries(python_binding PRIVATE ${GLOBAL_TARGET_DEPS}) if(CORE_SYSTEM_NAME STREQUAL windowsstore) set_target_properties(python_binding PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") From 72c4b0983fb0d9156daffabd006931aa2b925cde Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Fri, 1 Sep 2023 12:13:20 +0100 Subject: [PATCH 131/811] CPUInfoLinux: Support cpu_thermal hwmon temperature module On most ARM platforms, including Raspberry Pi the hwmon is named cpu_thermal: LibreELEC:~ # cat /sys/class/hwmon/hwmon1/name cpu_thermal LibreELEC:~ # cat /sys/class/hwmon/hwmon1/temp1_input 42450 Also support that name. --- xbmc/platform/linux/CPUInfoLinux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/linux/CPUInfoLinux.cpp b/xbmc/platform/linux/CPUInfoLinux.cpp index 79680fb52f903..728480ca2542d 100644 --- a/xbmc/platform/linux/CPUInfoLinux.cpp +++ b/xbmc/platform/linux/CPUInfoLinux.cpp @@ -95,11 +95,12 @@ CCPUInfoLinux::CCPUInfoLinux() if (freqPath.Exists()) m_freqPath = freqStr; - const std::array modules = { + const std::array modules = { "coretemp", "k10temp", "scpi_sensors", "imx_thermal_zone", + "cpu_thermal", }; for (int i = 0; i < 20; i++) From 822740366aa6c6650056579b14bec1e5ba11a159 Mon Sep 17 00:00:00 2001 From: Andrey Filipenkov Date: Mon, 28 Aug 2023 16:37:56 +0300 Subject: [PATCH 132/811] call -eventWithCGEvent: on the main queue fixes crash on pressing Caps Lock on an ARM Mac --- xbmc/platform/darwin/osx/HotKeyController.m | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/osx/HotKeyController.m b/xbmc/platform/darwin/osx/HotKeyController.m index 423edb083d05e..378eeb7cdb11a 100644 --- a/xbmc/platform/darwin/osx/HotKeyController.m +++ b/xbmc/platform/darwin/osx/HotKeyController.m @@ -123,7 +123,10 @@ static CGEventRef tapEventCallback2(CGEventTapProxy proxy, CGEventType type, CGE if ((type != NX_SYSDEFINED) || (![hot_key_controller getActive])) return event; - NSEvent *nsEvent = [NSEvent eventWithCGEvent:event]; + NSEvent* __block nsEvent; + dispatch_sync(dispatch_get_main_queue(), ^{ + nsEvent = [NSEvent eventWithCGEvent:event]; + }); if (!nsEvent || [nsEvent subtype] != 8) return event; From 9ecb597559456b4e790a47cd5237b19793ef8d52 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 16 Aug 2023 17:02:53 +0200 Subject: [PATCH 133/811] bump libandroidjni --- tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION b/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION index a43e4f7c07ebb..d8ee7b58f2d38 100644 --- a/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION +++ b/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION @@ -1,6 +1,6 @@ LIBNAME=libandroidjni -VERSION=c87bf16066428b13b6c3d53451a3a89ae8f70208 +VERSION=d2d606d06010c8e58f624f8502f03906695542e9 BASE_URL=https://github.com/xbmc/libandroidjni/archive ARCHIVE=$(VERSION).tar.gz -SHA512=688e471b0d87d1d983c70a5525edec75298eb25cd54515973d9d8a919f59b4cbc5b7eb712032d7a627e879ab9e64f579aecb862544954c396a796edc0e24a0fa +SHA512=a4f54e7c8ae16cf9ae8c4cbde448a9c39d1c82916d68ac43830a55c77a77924a2de9062fe467d13eed25998e561e3310ab65613560c6f00b68826a0dad6e6c6e BYPRODUCT=libandroidjni.a From 82465be609a0893ff44a9f7f717f3e3192cba0a6 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 21 Aug 2023 11:27:04 +0200 Subject: [PATCH 134/811] [ActiveAE] Improve CAESinkFactory::ParseDevice Prepares to allow include friendly name in the device string e.g: Before WASAPI:{B12C1A75-F8A8-4071-8CFB-A285C619CCB8} After WASAPI:{B12C1A75-F8A8-4071-8CFB-A285C619CCB8}:HDMI - DENON-AVR (NVIDIA High Definition Audio) --- xbmc/cores/AudioEngine/AESinkFactory.cpp | 41 +++++++++---- xbmc/cores/AudioEngine/AESinkFactory.h | 11 +++- .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 24 ++++---- .../Engines/ActiveAE/ActiveAESink.cpp | 58 +++++++++---------- 4 files changed, 79 insertions(+), 55 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp index d93a96351f701..6f27372de7786 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.cpp +++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp @@ -38,41 +38,58 @@ bool CAESinkFactory::HasSinks() return !m_AESinkRegEntry.empty(); } -void CAESinkFactory::ParseDevice(std::string &device, std::string &driver) +AESinkDevice CAESinkFactory::ParseDevice(const std::string& device) { - int pos = device.find_first_of(':'); + AESinkDevice dev{}; + dev.name = device; + + size_t pos = dev.name.find_first_of(':'); bool found = false; - if (pos > 0) + + if (pos != std::string::npos) { - driver = device.substr(0, pos); + dev.driver = device.substr(0, pos); for (const auto& reg : m_AESinkRegEntry) { - if (!StringUtils::EqualsNoCase(driver, reg.second.sinkName)) + if (!StringUtils::EqualsNoCase(dev.driver, reg.second.sinkName)) continue; - device = device.substr(pos + 1, device.length() - pos - 1); + dev.name = dev.name.substr(pos + 1, dev.name.length() - pos - 1); found = true; } } if (!found) - driver.clear(); + dev.driver.clear(); + + pos = dev.name.find_first_of(':'); + + if (pos != std::string::npos) + { + // if no known driver found considers the string starts + // with the device name and discarts the rest + if (found) + dev.friendlyName = dev.name.substr(pos + 1); + dev.name = dev.name.substr(0, pos); + } + + return dev; } -std::unique_ptr CAESinkFactory::Create(std::string& device, AEAudioFormat& desiredFormat) +std::unique_ptr CAESinkFactory::Create(const std::string& device, + AEAudioFormat& desiredFormat) { // extract the driver from the device string if it exists - std::string driver; - ParseDevice(device, driver); + const AESinkDevice dev = ParseDevice(device); AEAudioFormat tmpFormat = desiredFormat; std::unique_ptr sink; - std::string tmpDevice = device; + std::string tmpDevice = dev.name; for (const auto& reg : m_AESinkRegEntry) { - if (driver != reg.second.sinkName) + if (dev.driver != reg.second.sinkName) continue; sink = reg.second.createFunc(tmpDevice, tmpFormat); diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h index 92728bf2f91d4..704dc7673e7ce 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -42,6 +42,13 @@ struct AESinkRegEntry Cleanup cleanupFunc; }; +struct AESinkDevice +{ + std::string driver; + std::string name; + std::string friendlyName; +}; + class CAESinkFactory { public: @@ -49,8 +56,8 @@ class CAESinkFactory static void ClearSinks(); static bool HasSinks(); - static void ParseDevice(std::string &device, std::string &driver); - static std::unique_ptr Create(std::string& device, AEAudioFormat& desiredFormat); + static AESinkDevice ParseDevice(const std::string& device); + static std::unique_ptr Create(const std::string& device, AEAudioFormat& desiredFormat); static void EnumerateEx(std::vector& list, bool force, const std::string& driver); static void Cleanup(); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index f205a0c66bfd5..696d88c3f64e7 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -1191,17 +1191,18 @@ void CActiveAE::Configure(AEAudioFormat *desiredFmt) m_extKeepConfig = 0ms; std::string device = (m_sinkRequestFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device; - std::string driver; - CAESinkFactory::ParseDevice(device, driver); - if ((!CompareFormat(m_sinkRequestFormat, m_sinkFormat) && !CompareFormat(m_sinkRequestFormat, oldSinkRequestFormat)) || - m_currDevice.compare(device) != 0 || - m_settings.driver.compare(driver) != 0) + + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + if ((!CompareFormat(m_sinkRequestFormat, m_sinkFormat) && + !CompareFormat(m_sinkRequestFormat, oldSinkRequestFormat)) || + m_currDevice.compare(dev.name) != 0 || m_settings.driver.compare(dev.driver) != 0) { FlushEngine(); if (!InitSink()) return; - m_settings.driver = driver; - m_currDevice = device; + m_settings.driver = dev.driver; + m_currDevice = dev.name; initSink = true; m_stats.Reset(m_sinkFormat.m_sampleRate, m_mode == MODE_PCM); m_sink.m_controlPort.SendOutMessage(CSinkControlProtocol::VOLUME, &m_volume, sizeof(float)); @@ -1781,12 +1782,11 @@ bool CActiveAE::NeedReconfigureSink() ApplySettingsToFormat(newFormat, m_settings); std::string device = (newFormat.m_dataFormat == AE_FMT_RAW) ? m_settings.passthroughdevice : m_settings.device; - std::string driver; - CAESinkFactory::ParseDevice(device, driver); - return !CompareFormat(newFormat, m_sinkFormat) || - m_currDevice.compare(device) != 0 || - m_settings.driver.compare(driver) != 0; + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + return !CompareFormat(newFormat, m_sinkFormat) || m_currDevice.compare(dev.name) != 0 || + m_settings.driver.compare(dev.driver) != 0; } bool CActiveAE::InitSink() diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 01a204c852039..0b22c41b3a7de 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -73,15 +73,14 @@ void CActiveAESink::Dispose() AEDeviceType CActiveAESink::GetDeviceType(const std::string &device) { - std::string dev = device; - std::string dri; - CAESinkFactory::ParseDevice(dev, dri); + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { for (AEDeviceInfoList::iterator itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) return info.m_deviceType; } } @@ -104,18 +103,16 @@ bool CActiveAESink::HasPassthroughDevice() bool CActiveAESink::SupportsFormat(const std::string &device, AEAudioFormat &format) { - std::string dev = device; - std::string dri; + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); - CAESinkFactory::ParseDevice(dev, dri); for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { - if (dri == itt->m_sinkName) + if (dev.driver == itt->m_sinkName) { for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) { bool isRaw = format.m_dataFormat == AE_FMT_RAW; bool formatExists = false; @@ -184,18 +181,16 @@ bool CActiveAESink::SupportsFormat(const std::string &device, AEAudioFormat &for bool CActiveAESink::NeedIECPacking() { - std::string dev = m_device; - std::string dri; + const AESinkDevice dev = CAESinkFactory::ParseDevice(m_device); - CAESinkFactory::ParseDevice(dev, dri); for (auto itt = m_sinkInfoList.begin(); itt != m_sinkInfoList.end(); ++itt) { - if (dri == itt->m_sinkName) + if (dev.driver == itt->m_sinkName) { for (auto itt2 = itt->m_deviceInfoList.begin(); itt2 != itt->m_deviceInfoList.end(); ++itt2) { CAEDeviceInfo& info = *itt2; - if (info.m_deviceName == dev) + if (info.m_deviceName == dev.name) { return info.m_wantsIECPassthrough; } @@ -757,6 +752,16 @@ void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrou std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; + const std::string friendlyName = (devInfo.m_deviceName != devInfo.m_displayName) + ? devInfo.m_displayName + : devInfo.m_displayNameExtra; + + if (!friendlyName.empty()) + { + device.append(":"); + device.append(friendlyName); + } + std::stringstream ss; /* add the sink name if we have more then one sink type */ @@ -793,15 +798,12 @@ void CActiveAESink::GetDeviceFriendlyName(const std::string& device) void CActiveAESink::OpenSink() { - // we need a copy of m_device here because ParseDevice and CreateDevice write back - // into this variable - std::string device = m_device; - std::string driver; bool passthrough = (m_requestedFormat.m_dataFormat == AE_FMT_RAW); - CAESinkFactory::ParseDevice(device, driver); - if (driver.empty() && m_sink) - driver = m_sink->GetName(); + AESinkDevice dev = CAESinkFactory::ParseDevice(m_device); + + if (dev.driver.empty() && m_sink) + dev.driver = m_sink->GetName(); // iec packing or raw if (passthrough) @@ -825,11 +827,10 @@ void CActiveAESink::OpenSink() } // get the display name of the device - GetDeviceFriendlyName(device); + GetDeviceFriendlyName(dev.name); // if we already have a driver, prepend it to the device string - if (!driver.empty()) - device = driver + ":" + device; + std::string device = dev.driver.empty() ? dev.name : dev.driver + ":" + dev.name; // WARNING: this changes format and does not use passthrough m_sinkFormat = m_requestedFormat; @@ -839,11 +840,10 @@ void CActiveAESink::OpenSink() // try first device in out list if (!m_sink && !m_sinkInfoList.empty()) { - driver = m_sinkInfoList.front().m_sinkName; - device = m_sinkInfoList.front().m_deviceInfoList.front().m_deviceName; - GetDeviceFriendlyName(device); - if (!driver.empty()) - device = driver + ":" + device; + dev.driver = m_sinkInfoList.front().m_sinkName; + dev.name = m_sinkInfoList.front().m_deviceInfoList.front().m_deviceName; + GetDeviceFriendlyName(dev.name); + device = dev.driver.empty() ? dev.name : dev.driver + ":" + dev.name; m_sinkFormat = m_requestedFormat; CLog::Log(LOGDEBUG, "CActiveAESink::OpenSink - trying to open device {}", device); m_sink = CAESinkFactory::Create(device, m_sinkFormat); From ba77cb94fc47faf4528892f33baee500dece1b54 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Mon, 21 Aug 2023 12:32:56 +0200 Subject: [PATCH 135/811] [ActiveAE] Validates audio device names in settings (existence in system) If device name not exist (drivers updates, small HW changes, etc.) is used this fallback logic: 1. Try other device in the same driver with same friendly name. 2. Try the default device of same driver. 3. Try the first device of same driver. 4. Try the default device of any driver. 5. Fallback to the first device of any driver. This should avoid 99% of times unexpected settings changes, i.e. from WASAPI to DIRECTSOUND. Settings are auto updated/fixed without user intervention when change is the same audio device renamed e.g: after audio driver updates. --- xbmc/cores/AudioEngine/AESinkFactory.h | 5 + .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 56 ++++++++ .../AudioEngine/Engines/ActiveAE/ActiveAE.h | 1 + .../Engines/ActiveAE/ActiveAESink.cpp | 122 ++++++++++++++++-- .../Engines/ActiveAE/ActiveAESink.h | 1 + xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp | 16 +++ xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h | 2 + 7 files changed, 192 insertions(+), 11 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.h b/xbmc/cores/AudioEngine/AESinkFactory.h index 704dc7673e7ce..18b2eff09cf60 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.h +++ b/xbmc/cores/AudioEngine/AESinkFactory.h @@ -47,6 +47,11 @@ struct AESinkDevice std::string driver; std::string name; std::string friendlyName; + + bool IsSameDeviceAs(const AESinkDevice& d) const + { + return driver == d.driver && (name == d.name || friendlyName == d.friendlyName); + } }; class CAESinkFactory diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index 696d88c3f64e7..8bd9e45a7644a 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -516,6 +516,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_extError = false; m_sink.EnumerateSinkList(false, ""); LoadSettings(); + ValidateOutputDevices(true); Configure(); if (!m_isWinSysReg) { @@ -647,6 +648,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECHANGE); m_sink.EnumerateSinkList(true, ""); LoadSettings(); + ValidateOutputDevices(false); m_extError = false; Configure(); if (!m_extError) @@ -669,6 +671,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) { UnconfigureSink(); LoadSettings(); + ValidateOutputDevices(false); m_extError = false; Configure(); if (!m_extError) @@ -890,6 +893,7 @@ void CActiveAE::StateMachine(int signal, Protocol *port, Message *msg) m_controlPort.PurgeOut(CActiveAEControlProtocol::DEVICECOUNTCHANGE); m_sink.EnumerateSinkList(true, ""); LoadSettings(); + ValidateOutputDevices(false); } Configure(); if (!displayReset) @@ -2670,6 +2674,58 @@ void CActiveAE::LoadSettings() m_settings.silenceTimeoutMinutes = settings->GetInt(CSettings::SETTING_AUDIOOUTPUT_STREAMSILENCE); } +void CActiveAE::ValidateOutputDevices(bool saveChanges) +{ + std::string device = m_sink.ValidateOuputDevice(m_settings.device, false); + + if (!device.empty() && device != m_settings.device) + { + CLog::LogF(LOGWARNING, "audio output device setting has been updated from '{}' to '{}'", + m_settings.device, device); + + const AESinkDevice oldDevice = CAESinkFactory::ParseDevice(m_settings.device); + const AESinkDevice newDevice = CAESinkFactory::ParseDevice(device); + + m_settings.device = device; + + if (saveChanges && newDevice.IsSameDeviceAs(oldDevice)) + { + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings) + { + settings->SetString(CSettings::SETTING_AUDIOOUTPUT_AUDIODEVICE, m_settings.device); + settings->Save(); + CLog::LogF(LOGDEBUG, "the change of the audio output device setting has been saved"); + } + } + } + + device = m_sink.ValidateOuputDevice(m_settings.passthroughdevice, true); + + if (!device.empty() && device != m_settings.passthroughdevice) + { + CLog::LogF(LOGWARNING, "passthrough output device setting has been updated from '{}' to '{}'", + m_settings.passthroughdevice, device); + + const AESinkDevice oldDevice = CAESinkFactory::ParseDevice(m_settings.passthroughdevice); + const AESinkDevice newDevice = CAESinkFactory::ParseDevice(device); + + m_settings.passthroughdevice = device; + + if (saveChanges && newDevice.IsSameDeviceAs(oldDevice)) + { + const auto settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + if (settings) + { + settings->SetString(CSettings::SETTING_AUDIOOUTPUT_PASSTHROUGHDEVICE, + m_settings.passthroughdevice); + settings->Save(); + CLog::LogF(LOGDEBUG, "the change of the passthrough output device setting has been saved"); + } + } + } +} + void CActiveAE::Start() { Create(); diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h index 1ddce5eb18fbe..7c43d2037ce4e 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.h @@ -308,6 +308,7 @@ class CActiveAE : public IAE, public IDispResource, private CThread void UnconfigureSink(); void Dispose(); void LoadSettings(); + void ValidateOutputDevices(bool saveChanges); bool NeedReconfigureBuffers(); bool NeedReconfigureSink(); void ApplySettingsToFormat(AEAudioFormat& format, diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp index 0b22c41b3a7de..54d46e3fd86c3 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp @@ -728,6 +728,116 @@ void CActiveAESink::PrintSinks(std::string& driver) } } +std::string CActiveAESink::ValidateOuputDevice(const std::string& device, bool passthrough) const +{ + if (m_sinkInfoList.empty()) + return {}; + + const AESinkDevice dev = CAESinkFactory::ParseDevice(device); + + // find exact match of deviceName in same driver + if (!dev.driver.empty() && !dev.name.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (d.m_deviceName == dev.name) + return d.ToDeviceString(sink.m_sinkName); + } + } + } + + // find same friendly name on other device in same driver + if (!dev.driver.empty() && !dev.friendlyName.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (d.GetFriendlyName() == dev.friendlyName) + return d.ToDeviceString(sink.m_sinkName); + } + } + } + + std::string firstDevice; + + // find default device of same driver or first device of same driver + if (!dev.driver.empty()) + { + for (const auto& sink : m_sinkInfoList) + { + if (sink.m_sinkName != dev.driver) + continue; + + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (firstDevice.empty()) + firstDevice = d.ToDeviceString(sink.m_sinkName); + + if (d.m_deviceName.find("default") != std::string::npos) + return d.ToDeviceString(sink.m_sinkName); + } + + if (!firstDevice.empty()) + break; + } + } + + // return first device of same driver + if (!firstDevice.empty()) + return firstDevice; + + firstDevice.clear(); + + // find the default of any driver or first of any driver + for (const auto& sink : m_sinkInfoList) + { + for (const auto& d : sink.m_deviceInfoList) + { + if (passthrough && (d.m_deviceType == AE_DEVTYPE_PCM || d.m_onlyPCM)) + continue; + + if (!passthrough && d.m_onlyPassthrough) + continue; + + if (firstDevice.empty()) + firstDevice = d.ToDeviceString(sink.m_sinkName); + + if (d.m_deviceName.find("default") != std::string::npos) + return d.ToDeviceString(sink.m_sinkName); + } + } + + // return first device of any driver or empty + return firstDevice; +} + void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrough) { EnumerateSinkList(false, ""); @@ -750,17 +860,7 @@ void CActiveAESink::EnumerateOutputDevices(AEDeviceList &devices, bool passthrou if (devInfo.m_onlyPCM && passthrough) continue; - std::string device = sinkInfo.m_sinkName + ":" + devInfo.m_deviceName; - - const std::string friendlyName = (devInfo.m_deviceName != devInfo.m_displayName) - ? devInfo.m_displayName - : devInfo.m_displayNameExtra; - - if (!friendlyName.empty()) - { - device.append(":"); - device.append(friendlyName); - } + const std::string device = devInfo.ToDeviceString(sinkInfo.m_sinkName); std::stringstream ss; diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h index aab1c315dd01e..44f118eb1472a 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.h @@ -97,6 +97,7 @@ class CActiveAESink : private CThread void EnumerateSinkList(bool force, std::string driver); void EnumerateOutputDevices(AEDeviceList &devices, bool passthrough); + std::string ValidateOuputDevice(const std::string& device, bool passthrough) const; void Start(); void Dispose(); AEDeviceType GetDeviceType(const std::string &device); diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp index 473a8490464ce..8f8f21d97fa2f 100644 --- a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp @@ -64,3 +64,19 @@ std::string CAEDeviceInfo::DeviceTypeToString(enum AEDeviceType deviceType) } return "INVALID"; } + +std::string CAEDeviceInfo::GetFriendlyName() const +{ + return (m_deviceName != m_displayName) ? m_displayName : m_displayNameExtra; +} + +std::string CAEDeviceInfo::ToDeviceString(const std::string& driver) const +{ + std::string device = driver.empty() ? m_deviceName : driver + ":" + m_deviceName; + + const std::string fn = GetFriendlyName(); + if (!fn.empty()) + device += ":" + fn; + + return device; +} diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h index 387d61700f4b0..c28c29c9f68b4 100644 --- a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h +++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.h @@ -47,6 +47,8 @@ class CAEDeviceInfo operator std::string(); static std::string DeviceTypeToString(enum AEDeviceType deviceType); + std::string GetFriendlyName() const; + std::string ToDeviceString(const std::string& driver) const; }; typedef std::vector AEDeviceInfoList; From eaa5f0aeaebacc14e01fb52c1bfd835b78d5441d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 30 Aug 2023 13:56:01 +0200 Subject: [PATCH 136/811] [video] Fix art selection dialog re-appearing endless on 'Set actor thumb' and 'Set artist thumb' context menu item action. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index ff9d4b489a2da..2d662994d4a39 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1935,17 +1935,28 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); + } else { // SEASON, SHOW, SET + + // We want to re-open the art type selection dialog after selecting an image for the type + // until user canceled the art type selection dialog, controlled via 'finished' value by + // the caller of this method. + artType = ChooseArtType(*item); if (artType.empty()) + { + finished = true; return false; + } + + finished = false; if (artType == "fanart" && type != MediaTypeVideoCollection) { const bool result = OnGetFanart(item); - finished = false; return result; } @@ -2110,10 +2121,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetWindowManager().SendMessage(msg); - finished = false; return true; } From b4eb710878bc16e3ec967a3b15e635b498d0fb82 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Fri, 1 Sep 2023 19:17:23 +0100 Subject: [PATCH 137/811] CDVDDemuxFFmpeg: After seek avoid discarding packet when waiting for valid timestamp Otherwise we are likely to lose the I frame and stall video until the next one. --- .../VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 16 ++++++++++++---- .../VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 03388f761b1f3..4d64c2540a5d3 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1026,7 +1026,7 @@ double CDVDDemuxFFmpeg::ConvertTimestamp(int64_t pts, int den, int num) return timestamp * DVD_TIME_BASE; } -DemuxPacket* CDVDDemuxFFmpeg::Read() +DemuxPacket* CDVDDemuxFFmpeg::ReadInternal(bool keep) { DemuxPacket* pPacket = NULL; // on some cases where the received packet is invalid we will need to return an empty packet (0 length) otherwise the main loop (in CVideoPlayer) @@ -1182,8 +1182,11 @@ DemuxPacket* CDVDDemuxFFmpeg::Read() // the stream might not have been created yet pPacket->iStreamId = m_pkt.pkt.stream_index; } - m_pkt.result = -1; - av_packet_unref(&m_pkt.pkt); + if (!keep) + { + m_pkt.result = -1; + av_packet_unref(&m_pkt.pkt); + } } } } // end of lock scope @@ -1243,6 +1246,11 @@ DemuxPacket* CDVDDemuxFFmpeg::Read() return pPacket; } +DemuxPacket* CDVDDemuxFFmpeg::Read() +{ + return ReadInternal(false); +} + bool CDVDDemuxFFmpeg::SeekTime(double time, bool backwards, double* startpts) { bool hitEnd = false; @@ -1358,7 +1366,7 @@ bool CDVDDemuxFFmpeg::SeekTime(double time, bool backwards, double* startpts) m_pkt.result = -1; av_packet_unref(&m_pkt.pkt); - DemuxPacket* pkt = Read(); + DemuxPacket* pkt = ReadInternal(true); if (!pkt) { KODI::TIME::Sleep(10ms); diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h index 2cf4707e6f53c..03fb98552c663 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.h @@ -92,6 +92,7 @@ class CDVDDemuxFFmpeg : public CDVDDemux std::string GetFileName() override; DemuxPacket* Read() override; + DemuxPacket* ReadInternal(bool keep); bool SeekTime(double time, bool backwards = false, double* startpts = NULL) override; bool SeekByte(int64_t pos); From 4351bc74937b152334956eebb7e8a1ebac422607 Mon Sep 17 00:00:00 2001 From: Dom Cobley Date: Fri, 1 Sep 2023 19:56:21 +0100 Subject: [PATCH 138/811] CDVDDemuxFFmpeg: Also accept pts for m_currentPts Some video files have valid pts but dts=DVD_NOPTS_VALUE for I frames. Allow these up update m_currentPts --- xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 4d64c2540a5d3..1f09cccc03d99 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -1177,6 +1177,9 @@ DemuxPacket* CDVDDemuxFFmpeg::ReadInternal(bool keep) if (pPacket->dts != DVD_NOPTS_VALUE && (pPacket->dts > m_currentPts || m_currentPts == DVD_NOPTS_VALUE)) m_currentPts = pPacket->dts; + else if (pPacket->pts != DVD_NOPTS_VALUE && + (pPacket->pts > m_currentPts || m_currentPts == DVD_NOPTS_VALUE)) + m_currentPts = pPacket->pts; // store internal id until we know the continuous id presented to player // the stream might not have been created yet From dc595048c6b74d9a74ff54f32c0a80709346f05f Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Sun, 2 Jul 2023 14:58:32 -0400 Subject: [PATCH 139/811] tools/depends: Add Rust toolchain, cargo-c and libdovi dependencies --- tools/depends/native/Makefile | 5 ++ tools/depends/native/cargo-c/CARGO-C-VERSION | 5 ++ tools/depends/native/cargo-c/Makefile | 38 +++++++++++++++ tools/depends/native/rustup/Makefile | 51 ++++++++++++++++++++ tools/depends/native/rustup/RUSTUP-VERSION | 5 ++ tools/depends/target/Makefile | 2 +- tools/depends/target/libdovi/LIBDOVI-VERSION | 6 +++ tools/depends/target/libdovi/Makefile | 43 +++++++++++++++++ 8 files changed, 154 insertions(+), 1 deletion(-) create mode 100644 tools/depends/native/cargo-c/CARGO-C-VERSION create mode 100644 tools/depends/native/cargo-c/Makefile create mode 100644 tools/depends/native/rustup/Makefile create mode 100644 tools/depends/native/rustup/RUSTUP-VERSION create mode 100644 tools/depends/target/libdovi/LIBDOVI-VERSION create mode 100644 tools/depends/target/libdovi/Makefile diff --git a/tools/depends/native/Makefile b/tools/depends/native/Makefile index f7a66a30b11f2..c0489a2119581 100644 --- a/tools/depends/native/Makefile +++ b/tools/depends/native/Makefile @@ -49,6 +49,10 @@ ifeq ($(OS),linux) endif endif +ifeq ($(OS),android) + NATIVE += rustup cargo-c +endif + .PHONY: $(NATIVE) native download $(DOWNLOAD_TARGETS) all: native @@ -65,6 +69,7 @@ download: $(DOWNLOAD_TARGETS) autoconf-archive: autoconf autoconf: m4 automake: autoconf +cargo-c: pkg-config openssl rustup dpkg: automake gettext libtool pkg-config tar heimdal: libtool JsonSchemaBuilder: cmake diff --git a/tools/depends/native/cargo-c/CARGO-C-VERSION b/tools/depends/native/cargo-c/CARGO-C-VERSION new file mode 100644 index 0000000000000..b3737ba01a669 --- /dev/null +++ b/tools/depends/native/cargo-c/CARGO-C-VERSION @@ -0,0 +1,5 @@ +APPNAME=cargo-c +VERSION=0.9.21 +SOURCE=$(APPNAME)-$(VERSION) +ARCHIVE=$(SOURCE).tar.gz +SHA512=855391c29843f8e5f204f889cab16d5d569ebb2174367a8be0d4be3d87141133f98fb7a6750ed0030783a1ff29f358d60c6ca79ed5bb65f61196c726f9c1a0ec diff --git a/tools/depends/native/cargo-c/Makefile b/tools/depends/native/cargo-c/Makefile new file mode 100644 index 0000000000000..3c12014999527 --- /dev/null +++ b/tools/depends/native/cargo-c/Makefile @@ -0,0 +1,38 @@ +include ../../Makefile.include CARGO-C-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile CARGO-C-VERSION ../../download-files.include + +PREFIX=$(NATIVEPREFIX) +PLATFORM=$(NATIVEPLATFORM) + +export PKG_CONFIG_PATH=$(PREFIX)/lib/pkgconfig + +APP=$(PLATFORM)/target/release/$(APPNAME) + +CARGO_ENV_VARS = RUSTUP_HOME=$(PREFIX)/.rustup \ + CARGO_HOME=$(PREFIX)/.cargo +CARGO = $(CARGO_ENV_VARS) $(PREFIX)/bin/cargo + +CLEANUP_CMD = [ -e $(PREFIX)/bin/cargo ] \ + && $(CARGO) uninstall cargo-c || true + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + +$(APP): $(PLATFORM) + $(CARGO) build --release --manifest-path $(PLATFORM)/Cargo.toml + +.installed-$(PLATFORM): $(APP) + $(CARGO) install --profile release --path $(PLATFORM) + + touch $@ + +clean: + $(CLEANUP_CMD) + rm -f .installed-$(PLATFORM) + +distclean:: + $(CLEANUP_CMD) + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/native/rustup/Makefile b/tools/depends/native/rustup/Makefile new file mode 100644 index 0000000000000..13d145cccda07 --- /dev/null +++ b/tools/depends/native/rustup/Makefile @@ -0,0 +1,51 @@ +include ../../Makefile.include RUSTUP-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile RUSTUP-VERSION ../../download-files.include + +PREFIX=$(NATIVEPREFIX) +PLATFORM=$(NATIVEPLATFORM) + +APP=$(PLATFORM)/bin/$(APPNAME) + +export RUSTUP_HOME=$(PREFIX)/.rustup +export CARGO_HOME=$(PREFIX)/.cargo + +RUST_TOOLCHAIN_VERSION=1.71.0 +RUSTUP_ENV_VARS = RUSTUP_HOME=$(PREFIX)/.rustup \ + CARGO_HOME=$(PREFIX)/.cargo +RUSTUP = $(RUSTUP_ENV_VARS) $(PREFIX)/bin/rustup + +CLEANUP_CMD=[ -e $(PREFIX)/bin/rustup ] \ + && $(RUSTUP) self uninstall -y \ + && rm -f $(PREFIX)/bin/rustup \ + && rm -f $(PREFIX)/bin/cargo || true + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + +$(APP): $(PLATFORM) + bash $(PLATFORM)/rustup-init.sh -y --no-modify-path \ + --profile minimal \ + --default-toolchain=$(RUST_TOOLCHAIN_VERSION) + +.installed-$(PLATFORM): $(APP) + ln -sf $(CARGO_HOME)/bin/rustup $(PREFIX)/bin/rustup + ln -sf $(CARGO_HOME)/bin/cargo $(PREFIX)/bin/cargo + + $(RUSTUP) default $(RUST_TOOLCHAIN_VERSION) + +ifeq ($(CROSS_COMPILING),yes) + $(RUSTUP) target add $(HOST) +endif + + touch $@ + +clean: + $(CLEANUP_CMD) + rm -f .installed-$(PLATFORM) + +distclean:: + $(CLEANUP_CMD) + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/native/rustup/RUSTUP-VERSION b/tools/depends/native/rustup/RUSTUP-VERSION new file mode 100644 index 0000000000000..ae487ce2b807b --- /dev/null +++ b/tools/depends/native/rustup/RUSTUP-VERSION @@ -0,0 +1,5 @@ +APPNAME=rustup +VERSION=1.26.0 +SOURCE=$(APPNAME)-$(VERSION) +ARCHIVE=$(SOURCE).tar.gz +SHA512=bc7cb580640248a601dbafb87c3a9e908b6c687377b4e0f88280576af15527f5837d9463f7831c14b0c274cd3170449e634cd851e0d03ea4ff1d0461d4a941be diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index 9a6e449c0000e..c968d71f8d5ec 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -76,7 +76,7 @@ endif ifeq ($(OS),android) EXCLUDED_DEPENDS = libcec libusb gtest - DEPENDS += dummy-libxbmc libuuid + DEPENDS += dummy-libxbmc libdovi libuuid PYMODULE_DEPS = dummy-libxbmc LIBUUID = libuuid endif diff --git a/tools/depends/target/libdovi/LIBDOVI-VERSION b/tools/depends/target/libdovi/LIBDOVI-VERSION new file mode 100644 index 0000000000000..6b6d6ab1fc3ec --- /dev/null +++ b/tools/depends/target/libdovi/LIBDOVI-VERSION @@ -0,0 +1,6 @@ +LIBNAME=libdovi +VERSION=3.1.2 +SOURCE=$(LIBNAME)-$(VERSION) +ARCHIVE=$(SOURCE).tar.gz +SHA512=577d5a5916dedbf222150ddb76219325e0e9a7ae91c5978b1b1fd65048d1f548e29aa8ebbbdc836380ec399e2bc105a722515f783be70837dc6403cb34586bb2 +BYPRODUCT=libdovi.a diff --git a/tools/depends/target/libdovi/Makefile b/tools/depends/target/libdovi/Makefile new file mode 100644 index 0000000000000..2d868d2fde634 --- /dev/null +++ b/tools/depends/target/libdovi/Makefile @@ -0,0 +1,43 @@ +include ../../Makefile.include LIBDOVI-VERSION ../../download-files.include +DEPS = ../../Makefile.include Makefile LIBDOVI-VERSION ../../download-files.include + +LIBDYLIB=$(PLATFORM)/target/$(HOST)/release/$(BYPRODUCT) + +CARGO_ENV_VARS = RUSTUP_HOME=$(NATIVEPREFIX)/.rustup \ + CARGO_HOME=$(NATIVEPREFIX)/.cargo +CARGO = $(CARGO_ENV_VARS) $(NATIVEPREFIX)/bin/cargo + +CARGO_BASE_OPTS = --manifest-path $(PLATFORM)/dolby_vision/Cargo.toml +ifeq ($(CROSS_COMPILING),yes) + CARGO_BASE_OPTS += --target $(HOST) +endif + +CARGO_BUILD_OPTS = --offline \ + --frozen \ + --library-type staticlib \ + --profile release \ + --prefix $(PREFIX) \ + $(CARGO_BASE_OPTS) + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); + $(CARGO) fetch $(CARGO_BASE_OPTS) + +$(LIBDYLIB): $(PLATFORM) + $(CARGO) cbuild $(CARGO_BUILD_OPTS) + +.installed-$(PLATFORM): $(LIBDYLIB) + $(CARGO) cinstall $(CARGO_BUILD_OPTS) + + touch $@ + +clean: + cd $(PLATFORM); $(CARGO) clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) From 1c52605c272aa57b5d47107a9023ac58ee7e7aee Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Sun, 16 Jul 2023 09:54:44 -0400 Subject: [PATCH 140/811] [cmake][android] Add libdovi as optional build dependency --- cmake/modules/FindLibDovi.cmake | 32 ++++++++++++++++++++++++++++ cmake/platform/android/android.cmake | 1 + 2 files changed, 33 insertions(+) create mode 100644 cmake/modules/FindLibDovi.cmake diff --git a/cmake/modules/FindLibDovi.cmake b/cmake/modules/FindLibDovi.cmake new file mode 100644 index 0000000000000..8646ba368a142 --- /dev/null +++ b/cmake/modules/FindLibDovi.cmake @@ -0,0 +1,32 @@ +# FindDovi +# ------- +# Finds the libdovi library +# +# This will define the following variables:: +# +# LIBDOVI_FOUND - system has libdovi +# LIBDOVI_INCLUDE_DIRS - the libdovi include directories +# LIBDOVI_LIBRARIES - the libdovi libraries +# LIBDOVI_DEFINITIONS - the libdovi compile definitions + +if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBDOVI libdovi QUIET) +endif() + +find_library(LIBDOVI_LIBRARY NAMES dovi libdovi + PATHS ${PC_LIBDOVI_LIBDIR} +) +find_path(LIBDOVI_INCLUDE_DIR NAMES libdovi/rpu_parser.h + PATHS ${PC_LIBDOVI_INCLUDEDIR}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(LibDovi + REQUIRED_VARS LIBDOVI_LIBRARY LIBDOVI_INCLUDE_DIR) + +if(LIBDOVI_FOUND) + set(LIBDOVI_INCLUDE_DIRS ${LIBDOVI_INCLUDE_DIR}) + set(LIBDOVI_LIBRARIES ${LIBDOVI_LIBRARY}) + set(LIBDOVI_DEFINITIONS -DHAVE_LIBDOVI=1) +endif() + +mark_as_advanced(LIBDOVI_INCLUDE_DIR LIBDOVI_LIBRARY) diff --git a/cmake/platform/android/android.cmake b/cmake/platform/android/android.cmake index 2c4fbb48f2d2f..ca3fad9dce819 100644 --- a/cmake/platform/android/android.cmake +++ b/cmake/platform/android/android.cmake @@ -1,5 +1,6 @@ set(PLATFORM_REQUIRED_DEPS LibAndroidJNI OpenGLES EGL LibZip) set(APP_RENDER_SYSTEM gles) +list(APPEND PLATFORM_OPTIONAL_DEPS LibDovi) # Store SDK compile version set(TARGET_SDK 33) From cfcb8a2798fa3d84c765f0683121974d0113f584 Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Sun, 16 Jul 2023 09:55:06 -0400 Subject: [PATCH 141/811] [Android] Add Dolby Vision compatibility mode setting For Dolby Vision profile 7 video files, the metadata is converted automatically to improve compatibility on certain Android devices --- .../resources/strings.po | 12 +++ system/settings/settings.xml | 11 +++ .../Video/DVDVideoCodecAndroidMediaCodec.cpp | 15 ++++ xbmc/settings/SettingConditions.cpp | 9 +++ xbmc/settings/Settings.h | 1 + xbmc/utils/BitstreamConverter.cpp | 76 ++++++++++++++++++- xbmc/utils/BitstreamConverter.h | 2 + 7 files changed, 125 insertions(+), 1 deletion(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 8c2dcfa3a1a68..80e144370b419 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -23481,3 +23481,15 @@ msgstr "" msgctxt "#39195" msgid "Enable this option for the best picture quality. Disabling reduces the load on resource-limited systems." msgstr "" + +#. Title of Dolby Vision RPU conversion setting +#: system/settings/settings.xml +msgctxt "#39196" +msgid "Dolby Vision compatibility mode" +msgstr "" + +#. Help text for setting "Convert Dolby Vision for compatibility" of label #39196 +#: system/settings/settings.xml +msgctxt "#39197" +msgid "If enabled, Dolby Vision profile 7 will be converted to profile 8.1, which is more commonly supported by devices. Enable if your device supports Dolby Vision, but has issues with some videos." +msgstr "" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index 60539c9b0d94a..d9218c470dc03 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -179,6 +179,17 @@ true + + HAS_MEDIACODEC + + + + + + 2 + false + + diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp index 31bf35bfe5376..68203861fc760 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecAndroidMediaCodec.cpp @@ -560,7 +560,22 @@ bool CDVDVideoCodecAndroidMediaCodec::Open(CDVDStreamInfo &hints, CDVDCodecOptio { m_bitstream.reset(); } + + // Only set for profile 7, container hint allows to skip parsing unnecessarily + if (m_bitstream && m_hints.dovi.dv_profile == 7) + { + bool convertDovi = CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_VIDEOPLAYER_CONVERTDOVI); + + CLog::Log(LOGDEBUG, + "CDVDVideoCodecAndroidMediaCodec::Open Dolby Vision compatibility mode " + "enabled: {}", + convertDovi); + + m_bitstream->SetConvertDovi(convertDovi); + } } + break; } case AV_CODEC_ID_WMV3: diff --git a/xbmc/settings/SettingConditions.cpp b/xbmc/settings/SettingConditions.cpp index d9710c0e0cbed..45997a36a41ef 100644 --- a/xbmc/settings/SettingConditions.cpp +++ b/xbmc/settings/SettingConditions.cpp @@ -118,6 +118,14 @@ bool SupportsVideoSuperResolution(const std::string& condition, return CServiceBroker::GetWinSystem()->SupportsVideoSuperResolution(); } +bool SupportsDolbyVision(const std::string& condition, + const std::string& value, + const SettingConstPtr& setting, + void* data) +{ + return CServiceBroker::GetWinSystem()->GetDisplayHDRCapabilities().SupportsDolbyVision(); +} + bool SupportsScreenMove(const std::string& condition, const std::string& value, const SettingConstPtr& setting, @@ -470,6 +478,7 @@ void CSettingConditions::Initialize() m_complexConditions.emplace("hassystemsdrpeakluminance", HasSystemSdrPeakLuminance); m_complexConditions.emplace("supportsscreenmove", SupportsScreenMove); m_complexConditions.emplace("supportsvideosuperresolution", SupportsVideoSuperResolution); + m_complexConditions.emplace("supportsdolbyvision", SupportsDolbyVision); m_complexConditions.emplace("ishdrdisplay", IsHDRDisplay); m_complexConditions.emplace("ismasteruser", IsMasterUser); m_complexConditions.emplace("hassubtitlesfontextensions", HasSubtitlesFontExtensions); diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 23206f3439385..349a153065852 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -129,6 +129,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_VIDEOPLAYER_USESTAGEFRIGHT = "videoplayer.usestagefright"; static constexpr auto SETTING_VIDEOPLAYER_LIMITGUIUPDATE = "videoplayer.limitguiupdate"; static constexpr auto SETTING_VIDEOPLAYER_SUPPORTMVC = "videoplayer.supportmvc"; + static constexpr auto SETTING_VIDEOPLAYER_CONVERTDOVI = "videoplayer.convertdovi"; static constexpr auto SETTING_MYVIDEOS_SELECTACTION = "myvideos.selectaction"; static constexpr auto SETTING_MYVIDEOS_USETAGS = "myvideos.usetags"; static constexpr auto SETTING_MYVIDEOS_EXTRACTFLAGS = "myvideos.extractflags"; diff --git a/xbmc/utils/BitstreamConverter.cpp b/xbmc/utils/BitstreamConverter.cpp index 1bca7d14d8400..5fb96d4ed5287 100644 --- a/xbmc/utils/BitstreamConverter.cpp +++ b/xbmc/utils/BitstreamConverter.cpp @@ -20,6 +20,13 @@ #include +extern "C" +{ +#ifdef HAVE_LIBDOVI +#include +#endif +} + enum { AVC_NAL_SLICE=1, AVC_NAL_DPA, @@ -257,6 +264,32 @@ static bool has_sei_recovery_point(const uint8_t *p, const uint8_t *end) return false; } +#ifdef HAVE_LIBDOVI +// The returned data must be freed with `dovi_data_free` +// May be NULL if no conversion was done +static const DoviData* convert_dovi_rpu_nal(uint8_t* buf, uint32_t nal_size) +{ + DoviRpuOpaque* rpu = dovi_parse_unspec62_nalu(buf, nal_size); + const DoviRpuDataHeader* header = dovi_rpu_get_header(rpu); + const DoviData* rpu_data = NULL; + + if (header && header->guessed_profile == 7) + { + int ret = dovi_convert_rpu_with_mode(rpu, 2); + if (ret < 0) + goto done; + + rpu_data = dovi_write_unspec62_nalu(rpu); + } + +done: + dovi_rpu_free_header(header); + dovi_rpu_free(rpu); + + return rpu_data; +} +#endif + //////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////// CBitstreamParser::CBitstreamParser() = default; @@ -322,6 +355,7 @@ CBitstreamConverter::CBitstreamConverter() m_convert_bytestream = false; m_sps_pps_context.sps_pps_data = NULL; m_start_decode = true; + m_convert_dovi = false; } CBitstreamConverter::~CBitstreamConverter() @@ -875,6 +909,10 @@ bool CBitstreamConverter::BitstreamConvert(uint8_t* pData, int iSize, uint8_t ** uint32_t cumul_size = 0; const uint8_t *buf_end = buf + buf_size; +#ifdef HAVE_LIBDOVI + const DoviData* rpu_data = NULL; +#endif + switch (m_codec) { case AV_CODEC_ID_H264: @@ -928,12 +966,48 @@ bool CBitstreamConverter::BitstreamConvert(uint8_t* pData, int iSize, uint8_t ** } else { - BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf, nal_size, unit_type); + bool write_buf = true; + const uint8_t* buf_to_write = buf; + int32_t final_nal_size = nal_size; + if (!m_sps_pps_context.first_idr && IsSlice(unit_type)) { m_sps_pps_context.first_idr = 1; m_sps_pps_context.idr_sps_pps_seen = 0; } + + if (m_convert_dovi) + { + if (unit_type == HEVC_NAL_UNSPEC62) + { +#ifdef HAVE_LIBDOVI + // Convert the RPU itself + rpu_data = convert_dovi_rpu_nal(buf, nal_size); + if (rpu_data) + { + buf_to_write = rpu_data->data; + final_nal_size = rpu_data->len; + } +#endif + } + else if (unit_type == HEVC_NAL_UNSPEC63) + { + // Ignore the enhancement layer, may or may not help + write_buf = false; + } + } + + if (write_buf) + BitstreamAllocAndCopy(poutbuf, poutbuf_size, NULL, 0, buf_to_write, final_nal_size, + unit_type); + +#ifdef HAVE_LIBDOVI + if (rpu_data) + { + dovi_data_free(rpu_data); + rpu_data = NULL; + } +#endif } buf += nal_size; diff --git a/xbmc/utils/BitstreamConverter.h b/xbmc/utils/BitstreamConverter.h index be63b7590cfad..1920212a2a79a 100644 --- a/xbmc/utils/BitstreamConverter.h +++ b/xbmc/utils/BitstreamConverter.h @@ -99,6 +99,7 @@ class CBitstreamConverter int GetExtraSize() const; void ResetStartDecode(void); bool CanStartDecode() const; + void SetConvertDovi(bool value) { m_convert_dovi = value; } static bool mpeg2_sequence_header(const uint8_t *data, const uint32_t size, mpeg2_sequence *sequence); @@ -143,4 +144,5 @@ class CBitstreamConverter bool m_convert_bytestream; AVCodecID m_codec; bool m_start_decode; + bool m_convert_dovi; }; From 3b39bd50d89eea6650e66e62555c34b94c0facac Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 3 Sep 2023 15:02:47 +1000 Subject: [PATCH 142/811] [cmake][buildtools] use flatbuffers::flatc target for executable --- cmake/modules/FindFlatBuffers.cmake | 19 ++++----- cmake/modules/buildtools/FindFlatC.cmake | 42 +++++++------------ .../cores/RetroPlayer/messages/CMakeLists.txt | 6 ++- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/cmake/modules/FindFlatBuffers.cmake b/cmake/modules/FindFlatBuffers.cmake index 4c0a28b63581e..0553c976ce5e7 100644 --- a/cmake/modules/FindFlatBuffers.cmake +++ b/cmake/modules/FindFlatBuffers.cmake @@ -1,12 +1,10 @@ # FindFlatBuffers # -------- -# Find the FlatBuffers schema compiler and headers +# Find the FlatBuffers schema headers # -# This will define the following variables: +# This will define the following target: # -# FLATBUFFERS_FOUND - system has FlatBuffers compiler and headers -# FLATBUFFERS_INCLUDE_DIRS - the FlatFuffers include directory -# FLATBUFFERS_MESSAGES_INCLUDE_DIR - the directory for generated headers +# flatbuffers::flatbuffers - The flatbuffers headers find_package(FlatC REQUIRED) @@ -15,7 +13,9 @@ if(NOT TARGET flatbuffers::flatbuffers) include(cmake/scripts/common/ModuleHelpers.cmake) set(MODULE_LC flatbuffers) - + # Duplicate URL may exist from FindFlatC.cmake + # unset otherwise it thinks we are providing a local file location and incorrect concatenation happens + unset(FLATBUFFERS_URL) SETUP_BUILD_VARS() # Override build type detection and always build as release @@ -42,12 +42,10 @@ if(NOT TARGET flatbuffers::flatbuffers) REQUIRED_VARS FLATBUFFERS_INCLUDE_DIR VERSION_VAR FLATBUFFERS_VER) - set(FLATBUFFERS_MESSAGES_INCLUDE_DIR ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/cores/RetroPlayer/messages CACHE INTERNAL "Generated FlatBuffer headers") - add_library(flatbuffers::flatbuffers INTERFACE IMPORTED) set_target_properties(flatbuffers::flatbuffers PROPERTIES FOLDER "External Projects" - INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR};${FLATBUFFERS_MESSAGES_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}") add_dependencies(flatbuffers::flatbuffers flatbuffers::flatc) @@ -55,7 +53,4 @@ if(NOT TARGET flatbuffers::flatbuffers) add_dependencies(flatbuffers::flatbuffers flatbuffers) endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP flatbuffers::flatbuffers) - endif() - -mark_as_advanced(FLATBUFFERS_INCLUDE_DIR) diff --git a/cmake/modules/buildtools/FindFlatC.cmake b/cmake/modules/buildtools/FindFlatC.cmake index f28477e7ff866..121bcdbdfe9e0 100644 --- a/cmake/modules/buildtools/FindFlatC.cmake +++ b/cmake/modules/buildtools/FindFlatC.cmake @@ -2,13 +2,7 @@ # -------- # Find the FlatBuffers schema compiler # -# This will define the following variables: -# -# FLATBUFFERS_FLATC_EXECUTABLE_FOUND - system has FlatBuffers compiler -# FLATBUFFERS_FLATC_EXECUTABLE - the flatc compiler executable -# FLATBUFFERS_FLATC_VERSION - the flatc compiler version -# -# and the following imported targets: +# This will define the following target: # # flatbuffers::flatc - The FlatC compiler @@ -17,7 +11,8 @@ if(NOT TARGET flatbuffers::flatc) # Check for existing FLATC. find_program(FLATBUFFERS_FLATC_EXECUTABLE NAMES flatc - HINTS ${NATIVEPREFIX}/bin) + HINTS ${NATIVEPREFIX}/bin + NO_CACHE) if(FLATBUFFERS_FLATC_EXECUTABLE) execute_process(COMMAND "${FLATBUFFERS_FLATC_EXECUTABLE}" --version @@ -34,7 +29,8 @@ if(NOT TARGET flatbuffers::flatc) unset(FLATBUFFERS_URL) SETUP_BUILD_VARS() - if(NOT FLATBUFFERS_FLATC_EXECUTABLE OR (ENABLE_INTERNAL_FLATBUFFERS AND NOT "${FLATBUFFERS_FLATC_VERSION}" VERSION_EQUAL "${FLATBUFFERS_VER}")) + if(NOT FLATBUFFERS_FLATC_EXECUTABLE OR + (ENABLE_INTERNAL_FLATBUFFERS AND NOT "${FLATBUFFERS_FLATC_VERSION}" VERSION_EQUAL "${FLATBUFFERS_VER}")) # Override build type detection and always build as release set(FLATBUFFERS_BUILD_TYPE Release) @@ -67,7 +63,7 @@ if(NOT TARGET flatbuffers::flatc) set(WIN_DISABLE_PROJECT_FLAGS 1) endif() - set(FLATBUFFERS_FLATC_EXECUTABLE ${INSTALL_DIR}/flatc CACHE INTERNAL "FlatBuffer compiler") + set(FLATBUFFERS_FLATC_EXECUTABLE ${INSTALL_DIR}/flatc) set(BUILD_NAME flatc) set(BUILD_BYPRODUCTS ${FLATBUFFERS_FLATC_EXECUTABLE}) @@ -76,25 +72,15 @@ if(NOT TARGET flatbuffers::flatc) BUILD_DEP_TARGET() endif() - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(FlatC - REQUIRED_VARS FLATBUFFERS_FLATC_EXECUTABLE - VERSION_VAR FLATBUFFERS_FLATC_VERSION) - - if(FLATC_FOUND) + include(FindPackageMessage) + find_package_message(FlatC "Found FlatC Compiler: ${FLATBUFFERS_FLATC_EXECUTABLE} (found version \"${FLATBUFFERS_FLATC_VERSION}\")" "[${FLATBUFFERS_FLATC_EXECUTABLE}][${FLATBUFFERS_FLATC_VERSION}]") - add_library(flatbuffers::flatc UNKNOWN IMPORTED) - set_target_properties(flatbuffers::flatc PROPERTIES - FOLDER "External Projects") + add_executable(flatbuffers::flatc IMPORTED) + set_target_properties(flatbuffers::flatc PROPERTIES + IMPORTED_LOCATION "${FLATBUFFERS_FLATC_EXECUTABLE}" + FOLDER "External Projects") - if(TARGET flatc) - add_dependencies(flatbuffers::flatc flatc) - endif() - else() - if(FLATC_FIND_REQUIRED) - message(FATAL_ERROR "Flatc compiler not found.") - endif() + if(TARGET flatc) + add_dependencies(flatbuffers::flatc flatc) endif() - - mark_as_advanced(FLATBUFFERS_FLATC_EXECUTABLE) endif() diff --git a/xbmc/cores/RetroPlayer/messages/CMakeLists.txt b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt index e2e5fa14af97e..41eb3d6e2fb86 100644 --- a/xbmc/cores/RetroPlayer/messages/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/messages/CMakeLists.txt @@ -8,8 +8,8 @@ foreach(_file ${MESSAGES}) list(APPEND FLATC_OUTPUTS ${FLATC_OUTPUT}) add_custom_command(OUTPUT ${FLATC_OUTPUT} - COMMAND ${FLATBUFFERS_FLATC_EXECUTABLE} - ARGS -c -o "${FLATBUFFERS_MESSAGES_INCLUDE_DIR}/" ${_file} + COMMAND flatbuffers::flatc + ARGS -c -o "${CMAKE_CURRENT_BINARY_DIR}/" ${_file} DEPENDS ${_file} COMMENT "Building C++ header for ${_file}" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) @@ -21,5 +21,7 @@ set_target_properties(retroplayer_messages PROPERTIES FOLDER "Generated Messages SOURCES "${FLATC_OUTPUTS}") if(TARGET flatbuffers::flatbuffers) + set_property(TARGET flatbuffers::flatbuffers APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${CMAKE_CURRENT_BINARY_DIR}") add_dependencies(retroplayer_messages flatbuffers::flatbuffers) endif() From 9b6709e0e694e5651e1028d0b49b103e093a51c8 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 3 Sep 2023 17:47:52 +0200 Subject: [PATCH 143/811] [fileitem] Add support for dynpath to CFileItem::GetLocalArtBaseFilename. --- xbmc/FileItem.cpp | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 77ad297a7a36a..2d033eea87323 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3382,20 +3382,22 @@ std::string CFileItem::GetLocalArtBaseFilename() const std::string CFileItem::GetLocalArtBaseFilename(bool& useFolder) const { - std::string strFile = m_strPath; + std::string strFile; if (IsStack()) { std::string strPath; URIUtils::GetParentPath(m_strPath,strPath); - strFile = URIUtils::AddFileToFolder(strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(strFile))); + strFile = URIUtils::AddFileToFolder( + strPath, URIUtils::GetFileName(CStackDirectory::GetStackedTitlePath(m_strPath))); } - if (URIUtils::IsInRAR(strFile) || URIUtils::IsInZIP(strFile)) + std::string file = strFile.empty() ? m_strPath : strFile; + if (URIUtils::IsInRAR(file) || URIUtils::IsInZIP(file)) { - std::string strPath = URIUtils::GetDirectory(strFile); + std::string strPath = URIUtils::GetDirectory(file); std::string strParent; URIUtils::GetParentPath(strPath,strParent); - strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(strFile)); + strFile = URIUtils::AddFileToFolder(strParent, URIUtils::GetFileName(file)); } if (IsMultiPath()) @@ -3407,7 +3409,13 @@ std::string CFileItem::GetLocalArtBaseFilename(bool& useFolder) const strFile = GetLocalMetadataPath(); } else if (useFolder && !(m_bIsFolder && !IsFileFolder())) - strFile = URIUtils::GetDirectory(strFile); + { + file = strFile.empty() ? m_strPath : strFile; + strFile = URIUtils::GetDirectory(file); + } + + if (strFile.empty()) + strFile = GetDynPath(); return strFile; } From b06f51f3b962f9254a8fcc880d460cf2084c9603 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 3 Sep 2023 13:24:35 +0200 Subject: [PATCH 144/811] [video] Fix 'Local art' missing in art selection dialog. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 2d662994d4a39..f33515a2bc261 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1996,6 +1996,8 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr thumbs; if (type != MediaTypeArtist) @@ -2056,8 +2058,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt("icon", "DefaultActor.png"); } - - if (type == MediaTypeVideoCollection) + else if (type == MediaTypeVideoCollection) { std::string localFile = FindLocalMovieSetArtworkFile(item, artType); if (!localFile.empty()) @@ -2071,6 +2072,20 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt("icon", "DefaultVideo.png"); } + else + { + localThumb = CVideoThumbLoader::GetLocalArt(*item, artType); + if (!localThumb.empty()) + { + const auto localitem = std::make_shared("thumb://Local", false); + localitem->SetArt("thumb", localThumb); + localitem->SetArt("icon", "DefaultPicture.png"); + localitem->SetLabel(g_localizeStrings.Get(13514)); + items.Add(localitem); + } + else + noneitem->SetArt("icon", "DefaultPicture.png"); + } } else { @@ -2126,6 +2141,9 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr Date: Fri, 1 Sep 2023 21:55:13 -0400 Subject: [PATCH 145/811] [uPnP] Fix playback from authenticated sources Credentials must be added to the URL for sources that require authentication. --- xbmc/filesystem/NptXbmcFile.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/xbmc/filesystem/NptXbmcFile.cpp b/xbmc/filesystem/NptXbmcFile.cpp index 90252ba0b4a72..aba25873dcce9 100644 --- a/xbmc/filesystem/NptXbmcFile.cpp +++ b/xbmc/filesystem/NptXbmcFile.cpp @@ -13,7 +13,9 @@ +---------------------------------------------------------------------*/ #include "File.h" #include "FileFactory.h" +#include "PasswordManager.h" #include "URL.h" +#include "utils/URIUtils.h" #include @@ -294,16 +296,17 @@ NPT_XbmcFile::Open(NPT_File::OpenMode mode) } bool result; - CURL* url = new CURL(name); + CURL url(URIUtils::SubstitutePath(name)); + + if (CPasswordManager::GetInstance().IsURLSupported(url) && url.GetUserName().empty()) + CPasswordManager::GetInstance().AuthenticateURL(url); // compute mode - if (mode & NPT_FILE_OPEN_MODE_WRITE) { - result = file->OpenForWrite(*url, (mode & NPT_FILE_OPEN_MODE_TRUNCATE)?true:false); - } else { - result = file->Open(*url); - } + if (mode & NPT_FILE_OPEN_MODE_WRITE) + result = file->OpenForWrite(url, (mode & NPT_FILE_OPEN_MODE_TRUNCATE) ? true : false); + else + result = file->Open(url); - delete url; if (!result) return NPT_ERROR_NO_SUCH_FILE; } From 94f83bf0912498fca957c6859706485190e15c8f Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Mon, 28 Aug 2023 17:35:09 +0200 Subject: [PATCH 146/811] [webOS] Allow relaunches Register to lifecycle events. On relaunch the window needs to be brought back to fullscreen through webos shell --- tools/webOS/packaging/appinfo.json.in | 4 +- xbmc/windowing/wayland/WinSystemWayland.h | 1 + .../wayland/WinSystemWaylandWebOS.cpp | 37 +++++++++++++++++++ .../windowing/wayland/WinSystemWaylandWebOS.h | 7 ++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tools/webOS/packaging/appinfo.json.in b/tools/webOS/packaging/appinfo.json.in index 2efda3b47447e..300a52be6b814 100644 --- a/tools/webOS/packaging/appinfo.json.in +++ b/tools/webOS/packaging/appinfo.json.in @@ -8,5 +8,7 @@ "icon": "icon.png", "largeIcon": "largeIcon.png", "splashBackground": "media/splash_webOS.png", - "spinnerOnLaunch": false + "spinnerOnLaunch": false, + "handlesRelaunch": true, + "nativeLifeCycleInterfaceVersion": 2 } diff --git a/xbmc/windowing/wayland/WinSystemWayland.h b/xbmc/windowing/wayland/WinSystemWayland.h index b50c4c820ef38..62720c5e188ff 100644 --- a/xbmc/windowing/wayland/WinSystemWayland.h +++ b/xbmc/windowing/wayland/WinSystemWayland.h @@ -111,6 +111,7 @@ class CWinSystemWayland : public CWinSystemBase, { return m_surface; } + IShellSurface* GetShellSurface() { return m_shellSurface.get(); } void PrepareFramePresentation(); void FinishFramePresentation(); diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp index f162feba734f6..ec1567b2a2518 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp @@ -15,10 +15,16 @@ #include "cores/AudioEngine/Sinks/AESinkStarfish.h" #include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h" #include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h" +#include "utils/JSONVariantParser.h" #include "utils/log.h" #include +namespace +{ +constexpr const char* LUNA_REGISTER_APP = "luna://com.webos.service.applicationmanager/registerApp"; +} // namespace + namespace KODI::WINDOWING::WAYLAND { @@ -37,6 +43,16 @@ bool CWinSystemWaylandWebOS::InitWindowSystem() m_webosRegistry->RequestSingleton(m_webosForeign, 1, 2, false); m_webosRegistry->Bind(); + m_requestContext->pub = true; + m_requestContext->multiple = true; + m_requestContext->callback = &OnAppLifecycleEventWrapper; + m_requestContext->userdata = this; + if (HLunaServiceCall(LUNA_REGISTER_APP, "{}", m_requestContext.get())) + { + CLog::LogF(LOGWARNING, "Luna request call failed"); + m_requestContext = nullptr; + } + return true; } @@ -122,4 +138,25 @@ std::unique_ptr CWinSystemWaylandWebOS::GetOSSc return std::make_unique(); } +bool CWinSystemWaylandWebOS::OnAppLifecycleEventWrapper(LSHandle* sh, LSMessage* reply, void* ctx) +{ + HContext* context = static_cast(ctx); + return static_cast(context->userdata)->OnAppLifecycleEvent(sh, reply); +} + +bool CWinSystemWaylandWebOS::OnAppLifecycleEvent(LSHandle* sh, LSMessage* reply) +{ + const char* msg = HLunaServiceMessage(reply); + CLog::Log(LOGDEBUG, "Got lifecycle event: {}", msg); + + CVariant event; + CJSONVariantParser::Parse(msg, event); + + IShellSurface* shellSurface = GetShellSurface(); + if (event["event"] == "relaunch" && shellSurface) + shellSurface->SetFullScreen(nullptr, 60.0f); + + return true; +} + } // namespace KODI::WINDOWING::WAYLAND diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h index 5732b01676be5..5584d5eb32790 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h @@ -12,6 +12,7 @@ #include "WinSystemWayland.h" #include +#include namespace KODI::WINDOWING::WAYLAND { @@ -48,6 +49,9 @@ class CWinSystemWaylandWebOS : public CWinSystemWayland std::unique_ptr GetOSScreenSaverImpl() override; private: + static bool OnAppLifecycleEventWrapper(LSHandle* sh, LSMessage* reply, void* ctx); + bool OnAppLifecycleEvent(LSHandle* sh, LSMessage* reply); + std::unique_ptr m_webosRegistry; // WebOS foreign surface @@ -55,6 +59,9 @@ class CWinSystemWaylandWebOS : public CWinSystemWayland wayland::compositor_t m_compositor; wayland::webos_exported_t m_exportedSurface; wayland::webos_foreign_t m_webosForeign; + + std::unique_ptr m_requestContext{new HContext(), + HUnregisterServiceCallback}; }; } // namespace KODI::WINDOWING::WAYLAND From e03e80e9726bcae71fe5b63e9f1b8c81213d5e3a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 3 Sep 2023 17:10:24 +1000 Subject: [PATCH 147/811] [cmake] FindFmt correct comment for fmt::fmt target --- cmake/modules/FindFmt.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index 4c50291efb9e5..223445c595b70 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -143,7 +143,7 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR VERSION_VAR FMT_VERSION) - # Check whether we already have tinyxml2::tinyxml2 target added to dep property list + # Check whether we already have fmt::fmt target added to dep property list get_property(CHECK_INTERNAL_DEPS GLOBAL PROPERTY INTERNAL_DEPS_PROP) list(FIND CHECK_INTERNAL_DEPS "fmt::fmt" FMT_PROP_FOUND) From 423d9061c3e46b04044423977ab4b642799f8d48 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 3 Sep 2023 17:11:23 +1000 Subject: [PATCH 148/811] [cmake] FindNFS correct comment for libnfs --- cmake/modules/FindNFS.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index b2233a170d1b8..8608571d62e51 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -18,8 +18,8 @@ if(NOT TARGET libnfs::nfs) # Search for cmake config. Suitable for all platforms including windows find_package(libnfs CONFIG QUIET) - # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build - # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway + # Check for existing LIBNFS. If version >= LIBNFS-VERSION file version, dont build + # A corner case, but if a linux/freebsd user WANTS to build internal libnfs, build anyway if((libnfs_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_NFS) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_NFS)) From 51c1ba803f1d8a661bb8b4739ad14dcda466071a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 3 Sep 2023 16:44:44 +1000 Subject: [PATCH 149/811] [cmake] remove FindLibXML2 and rely on cmake provided Find module (cmake 3.12+ 2018) --- cmake/modules/FindLibXml2.cmake | 84 --------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 cmake/modules/FindLibXml2.cmake diff --git a/cmake/modules/FindLibXml2.cmake b/cmake/modules/FindLibXml2.cmake deleted file mode 100644 index d6e389af78e6b..0000000000000 --- a/cmake/modules/FindLibXml2.cmake +++ /dev/null @@ -1,84 +0,0 @@ -#.rst: -# FindLibXml2 -# ----------- -# -# Try to find the LibXml2 xml processing library -# -# Once done this will define -# -# :: -# -# LIBXML2_FOUND - System has LibXml2 -# LIBXML2_INCLUDE_DIR - The LibXml2 include directory -# LIBXML2_LIBRARIES - The libraries needed to use LibXml2 -# LIBXML2_DEFINITIONS - Compiler switches required for using LibXml2 -# LIBXML2_XMLLINT_EXECUTABLE - The XML checking tool xmllint coming with LibXml2 -# LIBXML2_VERSION_STRING - the version of LibXml2 found (since CMake 2.8.8) - -#============================================================================= -# Copyright 2006-2009 Kitware, Inc. -# Copyright 2006 Alexander Neundorf -# Copyright 2016 Team Kodi -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# use pkg-config to get the directories and then use these values -# in the find_path() and find_library() calls -find_package(PkgConfig QUIET) -PKG_CHECK_MODULES(PC_LIBXML QUIET libxml-2.0) -set(LIBXML2_DEFINITIONS ${PC_LIBXML_CFLAGS_OTHER}) - -find_path(LIBXML2_INCLUDE_DIR NAMES libxml/xpath.h - HINTS - ${PC_LIBXML_INCLUDEDIR} - ${PC_LIBXML_INCLUDE_DIRS} - PATH_SUFFIXES libxml2 - ) - -find_library(LIBXML2_LIBRARY NAMES xml2 libxml2 - HINTS - ${PC_LIBXML_LIBDIR} - ${PC_LIBXML_LIBRARY_DIRS} - ) - -find_program(LIBXML2_XMLLINT_EXECUTABLE xmllint) -# for backwards compat. with KDE 4.0.x: -set(XMLLINT_EXECUTABLE "${LIBXML2_XMLLINT_EXECUTABLE}") - -# Make sure to use static flags if appropriate -if(PC_LIBXML_FOUND) - if(${LIBXML2_LIBRARY} MATCHES ".+\.a$" AND PC_LIBXML_STATIC_LDFLAGS) - set(LIBXML2_LIBRARY ${LIBXML2_LIBRARY} ${PC_LIBXML_STATIC_LDFLAGS}) - endif() -endif() - -if(PC_LIBXML_VERSION) - set(LIBXML2_VERSION_STRING ${PC_LIBXML_VERSION}) -elseif(LIBXML2_INCLUDE_DIR AND EXISTS "${LIBXML2_INCLUDE_DIR}/libxml/xmlversion.h") - file(STRINGS "${LIBXML2_INCLUDE_DIR}/libxml/xmlversion.h" libxml2_version_str - REGEX "^#define[\t ]+LIBXML_DOTTED_VERSION[\t ]+\".*\"") - string(REGEX REPLACE "^#define[\t ]+LIBXML_DOTTED_VERSION[\t ]+\"([^\"]*)\".*" "\\1" - LIBXML2_VERSION_STRING "${libxml2_version_str}") - unset(libxml2_version_str) -endif() - - -include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(LibXml2 - REQUIRED_VARS LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR - VERSION_VAR LIBXML2_VERSION_STRING) - -if(LibXml2_FOUND) - set(LIBXML2_LIBRARIES ${LIBXML2_LIBRARY}) - set(LIBXML2_INCLUDE_DIRS ${LIBXML2_INCLUDE_DIR}) -endif() - -mark_as_advanced(LIBXML2_INCLUDE_DIRS LIBXML2_LIBRARIES LIBXML2_XMLLINT_EXECUTABLE) From 406cbecc6f68d8ba4f5f716206785b7685776de2 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:35:21 +0200 Subject: [PATCH 150/811] [video] Fix no information dialog for music videos. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index a1643d10b1d8c..2a2a482bd203d 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -239,7 +239,8 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) if (!scraper && !(fileItem.IsPlugin() || fileItem.IsScript()) && !(m_database.HasMovieInfo(fileItem.GetDynPath()) || m_database.HasTvShowInfo(strDir) || - m_database.HasEpisodeInfo(fileItem.GetDynPath()))) + m_database.HasEpisodeInfo(fileItem.GetDynPath()) || + m_database.HasMusicVideoInfo(fileItem.GetDynPath()))) { HELPERS::ShowOKDialogText(CVariant{20176}, // Show video information CVariant{19055}); // no information available @@ -396,7 +397,7 @@ bool CGUIWindowVideoBase::ShowInfo(const CFileItemPtr& item2, const ScraperPtr& } if (info->Content() == CONTENT_MUSICVIDEOS) { - bHasInfo = m_database.GetMusicVideoInfo(item->GetPath(), movieDetails); + bHasInfo = m_database.GetMusicVideoInfo(item->GetDynPath(), movieDetails); } m_database.Close(); } From 5e6ae831e04d4b3211b1298f43b52e2af80362a6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 14:58:36 +1000 Subject: [PATCH 151/811] [cmake] FindDBus move to full TARGET usage --- cmake/modules/FindDBus.cmake | 58 ++++++++----------- xbmc/platform/freebsd/CMakeLists.txt | 2 +- xbmc/platform/linux/CMakeLists.txt | 2 +- .../linux/powermanagement/CMakeLists.txt | 2 +- xbmc/platform/linux/storage/CMakeLists.txt | 2 +- xbmc/windowing/linux/CMakeLists.txt | 2 +- 6 files changed, 30 insertions(+), 38 deletions(-) diff --git a/cmake/modules/FindDBus.cmake b/cmake/modules/FindDBus.cmake index df3edc518068f..cec6a6af8dfd9 100644 --- a/cmake/modules/FindDBus.cmake +++ b/cmake/modules/FindDBus.cmake @@ -3,50 +3,42 @@ # ------- # Finds the DBUS library # -# This will define the following variables:: -# -# DBUS_FOUND - system has DBUS -# DBUS_INCLUDE_DIRS - the DBUS include directory -# DBUS_LIBRARIES - the DBUS libraries -# DBUS_DEFINITIONS - the DBUS definitions -# -# and the following imported targets:: +# This will define the following target: # # DBus::DBus - The DBUS library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_DBUS dbus-1 QUIET) -endif() +if(NOT TARGET DBus::DBus) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_DBUS dbus-1 QUIET) + endif() -find_path(DBUS_INCLUDE_DIR NAMES dbus/dbus.h - PATH_SUFFIXES dbus-1.0 - PATHS ${PC_DBUS_INCLUDE_DIR}) -find_path(DBUS_ARCH_INCLUDE_DIR NAMES dbus/dbus-arch-deps.h - PATH_SUFFIXES dbus-1.0/include - PATHS ${PC_DBUS_LIBDIR} - /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}) -find_library(DBUS_LIBRARY NAMES dbus-1 - PATHS ${PC_DBUS_LIBDIR}) + find_path(DBUS_INCLUDE_DIR NAMES dbus/dbus.h + PATH_SUFFIXES dbus-1.0 + PATHS ${PC_DBUS_INCLUDE_DIR}) + find_path(DBUS_ARCH_INCLUDE_DIR NAMES dbus/dbus-arch-deps.h + PATH_SUFFIXES dbus-1.0/include + PATHS ${PC_DBUS_LIBDIR} + /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}) + find_library(DBUS_LIBRARY NAMES dbus-1 + PATHS ${PC_DBUS_LIBDIR}) -set(DBUS_VERSION ${PC_DBUS_VERSION}) + set(DBUS_VERSION ${PC_DBUS_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(DBus - REQUIRED_VARS DBUS_LIBRARY DBUS_INCLUDE_DIR DBUS_ARCH_INCLUDE_DIR - VERSION_VAR DBUS_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(DBus + REQUIRED_VARS DBUS_LIBRARY DBUS_INCLUDE_DIR DBUS_ARCH_INCLUDE_DIR + VERSION_VAR DBUS_VERSION) -if(DBUS_FOUND) - set(DBUS_LIBRARIES ${DBUS_LIBRARY}) - set(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) - set(DBUS_DEFINITIONS -DHAS_DBUS=1) + if(DBUS_FOUND) - if(NOT TARGET DBus::DBus) add_library(DBus::DBus UNKNOWN IMPORTED) set_target_properties(DBus::DBus PROPERTIES IMPORTED_LOCATION "${DBUS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${DBUS_INCLUDE_DIR}" + INTERFACE_INCLUDE_DIRECTORIES "${DBUS_INCLUDE_DIR};${DBUS_ARCH_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_DBUS=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP DBus::DBus) endif() -endif() -mark_as_advanced(DBUS_INCLUDE_DIR DBUS_LIBRARY) + mark_as_advanced(DBUS_INCLUDE_DIR DBUS_LIBRARY) +endif() diff --git a/xbmc/platform/freebsd/CMakeLists.txt b/xbmc/platform/freebsd/CMakeLists.txt index 9b8782f4cdad4..1c045d2ab708e 100644 --- a/xbmc/platform/freebsd/CMakeLists.txt +++ b/xbmc/platform/freebsd/CMakeLists.txt @@ -18,7 +18,7 @@ if(ALSA_FOUND) list(APPEND HEADERS ../linux/FDEventMonitor.h) endif() -if(DBUS_FOUND) +if(TARGET DBus::DBus) list(APPEND SOURCES ../linux/DBusMessage.cpp ../linux/DBusUtil.cpp) list(APPEND HEADERS ../linux/DBusMessage.h diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt index 54a3b25d28ff7..7292690c04ea8 100644 --- a/xbmc/platform/linux/CMakeLists.txt +++ b/xbmc/platform/linux/CMakeLists.txt @@ -23,7 +23,7 @@ if(ALSA_FOUND) list(APPEND HEADERS FDEventMonitor.h) endif() -if(DBUS_FOUND) +if(TARGET DBus::DBus) list(APPEND SOURCES DBusMessage.cpp DBusUtil.cpp) list(APPEND HEADERS DBusMessage.h diff --git a/xbmc/platform/linux/powermanagement/CMakeLists.txt b/xbmc/platform/linux/powermanagement/CMakeLists.txt index 3694a7967e509..6870f83928fdf 100644 --- a/xbmc/platform/linux/powermanagement/CMakeLists.txt +++ b/xbmc/platform/linux/powermanagement/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES LinuxPowerSyscall.cpp) set(HEADERS FallbackPowerSyscall.h LinuxPowerSyscall.h) -if(DBUS_FOUND) +if(TARGET DBus::DBus) list(APPEND SOURCES ConsoleUPowerSyscall.cpp LogindUPowerSyscall.cpp UPowerSyscall.cpp) diff --git a/xbmc/platform/linux/storage/CMakeLists.txt b/xbmc/platform/linux/storage/CMakeLists.txt index 223655aef6c4a..322ee5cffd804 100644 --- a/xbmc/platform/linux/storage/CMakeLists.txt +++ b/xbmc/platform/linux/storage/CMakeLists.txt @@ -2,7 +2,7 @@ set(SOURCES LinuxStorageProvider.cpp) set(HEADERS LinuxStorageProvider.h) -if(DBUS_FOUND) +if(TARGET DBus::DBus) list(APPEND SOURCES UDisksProvider.cpp UDisks2Provider.cpp) list(APPEND HEADERS UDisksProvider.h diff --git a/xbmc/windowing/linux/CMakeLists.txt b/xbmc/windowing/linux/CMakeLists.txt index 2a36d63120383..7b002c22dac51 100644 --- a/xbmc/windowing/linux/CMakeLists.txt +++ b/xbmc/windowing/linux/CMakeLists.txt @@ -1,7 +1,7 @@ set(SOURCES "") set(HEADERS "") -if(DBUS_FOUND) +if(TARGET DBus::DBus) list(APPEND SOURCES OSScreenSaverFreedesktop.cpp) list(APPEND HEADERS OSScreenSaverFreedesktop.h) endif() From 4274b70f2ebbca5418916eb80a94929fced52283 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:04:11 +1000 Subject: [PATCH 152/811] [cmake] FindXSLT move to full TARGET use --- cmake/modules/FindXSLT.cmake | 57 ++++++++++++++++-------------------- xbmc/utils/CMakeLists.txt | 2 +- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/cmake/modules/FindXSLT.cmake b/cmake/modules/FindXSLT.cmake index 3637214844694..316b35ed3d7e0 100644 --- a/cmake/modules/FindXSLT.cmake +++ b/cmake/modules/FindXSLT.cmake @@ -3,48 +3,41 @@ # -------- # Finds the XSLT library # -# This will define the following variables:: +# This will define the following target: # -# XSLT_FOUND - system has XSLT -# XSLT_INCLUDE_DIRS - the XSLT include directory -# XSLT_LIBRARIES - the XSLT libraries -# XSLT_DEFINITIONS - the XSLT definitions -# -# and the following imported targets:: -# -# XSLT::XSLT - The XSLT library +# XSLT::XSLT - The XSLT library -find_package(LibXml2 REQUIRED) +if(NOT TARGET XSLT::XSLT) -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_XSLT libxslt QUIET) -endif() + find_package(LibXml2 REQUIRED) + find_package(PkgConfig) -find_path(XSLT_INCLUDE_DIR NAMES libxslt/xslt.h - PATHS ${PC_XSLT_INCLUDEDIR}) -find_library(XSLT_LIBRARY NAMES xslt libxslt - PATHS ${PC_XSLT_LIBDIR}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_XSLT libxslt QUIET) + endif() -set(XSLT_VERSION ${PC_XSLT_VERSION}) + find_path(XSLT_INCLUDE_DIR NAMES libxslt/xslt.h + PATHS ${PC_XSLT_INCLUDEDIR}) + find_library(XSLT_LIBRARY NAMES xslt libxslt + PATHS ${PC_XSLT_LIBDIR}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(XSLT - REQUIRED_VARS XSLT_LIBRARY XSLT_INCLUDE_DIR - VERSION_VAR XSLT_VERSION) + set(XSLT_VERSION ${PC_XSLT_VERSION}) -if(XSLT_FOUND) - set(XSLT_LIBRARIES ${XSLT_LIBRARY} ${LIBXML2_LIBRARIES}) - set(XSLT_INCLUDE_DIRS ${XSLT_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}) - set(XSLT_DEFINITIONS -DHAVE_LIBXSLT=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(XSLT + REQUIRED_VARS XSLT_LIBRARY XSLT_INCLUDE_DIR + VERSION_VAR XSLT_VERSION) - if(NOT TARGET XSLT::XSLT) + if(XSLT_FOUND) add_library(XSLT::XSLT UNKNOWN IMPORTED) set_target_properties(XSLT::XSLT PROPERTIES IMPORTED_LOCATION "${XSLT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${XSLT_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXSLT=1 - INTERFACE_LINK_LIBRARIES "${LIBXML2_LIBRARIES}") + INTERFACE_INCLUDE_DIRECTORIES "${XSLT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXSLT=1) + + target_link_libraries(XSLT::XSLT INTERFACE LibXml2::LibXml2) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XSLT::XSLT) endif() -endif() -mark_as_advanced(XSLT_INCLUDE_DIR XSLT_LIBRARY) + mark_as_advanced(XSLT_INCLUDE_DIR XSLT_LIBRARY) +endif() diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 327e0b43e23f3..073d6c450de05 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -183,7 +183,7 @@ set(HEADERS ActorProtocol.h XMLUtils.h XTimeUtils.h) -if(XSLT_FOUND) +if(TARGET XSLT::XSLT) list(APPEND SOURCES XSLTUtils.cpp) list(APPEND HEADERS XSLTUtils.h) endif() From da125da78ef33371830acfd42e6e899c8656f725 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:14:32 +1000 Subject: [PATCH 153/811] [cmake] FindALSA migrate to full TARGET usage --- CMakeLists.txt | 2 +- cmake/modules/FindAlsa.cmake | 53 ++++++++++++--------------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- xbmc/platform/freebsd/CMakeLists.txt | 2 +- xbmc/platform/linux/CMakeLists.txt | 2 +- 5 files changed, 27 insertions(+), 34 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7dbbc53a8983e..4a09576bc56d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,7 +261,7 @@ endif() set(outputFilterRegex "addons/xbmc.json") find_addon_xml_in_files(${outputFilterRegex}) -if(ALSA_FOUND AND PULSEAUDIO_FOUND) +if(TARGET ALSA::ALSA AND PULSEAUDIO_FOUND) list(APPEND AUDIO_BACKENDS_LIST "alsa+pulseaudio") endif() diff --git a/cmake/modules/FindAlsa.cmake b/cmake/modules/FindAlsa.cmake index 6495b1aa5d85c..178e23033d78f 100644 --- a/cmake/modules/FindAlsa.cmake +++ b/cmake/modules/FindAlsa.cmake @@ -3,46 +3,39 @@ # -------- # Finds the Alsa library # -# This will define the following variables:: -# -# ALSA_FOUND - system has Alsa -# ALSA_INCLUDE_DIRS - the Alsa include directory -# ALSA_LIBRARIES - the Alsa libraries -# ALSA_DEFINITIONS - the Alsa compile definitions -# -# and the following imported targets:: +# This will define the following target: # # ALSA::ALSA - The Alsa library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_ALSA alsa>=1.0.27 QUIET) -endif() +if(NOT TARGET ALSA::ALSA) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_ALSA alsa>=1.0.27 QUIET) + endif() -find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h - PATHS ${PC_ALSA_INCLUDEDIR}) -find_library(ALSA_LIBRARY NAMES asound - PATHS ${PC_ALSA_LIBDIR}) + find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h + PATHS ${PC_ALSA_INCLUDEDIR} + NO_CACHE) + find_library(ALSA_LIBRARY NAMES asound + PATHS ${PC_ALSA_LIBDIR} + NO_CACHE) -set(ALSA_VERSION ${PC_ALSA_VERSION}) + set(ALSA_VERSION ${PC_ALSA_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Alsa - REQUIRED_VARS ALSA_LIBRARY ALSA_INCLUDE_DIR - VERSION_VAR ALSA_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Alsa + REQUIRED_VARS ALSA_LIBRARY ALSA_INCLUDE_DIR + VERSION_VAR ALSA_VERSION) -if(ALSA_FOUND) - set(ALSA_INCLUDE_DIRS "") # Don't want these added as 'timer.h' is a dangerous file - set(ALSA_LIBRARIES ${ALSA_LIBRARY}) - set(ALSA_DEFINITIONS -DHAS_ALSA=1) - list(APPEND AUDIO_BACKENDS_LIST "alsa") - set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) + if(ALSA_FOUND) + list(APPEND AUDIO_BACKENDS_LIST "alsa") + set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) - if(NOT TARGET ALSA::ALSA) + # We explicitly dont include ALSA_INCLUDE_DIR, as 'timer.h' is a dangerous file add_library(ALSA::ALSA UNKNOWN IMPORTED) set_target_properties(ALSA::ALSA PROPERTIES IMPORTED_LOCATION "${ALSA_LIBRARY}" - INTERFACE_COMPILE_DEFINITIONS "${ALSA_DEFINITIONS}") + INTERFACE_COMPILE_DEFINITIONS HAS_ALSA=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ALSA::ALSA) endif() endif() - -mark_as_advanced(ALSA_INCLUDE_DIR ALSA_LIBRARY) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 190c9ddeb2e66..e6a00ad711037 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -46,7 +46,7 @@ set(HEADERS AEResampleFactory.h Utils/AEStreamInfo.h Utils/AEUtil.h) -if(ALSA_FOUND) +if(TARGET ALSA::ALSA) list(APPEND SOURCES Sinks/AESinkALSA.cpp Utils/AEELDParser.cpp) list(APPEND HEADERS Sinks/AESinkALSA.h diff --git a/xbmc/platform/freebsd/CMakeLists.txt b/xbmc/platform/freebsd/CMakeLists.txt index 1c045d2ab708e..3506c2ee70cf5 100644 --- a/xbmc/platform/freebsd/CMakeLists.txt +++ b/xbmc/platform/freebsd/CMakeLists.txt @@ -13,7 +13,7 @@ set(HEADERS ../linux/AppParamParserLinux.cpp ../linux/TimeUtils.h PlatformFreebsd.h) -if(ALSA_FOUND) +if(TARGET ALSA::ALSA) list(APPEND SOURCES ../linux/FDEventMonitor.cpp) list(APPEND HEADERS ../linux/FDEventMonitor.h) endif() diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt index 7292690c04ea8..0c8bccf8c03c9 100644 --- a/xbmc/platform/linux/CMakeLists.txt +++ b/xbmc/platform/linux/CMakeLists.txt @@ -18,7 +18,7 @@ if(TARGET_WEBOS) list(APPEND HEADERS AppParamParserWebOS.h) endif() -if(ALSA_FOUND) +if(TARGET ALSA::ALSA) list(APPEND SOURCES FDEventMonitor.cpp) list(APPEND HEADERS FDEventMonitor.h) endif() From 445a213e9280d56ea1534de2786db1e0a7129a5a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:18:39 +1000 Subject: [PATCH 154/811] [cmake] FindLircClient migrate to full TARGET usage --- cmake/modules/FindLircClient.cmake | 42 +++++++++++------------- xbmc/platform/linux/input/CMakeLists.txt | 2 +- 2 files changed, 20 insertions(+), 24 deletions(-) diff --git a/cmake/modules/FindLircClient.cmake b/cmake/modules/FindLircClient.cmake index c469af57d7669..947acd8d78cc5 100644 --- a/cmake/modules/FindLircClient.cmake +++ b/cmake/modules/FindLircClient.cmake @@ -2,35 +2,31 @@ # ----------- # Finds the liblirc_client library # -# This will define the following variables:: +# This will define the following target: # -# LIRCCLIENT_FOUND - if false, do not try to link to lirc_client -# LIRCCLIENT_INCLUDE_DIRS - where to find lirc/lirc_client.h -# LIRCCLIENT_LIBRARYS - the library to link against -# LIRCCLIENT_DEFINITIONS - the lirc definitions +# LIRCCLIENT::LIRCCLIENT - The lirc library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIRC lirc QUIET) -endif() - -find_path(LIRCCLIENT_INCLUDE_DIR lirc/lirc_client.h PATHS ${PC_LIRC_INCLUDEDIR}) -find_library(LIRCCLIENT_LIBRARY lirc_client PATHS ${PC_LIRC_LIBDIR}) +if(NOT TARGET LIRCCLIENT::LIRCCLIENT) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIRC lirc QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LircClient - REQUIRED_VARS LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) + find_path(LIRCCLIENT_INCLUDE_DIR lirc/lirc_client.h PATHS ${PC_LIRC_INCLUDEDIR}) + find_library(LIRCCLIENT_LIBRARY lirc_client PATHS ${PC_LIRC_LIBDIR}) -if(LIRCCLIENT_FOUND) - set(LIRCCLIENT_LIBRARIES ${LIRCCLIENT_LIBRARY}) - set(LIRCCLIENT_INCLUDE_DIRS ${LIRCCLIENT_INCLUDE_DIR}) - set(LIRCCLIENT_DEFINITIONS -DHAS_LIRC=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LircClient + REQUIRED_VARS LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) - if(NOT TARGET LIRCCLIENT::LIRCCLIENT) + if(LIRCCLIENT_FOUND) add_library(LIRCCLIENT::LIRCCLIENT UNKNOWN IMPORTED) set_target_properties(LIRCCLIENT::LIRCCLIENT PROPERTIES - IMPORTED_LOCATION "${LIRCCLIENT_LIBRARYS}" - INTERFACE_INCLUDE_DIRECTORIES "${LIRCCLIENT_INCLUDE_DIRS}") + IMPORTED_LOCATION "${LIRCCLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIRCCLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "HAS_LIRC=1") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LIRCCLIENT::LIRCCLIENT) endif() -endif() -mark_as_advanced(LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) \ No newline at end of file + mark_as_advanced(LIRCCLIENT_LIBRARY LIRCCLIENT_INCLUDE_DIR) +endif() diff --git a/xbmc/platform/linux/input/CMakeLists.txt b/xbmc/platform/linux/input/CMakeLists.txt index ebb2ccd585288..0859158c9c1f3 100644 --- a/xbmc/platform/linux/input/CMakeLists.txt +++ b/xbmc/platform/linux/input/CMakeLists.txt @@ -1,7 +1,7 @@ set(SOURCES "") set(HEADERS "") -if(LIRCCLIENT_FOUND) +if(TARGET LIRCCLIENT::LIRCCLIENT) list(APPEND SOURCES LIRC.cpp) list(APPEND HEADERS LIRC.h) endif() From 12c5e4479f9a8679425aa0270cadc659ee7bef64 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:32:02 +1000 Subject: [PATCH 155/811] [cmake] FindUDEV migrate to full TARGET usage --- CMakeLists.txt | 2 +- cmake/modules/FindUDEV.cmake | 47 ++++++++----------- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- .../platform/linux/peripherals/CMakeLists.txt | 2 +- xbmc/platform/linux/storage/CMakeLists.txt | 2 +- 5 files changed, 23 insertions(+), 32 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4a09576bc56d2..26196ef78233c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,7 +236,7 @@ if(NOT MARIADBCLIENT_FOUND) core_optional_dep(MySqlClient) endif() -if(NOT UDEV_FOUND) +if(NOT TARGET UDEV::UDEV) core_optional_dep(LibUSB) endif() diff --git a/cmake/modules/FindUDEV.cmake b/cmake/modules/FindUDEV.cmake index a8840252567f3..2a88c496b785b 100644 --- a/cmake/modules/FindUDEV.cmake +++ b/cmake/modules/FindUDEV.cmake @@ -3,45 +3,36 @@ # ------- # Finds the UDEV library # -# This will define the following variables:: -# -# UDEV_FOUND - system has UDEV -# UDEV_INCLUDE_DIRS - the UDEV include directory -# UDEV_LIBRARIES - the UDEV libraries -# UDEV_DEFINITIONS - the UDEV definitions -# -# and the following imported targets:: +# This will define the following target: # # UDEV::UDEV - The UDEV library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_UDEV libudev QUIET) -endif() - -find_path(UDEV_INCLUDE_DIR NAMES libudev.h - PATHS ${PC_UDEV_INCLUDEDIR}) -find_library(UDEV_LIBRARY NAMES udev - PATHS ${PC_UDEV_LIBDIR}) +if(NOT TARGET UDEV::UDEV) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_UDEV libudev QUIET) + endif() -set(UDEV_VERSION ${PC_UDEV_VERSION}) + find_path(UDEV_INCLUDE_DIR NAMES libudev.h + PATHS ${PC_UDEV_INCLUDEDIR} + NO_CACHE) + find_library(UDEV_LIBRARY NAMES udev + PATHS ${PC_UDEV_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(UDEV - REQUIRED_VARS UDEV_LIBRARY UDEV_INCLUDE_DIR - VERSION_VAR UDEV_VERSION) + set(UDEV_VERSION ${PC_UDEV_VERSION}) -if(UDEV_FOUND) - set(UDEV_LIBRARIES ${UDEV_LIBRARY}) - set(UDEV_INCLUDE_DIRS ${UDEV_INCLUDE_DIR}) - set(UDEV_DEFINITIONS -DHAVE_LIBUDEV=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(UDEV + REQUIRED_VARS UDEV_LIBRARY UDEV_INCLUDE_DIR + VERSION_VAR UDEV_VERSION) - if(NOT TARGET UDEV::UDEV) + if(UDEV_FOUND) add_library(UDEV::UDEV UNKNOWN IMPORTED) set_target_properties(UDEV::UDEV PROPERTIES IMPORTED_LOCATION "${UDEV_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${UDEV_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBUDEV=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP UDEV::UDEV) endif() endif() - -mark_as_advanced(UDEV_INCLUDE_DIR UDEV_LIBRARY) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index e6a00ad711037..18425cc58bf84 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -57,7 +57,7 @@ if(TARGET ALSA::ALSA) list(APPEND HEADERS Sinks/alsa/ALSAHControlMonitor.h) endif() - if(UDEV_FOUND) + if(TARGET UDEV::UDEV) list(APPEND SOURCES Sinks/alsa/ALSADeviceMonitor.cpp) list(APPEND HEADERS Sinks/alsa/ALSADeviceMonitor.h) endif() diff --git a/xbmc/platform/linux/peripherals/CMakeLists.txt b/xbmc/platform/linux/peripherals/CMakeLists.txt index 626aa40205750..0458e951680f2 100644 --- a/xbmc/platform/linux/peripherals/CMakeLists.txt +++ b/xbmc/platform/linux/peripherals/CMakeLists.txt @@ -1,4 +1,4 @@ -if(UDEV_FOUND) +if(TARGET UDEV::UDEV) list(APPEND SOURCES PeripheralBusUSBLibUdev.cpp) list(APPEND HEADERS PeripheralBusUSBLibUdev.h) elseif(LIBUSB_FOUND) diff --git a/xbmc/platform/linux/storage/CMakeLists.txt b/xbmc/platform/linux/storage/CMakeLists.txt index 322ee5cffd804..9e56475a603ae 100644 --- a/xbmc/platform/linux/storage/CMakeLists.txt +++ b/xbmc/platform/linux/storage/CMakeLists.txt @@ -9,7 +9,7 @@ if(TARGET DBus::DBus) UDisks2Provider.h) endif() -if(UDEV_FOUND) +if(TARGET UDEV::UDEV) list(APPEND SOURCES UDevProvider.cpp) list(APPEND HEADERS UDevProvider.h) endif() From 2eca856d938ec04d858e00eaf8f492d98713ba9f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:35:22 +1000 Subject: [PATCH 156/811] [cmake] FindLibUSB migrate to full TARGET usage --- cmake/modules/FindLibUSB.cmake | 45 ++++++++----------- .../platform/linux/peripherals/CMakeLists.txt | 2 +- 2 files changed, 20 insertions(+), 27 deletions(-) diff --git a/cmake/modules/FindLibUSB.cmake b/cmake/modules/FindLibUSB.cmake index 7bf3a922b0aaf..4efc670d465be 100644 --- a/cmake/modules/FindLibUSB.cmake +++ b/cmake/modules/FindLibUSB.cmake @@ -3,43 +3,36 @@ # ---------- # Finds the USB library # -# This will define the following variables:: -# -# LIBUSB_FOUND - system has LibUSB -# LIBUSB_INCLUDE_DIRS - the USB include directory -# LIBUSB_LIBRARIES - the USB libraries -# -# and the following imported targets:: +# This will define the following target: # # LibUSB::LibUSB - The USB library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBUSB libusb QUIET) -endif() +if(NOT TARGET LibUSB::LibUSB) + find_package(PkgConfig) -find_path(LIBUSB_INCLUDE_DIR usb.h - PATHS ${PC_LIBUSB_INCLUDEDIR}) -find_library(LIBUSB_LIBRARY NAMES usb - PATHS ${PC_LIBUSB_INCLUDEDIR}) -set(LIBUSB_VERSION ${PC_LIBUSB_VERSION}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBUSB libusb QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibUSB - REQUIRED_VARS LIBUSB_LIBRARY LIBUSB_INCLUDE_DIR - VERSION_VAR LIBUSB_VERSION) + find_path(LIBUSB_INCLUDE_DIR usb.h + PATHS ${PC_LIBUSB_INCLUDEDIR} + NO_CACHE) + find_library(LIBUSB_LIBRARY NAMES usb + PATHS ${PC_LIBUSB_INCLUDEDIR} + NO_CACHE) + set(LIBUSB_VERSION ${PC_LIBUSB_VERSION}) -if(LIBUSB_FOUND) - set(LIBUSB_INCLUDE_DIRS ${LIBUSB_INCLUDE_DIR}) - set(LIBUSB_LIBRARIES ${LIBUSB_LIBRARY}) - set(LIBUSB_DEFINITIONS -DUSE_LIBUSB=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibUSB + REQUIRED_VARS LIBUSB_LIBRARY LIBUSB_INCLUDE_DIR + VERSION_VAR LIBUSB_VERSION) - if(NOT TARGET LibUSB::LibUSB) + if(LIBUSB_FOUND) add_library(LibUSB::LibUSB UNKNOWN IMPORTED) set_target_properties(LibUSB::LibUSB PROPERTIES IMPORTED_LOCATION "${LIBUSB_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${LIBUSB_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS USE_LIBUSB=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LibUSB::LibUSB) endif() endif() - -mark_as_advanced(USB_INCLUDE_DIR USB_LIBRARY) diff --git a/xbmc/platform/linux/peripherals/CMakeLists.txt b/xbmc/platform/linux/peripherals/CMakeLists.txt index 0458e951680f2..8295e1eae30ee 100644 --- a/xbmc/platform/linux/peripherals/CMakeLists.txt +++ b/xbmc/platform/linux/peripherals/CMakeLists.txt @@ -1,7 +1,7 @@ if(TARGET UDEV::UDEV) list(APPEND SOURCES PeripheralBusUSBLibUdev.cpp) list(APPEND HEADERS PeripheralBusUSBLibUdev.h) -elseif(LIBUSB_FOUND) +elseif(TARGET LibUSB::LibUSB) list(APPEND SOURCES PeripheralBusUSBLibUSB.cpp) list(APPEND HEADERS PeripheralBusUSBLibUSB.h) endif() From a56bb0a0064b57b5dfefbb5e3e2047266396d9fc Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:42:14 +1000 Subject: [PATCH 157/811] [cmake] FindCEC migrate to full TARGET usage --- cmake/modules/FindCEC.cmake | 72 +++++++++------------ xbmc/peripherals/bus/virtual/CMakeLists.txt | 2 +- xbmc/peripherals/devices/CMakeLists.txt | 2 +- 3 files changed, 32 insertions(+), 44 deletions(-) diff --git a/cmake/modules/FindCEC.cmake b/cmake/modules/FindCEC.cmake index 00b2e12727b00..b3874ff883341 100644 --- a/cmake/modules/FindCEC.cmake +++ b/cmake/modules/FindCEC.cmake @@ -3,59 +3,47 @@ # ------- # Finds the libCEC library # -# This will define the following variables:: +# This will define the following target: # -# CEC_FOUND - system has libCEC -# CEC_INCLUDE_DIRS - the libCEC include directory -# CEC_LIBRARIES - the libCEC libraries -# CEC_DEFINITIONS - the libCEC compile definitions -# -# and the following imported targets:: -# -# CEC::CEC - The libCEC library +# CEC::CEC - The libCEC library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_CEC libcec QUIET) -endif() - -find_path(CEC_INCLUDE_DIR NAMES libcec/cec.h libCEC/CEC.h - PATHS ${PC_CEC_INCLUDEDIR}) +if(NOT TARGET CEC::CEC) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CEC libcec QUIET) + endif() -if(PC_CEC_VERSION) - set(CEC_VERSION ${PC_CEC_VERSION}) -elseif(CEC_INCLUDE_DIR AND EXISTS "${CEC_INCLUDE_DIR}/libcec/version.h") - file(STRINGS "${CEC_INCLUDE_DIR}/libcec/version.h" cec_version_str REGEX "^[\t ]+LIBCEC_VERSION_TO_UINT\\(.*\\)") - string(REGEX REPLACE "^[\t ]+LIBCEC_VERSION_TO_UINT\\(([0-9]+), ([0-9]+), ([0-9]+)\\)" "\\1.\\2.\\3" CEC_VERSION "${cec_version_str}") - unset(cec_version_str) -endif() + find_path(CEC_INCLUDE_DIR NAMES libcec/cec.h libCEC/CEC.h + PATHS ${PC_CEC_INCLUDEDIR} + NO_CACHE) -if(NOT CEC_FIND_VERSION) - set(CEC_FIND_VERSION 4.0.0) -endif() + if(PC_CEC_VERSION) + set(CEC_VERSION ${PC_CEC_VERSION}) + elseif(CEC_INCLUDE_DIR AND EXISTS "${CEC_INCLUDE_DIR}/libcec/version.h") + file(STRINGS "${CEC_INCLUDE_DIR}/libcec/version.h" cec_version_str REGEX "^[\t ]+LIBCEC_VERSION_TO_UINT\\(.*\\)") + string(REGEX REPLACE "^[\t ]+LIBCEC_VERSION_TO_UINT\\(([0-9]+), ([0-9]+), ([0-9]+)\\)" "\\1.\\2.\\3" CEC_VERSION "${cec_version_str}") + unset(cec_version_str) + endif() -find_library(CEC_LIBRARY NAMES cec - PATHS ${PC_CEC_LIBDIR}) + if(NOT CEC_FIND_VERSION) + set(CEC_FIND_VERSION 4.0.0) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CEC - REQUIRED_VARS CEC_LIBRARY CEC_INCLUDE_DIR - VERSION_VAR CEC_VERSION) + find_library(CEC_LIBRARY NAMES cec + PATHS ${PC_CEC_LIBDIR} + NO_CACHE) -if(CEC_FOUND) - set(CEC_LIBRARIES ${CEC_LIBRARY}) - set(CEC_INCLUDE_DIRS ${CEC_INCLUDE_DIR}) - set(CEC_DEFINITIONS -DHAVE_LIBCEC=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CEC + REQUIRED_VARS CEC_LIBRARY CEC_INCLUDE_DIR + VERSION_VAR CEC_VERSION) - if(NOT TARGET CEC::CEC) + if(CEC_FOUND) add_library(CEC::CEC UNKNOWN IMPORTED) - if(CEC_LIBRARY) - set_target_properties(CEC::CEC PROPERTIES - IMPORTED_LOCATION "${CEC_LIBRARY}") - endif() set_target_properties(CEC::CEC PROPERTIES + IMPORTED_LOCATION "${CEC_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${CEC_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBCEC=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CEC::CEC) endif() endif() - -mark_as_advanced(CEC_INCLUDE_DIR CEC_LIBRARY) diff --git a/xbmc/peripherals/bus/virtual/CMakeLists.txt b/xbmc/peripherals/bus/virtual/CMakeLists.txt index bf596958ea295..0a70d59c84e61 100644 --- a/xbmc/peripherals/bus/virtual/CMakeLists.txt +++ b/xbmc/peripherals/bus/virtual/CMakeLists.txt @@ -4,7 +4,7 @@ set(SOURCES PeripheralBusAddon.cpp set(HEADERS PeripheralBusAddon.h PeripheralBusApplication.h) -if(CEC_FOUND) +if(TARGET CEC::CEC) list(APPEND SOURCES PeripheralBusCEC.cpp) list(APPEND HEADERS PeripheralBusCEC.h) endif() diff --git a/xbmc/peripherals/devices/CMakeLists.txt b/xbmc/peripherals/devices/CMakeLists.txt index 57795f5ffd869..873acc443193d 100644 --- a/xbmc/peripherals/devices/CMakeLists.txt +++ b/xbmc/peripherals/devices/CMakeLists.txt @@ -22,7 +22,7 @@ set(HEADERS Peripheral.h PeripheralNyxboard.h PeripheralTuner.h) -if(CEC_FOUND) +if(TARGET CEC::CEC) list(APPEND SOURCES PeripheralCecAdapter.cpp) list(APPEND HEADERS PeripheralCecAdapter.h) endif() From 1cf173748b122278aecb9222b3f517d501d084b4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:44:57 +1000 Subject: [PATCH 158/811] [cmake] FindPlist migrate to full TARGET usage --- cmake/modules/FindPlist.cmake | 54 ++++++++++++++--------------------- xbmc/network/CMakeLists.txt | 2 +- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/cmake/modules/FindPlist.cmake b/cmake/modules/FindPlist.cmake index 8f9b2d6cd7d4f..368b6758d5639 100644 --- a/cmake/modules/FindPlist.cmake +++ b/cmake/modules/FindPlist.cmake @@ -3,49 +3,37 @@ # --------- # Finds the Plist library # -# This will define the following variables:: +# This will define the following target: # -# PLIST_FOUND - system has Plist library -# PLIST_INCLUDE_DIRS - the Plist library include directory -# PLIST_LIBRARIES - the Plist libraries -# PLIST_DEFINITIONS - the Plist compile definitions -# -# and the following imported targets:: -# -# Plist::Plist - The Plist library - -if(PKG_CONFIG_FOUND) - pkg_search_module(PC_PLIST libplist-2.0 libplist QUIET) -endif() +# Plist::Plist - The Plist library -find_path(PLIST_INCLUDE_DIR plist/plist.h - PATHS ${PC_PLIST_INCLUDEDIR}) +if(NOT TARGET Plist::Plist) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_search_module(PC_PLIST libplist-2.0 libplist QUIET) + endif() -set(PLIST_VERSION ${PC_PLIST_VERSION}) + find_path(PLIST_INCLUDE_DIR plist/plist.h + PATHS ${PC_PLIST_INCLUDEDIR} + NO_CACHE) -find_library(PLIST_LIBRARY NAMES plist-2.0 plist libplist-2.0 libplist - PATHS ${PC_PLIST_LIBDIR}) + set(PLIST_VERSION ${PC_PLIST_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Plist - REQUIRED_VARS PLIST_LIBRARY PLIST_INCLUDE_DIR - VERSION_VAR PLIST_VERSION) + find_library(PLIST_LIBRARY NAMES plist-2.0 plist libplist-2.0 libplist + PATHS ${PC_PLIST_LIBDIR} + NO_CACHE) -if(PLIST_FOUND) - set(PLIST_LIBRARIES ${PLIST_LIBRARY}) - set(PLIST_INCLUDE_DIRS ${PLIST_INCLUDE_DIR}) - set(PLIST_DEFINITIONS -DHAS_AIRPLAY=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Plist + REQUIRED_VARS PLIST_LIBRARY PLIST_INCLUDE_DIR + VERSION_VAR PLIST_VERSION) - if(NOT TARGET Plist::Plist) + if(PLIST_FOUND) add_library(Plist::Plist UNKNOWN IMPORTED) - if(PLIST_LIBRARY) - set_target_properties(Plist::Plist PROPERTIES - IMPORTED_LOCATION "${PLIST_LIBRARY}") - endif() set_target_properties(Plist::Plist PROPERTIES + IMPORTED_LOCATION "${PLIST_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${PLIST_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_AIRPLAY=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Plist::Plist) endif() endif() - -mark_as_advanced(PLIST_INCLUDE_DIR PLIST_LIBRARY) diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index 400e15161590a..d6560e6d3d2da 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -31,7 +31,7 @@ if(ENABLE_OPTICAL) list(APPEND HEADERS cddb.h) endif() -if(PLIST_FOUND) +if(TARGET Plist::Plist) list(APPEND SOURCES AirPlayServer.cpp) list(APPEND HEADERS AirPlayServer.h) endif() From b509f541ea17b23424c54912d58ad0c079c0b6c6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:48:31 +1000 Subject: [PATCH 159/811] [cmake] FindShairplay migrate to full TARGET usage --- CMakeLists.txt | 2 +- cmake/modules/FindShairplay.cmake | 63 ++++++++++++------------------- xbmc/network/CMakeLists.txt | 2 +- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 26196ef78233c..3488ab96de150 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -250,7 +250,7 @@ endif() if(ENABLE_AIRTUNES) find_package(Shairplay) - if(SHAIRPLAY_FOUND) + if(TARGET Shairplay::Shairplay) core_require_dep(Shairplay) endif() endif() diff --git a/cmake/modules/FindShairplay.cmake b/cmake/modules/FindShairplay.cmake index 506cffd9af8d2..df4afa72d5b14 100644 --- a/cmake/modules/FindShairplay.cmake +++ b/cmake/modules/FindShairplay.cmake @@ -3,55 +3,42 @@ # ------------- # Finds the Shairplay library # -# This will define the following variables:: +# This will define the following target: # -# SHAIRPLAY_FOUND - system has Shairplay -# SHAIRPLAY_INCLUDE_DIRS - the Shairplay include directory -# SHAIRPLAY_LIBRARIES - the Shairplay libraries -# SHAIRPLAY_DEFINITIONS - the Shairplay compile definitions -# -# and the following imported targets:: -# -# Shairplay::Shairplay - The Shairplay library +# Shairplay::Shairplay - The Shairplay library -find_path(SHAIRPLAY_INCLUDE_DIR shairplay/raop.h) +if(NOT TARGET Shairplay::Shairplay) -include(FindPackageHandleStandardArgs) -find_library(SHAIRPLAY_LIBRARY NAMES shairplay libshairplay) + find_path(SHAIRPLAY_INCLUDE_DIR shairplay/raop.h NO_CACHE) -if(SHAIRPLAY_INCLUDE_DIR AND SHAIRPLAY_LIBRARY) - include(CheckCSourceCompiles) - set(CMAKE_REQUIRED_INCLUDES ${SHAIRPLAY_INCLUDE_DIR}) - set(CMAKE_REQUIRED_LIBRARIES ${SHAIRPLAY_LIBRARIES}) - check_c_source_compiles("#include + include(FindPackageHandleStandardArgs) + find_library(SHAIRPLAY_LIBRARY NAMES shairplay libshairplay + NO_CACHE) - int main() - { - struct raop_callbacks_s foo; - foo.cls; - return 0; - } - " HAVE_SHAIRPLAY_CALLBACK_CLS) -endif() + if(SHAIRPLAY_INCLUDE_DIR AND SHAIRPLAY_LIBRARY) + include(CheckCSourceCompiles) + set(CMAKE_REQUIRED_INCLUDES ${SHAIRPLAY_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES ${SHAIRPLAY_LIBRARIES}) + check_c_source_compiles("#include -find_package_handle_standard_args(Shairplay - REQUIRED_VARS SHAIRPLAY_LIBRARY SHAIRPLAY_INCLUDE_DIR HAVE_SHAIRPLAY_CALLBACK_CLS) + int main() + { + struct raop_callbacks_s foo; + foo.cls; + return 0; + } + " HAVE_SHAIRPLAY_CALLBACK_CLS) + endif() -if(SHAIRPLAY_FOUND) - set(SHAIRPLAY_LIBRARIES ${SHAIRPLAY_LIBRARY}) - set(SHAIRPLAY_INCLUDE_DIRS ${SHAIRPLAY_INCLUDE_DIR}) - set(SHAIRPLAY_DEFINITIONS -DHAS_AIRTUNES=1) + find_package_handle_standard_args(Shairplay + REQUIRED_VARS SHAIRPLAY_LIBRARY SHAIRPLAY_INCLUDE_DIR HAVE_SHAIRPLAY_CALLBACK_CLS) - if(NOT TARGET Shairplay::Shairplay) + if(SHAIRPLAY_FOUND) add_library(Shairplay::Shairplay UNKNOWN IMPORTED) - if(SHAIRPLAY_LIBRARY) - set_target_properties(Shairplay::Shairplay PROPERTIES - IMPORTED_LOCATION "${SHAIRPLAY_LIBRARY}") - endif() set_target_properties(Shairplay::Shairplay PROPERTIES + IMPORTED_LOCATION "${SHAIRPLAY_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${SHAIRPLAY_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_AIRTUNES=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Shairplay::Shairplay) endif() endif() - -mark_as_advanced(SHAIRPLAY_INCLUDE_DIR SHAIRPLAY_LIBRARY) diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index d6560e6d3d2da..50757a6c89d86 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -36,7 +36,7 @@ if(TARGET Plist::Plist) list(APPEND HEADERS AirPlayServer.h) endif() -if(SHAIRPLAY_FOUND) +if(TARGET Shairplay::Shairplay) list(APPEND SOURCES AirTunesServer.cpp) list(APPEND HEADERS AirTunesServer.h) endif() From 84c4cae1ae55dddfada4c80e5054b0405e860b84 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 15:52:21 +1000 Subject: [PATCH 160/811] [cmake] FindMDNS migrate to full TARGET usage --- cmake/modules/FindMDNS.cmake | 44 ++++++++++++-------------------- xbmc/network/mdns/CMakeLists.txt | 2 +- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindMDNS.cmake b/cmake/modules/FindMDNS.cmake index c2574c8e537b7..1ae839b81f15e 100644 --- a/cmake/modules/FindMDNS.cmake +++ b/cmake/modules/FindMDNS.cmake @@ -3,45 +3,33 @@ # -------- # Finds the mDNS library # -# This will define the following variables:: -# -# MDNS_FOUND - system has mDNS -# MDNS_INCLUDE_DIRS - the mDNS include directory -# MDNS_LIBRARIES - the mDNS libraries -# MDNS_DEFINITIONS - the mDNS definitions -# -# and the following imported targets:: +# This will define the following target: # # MDNS::MDNS - The mDNSlibrary -find_path(MDNS_INCLUDE_DIR NAMES dmDnsEmbedded.h dns_sd.h) -find_library(MDNS_LIBRARY NAMES mDNSEmbedded dnssd) +if(NOT TARGET MDNS::MDNS) + find_path(MDNS_INCLUDE_DIR NAMES dmDnsEmbedded.h dns_sd.h + NO_CACHE) + find_library(MDNS_LIBRARY NAMES mDNSEmbedded dnssd + NO_CACHE) -find_path(MDNS_EMBEDDED_INCLUDE_DIR NAMES mDnsEmbedded.h) + find_path(MDNS_EMBEDDED_INCLUDE_DIR NAMES mDnsEmbedded.h + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MDNS - REQUIRED_VARS MDNS_LIBRARY MDNS_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MDNS + REQUIRED_VARS MDNS_LIBRARY MDNS_INCLUDE_DIR) -if(MDNS_FOUND) - set(MDNS_INCLUDE_DIRS ${MDNS_INCLUDE_DIR}) - set(MDNS_LIBRARIES ${MDNS_LIBRARY}) - set(MDNS_DEFINITIONS -DHAS_MDNS=1 -DHAS_ZEROCONF=1) - if(MDNS_EMBEDDED_INCLUDE_DIR) - list(APPEND MDNS_DEFINITIONS -DHAS_MDNS_EMBEDDED=1) - endif() - - if(NOT TARGET MDNS::MDNS) + if(MDNS_FOUND) add_library(MDNS::MDNS UNKNOWN IMPORTED) set_target_properties(MDNS::MDNS PROPERTIES IMPORTED_LOCATION "${MDNS_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${MDNS_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_MDNS=1) + INTERFACE_COMPILE_DEFINITIONS "HAS_MDNS=1;HAS_ZEROCONF=1") if(MDNS_EMBEDDED_INCLUDE_DIR) - set_target_properties(MDNS::MDNS PROPERTIES - INTERFACE_COMPILE_DEFINITIONS HAS_MDNS_EMBEDDED=1) + set_property(TARGET MDNS::MDNS APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAS_MDNS_EMBEDDED=1) endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MDNS::MDNS) endif() endif() - -mark_as_advanced(MDNS_INCLUDE_DIR MDNS_EMBEDDED_INCLUDE_DIR MDNS_LIBRARY) diff --git a/xbmc/network/mdns/CMakeLists.txt b/xbmc/network/mdns/CMakeLists.txt index e0b084a88620b..66db8e8bc8fd7 100644 --- a/xbmc/network/mdns/CMakeLists.txt +++ b/xbmc/network/mdns/CMakeLists.txt @@ -1,4 +1,4 @@ -if(MDNS_FOUND) +if(TARGET MDNS::MDNS) set(SOURCES ZeroconfBrowserMDNS.cpp ZeroconfMDNS.cpp) From 5f9b9cfa26f274e381e92d73cfa33fb55582436e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:06:43 +1000 Subject: [PATCH 161/811] [cmake] FindUdfread migrate to full TARGET usage --- cmake/modules/FindUdfread.cmake | 111 +++++++++++++++++++++----------- xbmc/filesystem/CMakeLists.txt | 2 +- 2 files changed, 74 insertions(+), 39 deletions(-) diff --git a/cmake/modules/FindUdfread.cmake b/cmake/modules/FindUdfread.cmake index 35c1d9d5ba590..5aadd9167708b 100644 --- a/cmake/modules/FindUdfread.cmake +++ b/cmake/modules/FindUdfread.cmake @@ -3,55 +3,90 @@ # -------- # Finds the udfread library # -# This will define the following variables:: +# This will define the following target: # -# UDFREAD_FOUND - system has udfread -# UDFREAD_INCLUDE_DIRS - the udfread include directory -# UDFREAD_LIBRARIES - the udfread libraries -# UDFREAD_DEFINITIONS - the udfread definitions +# udfread::udfread - The libudfread library -if(ENABLE_INTERNAL_UDFREAD) - include(cmake/scripts/common/ModuleHelpers.cmake) +if(NOT TARGET udfread::udfread) + if(WIN32 OR WINDOWS_STORE) + include(FindPackageMessage) - set(MODULE_LC udfread) + find_package(libudfread CONFIG) - SETUP_BUILD_VARS() + if(libudfread_FOUND) + # Specifically tailored to kodi windows cmake config - Debug and RelWithDebInfo available + get_target_property(UDFREAD_LIBRARY_RELEASE libudfread::libudfread IMPORTED_LOCATION_RELWITHDEBINFO) + get_target_property(UDFREAD_LIBRARY_DEBUG libudfread::libudfread IMPORTED_LOCATION_DEBUG) + get_target_property(UDFREAD_INCLUDE_DIR libudfread::libudfread INTERFACE_INCLUDE_DIRECTORIES) - set(UDFREAD_VERSION ${${MODULE}_VER}) + include(SelectLibraryConfigurations) + select_library_configurations(UDFREAD) - set(CONFIGURE_COMMAND autoreconf -vif && - ./configure - --enable-static - --disable-shared - --prefix=${DEPENDS_PATH}) - set(BUILD_IN_SOURCE 1) + find_package_handle_standard_args(Udfread + REQUIRED_VARS UDFREAD_LIBRARY UDFREAD_INCLUDE_DIR + VERSION_VAR UDFREAD_VERSION) + endif() + else() + find_package(PkgConfig) + pkg_check_modules(udfread libudfread IMPORTED_TARGET GLOBAL QUIET) - BUILD_DEP_TARGET() + include(cmake/scripts/common/ModuleHelpers.cmake) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP udfread) -else() - if(PKG_CONFIG_FOUND) - pkg_search_module(PC_UDFREAD QUIET libudfread>=1.0.0 udfread>=1.0.0) - endif() + set(MODULE_LC udfread) + SETUP_BUILD_VARS() - find_path(UDFREAD_INCLUDE_DIR NAMES udfread/udfread.h - PATHS ${PC_UDFREAD_INCLUDEDIR}) + # Check for existing UDFREAD. If version >= UDFREAD-VERSION file version, dont build + # A corner case, but if a linux/freebsd user WANTS to build internal udfread, build anyway + if(NOT udfread_FOUND OR + (udfread_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_UDFREAD) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_UDFREAD)) - find_library(UDFREAD_LIBRARY NAMES udfread libudfread - PATHS ${PC_UDFREAD_LIBDIR}) + set(UDFREAD_VERSION ${${MODULE}_VER}) + set(BUILD_NAME udfread_build) - set(UDFREAD_VERSION ${PC_UDFREAD_VERSION}) -endif() + set(CONFIGURE_COMMAND autoreconf -vif && + ./configure + --enable-static + --disable-shared + --prefix=${DEPENDS_PATH}) + set(BUILD_IN_SOURCE 1) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Udfread - REQUIRED_VARS UDFREAD_LIBRARY UDFREAD_INCLUDE_DIR - VERSION_VAR UDFREAD_VERSION) + BUILD_DEP_TARGET() + elseif(udfread_FOUND) + get_target_property(UDFREAD_LIBRARY PkgConfig::udfread INTERFACE_LINK_LIBRARIES) + get_target_property(UDFREAD_INCLUDE_DIR PkgConfig::udfread INTERFACE_INCLUDE_DIRECTORIES) + set(UDFREAD_VERSION ${udfread_VERSION}) + endif() -if(UDFREAD_FOUND) - set(UDFREAD_LIBRARIES ${UDFREAD_LIBRARY}) - set(UDFREAD_INCLUDE_DIRS ${UDFREAD_INCLUDE_DIR}) - set(UDFREAD_DEFINITIONS -DHAS_UDFREAD=1) -endif() + if(udfread_FOUND OR TARGET udfread_build) + set(UDFREAD_FOUND ${udfread_FOUND}) + include(FindPackageMessage) + find_package_message(udfread "Found udfread: ${UDFREAD_LIBRARY} (version: \"${UDFREAD_VERSION}\")" "[${UDFREAD_LIBRARY}][${UDFREAD_INCLUDE_DIR}]") + endif() + + endif() -mark_as_advanced(UDFREAD_INCLUDE_DIR UDFREAD_LIBRARY) + # pkgconfig populate target that is sufficient version + if(TARGET PkgConfig::udfread AND NOT TARGET udfread_build) + add_library(udfread::udfread ALIAS PkgConfig::udfread) + set_target_properties(PkgConfig::udfread PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) + # windows cmake config populated target + elseif(TARGET libudfread::libudfread) + add_library(udfread::udfread ALIAS libudfread::libudfread) + set_target_properties(libudfread::libudfread PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) + # otherwise we are building + elseif(TARGET udfread_build) + add_library(udfread::udfread UNKNOWN IMPORTED) + set_target_properties(udfread::udfread PROPERTIES + IMPORTED_LOCATION "${UDFREAD_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${UDFREAD_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_UDFREAD=1) + add_dependencies(udfread::udfread udfread_build) + endif() + + if(TARGET udfread::udfread) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP udfread::udfread) + endif() +endif() diff --git a/xbmc/filesystem/CMakeLists.txt b/xbmc/filesystem/CMakeLists.txt index a88132b3bc95a..8d2336a347156 100644 --- a/xbmc/filesystem/CMakeLists.txt +++ b/xbmc/filesystem/CMakeLists.txt @@ -131,7 +131,7 @@ if(ISO9660PP_FOUND) ISO9660File.h) endif() -if(UDFREAD_FOUND) +if(TARGET udfread::udfread) list(APPEND SOURCES UDFBlockInput.cpp UDFDirectory.cpp UDFFile.cpp) From bd675ec817f4b2ef5e3d5ed7d55bba186f381559 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:12:18 +1000 Subject: [PATCH 162/811] [cmake] FindMariaDBClient migrate to full TARGET usage --- CMakeLists.txt | 2 +- cmake/modules/FindMariaDBClient.cmake | 96 ++++++++++++--------------- xbmc/dbwrappers/CMakeLists.txt | 2 +- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3488ab96de150..59d7d301fff14 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -232,7 +232,7 @@ elseif(ENABLE_MYSQLCLIENT AND NOT ENABLE_MYSQLCLIENT STREQUAL AUTO) endif() core_optional_dep(MariaDBClient) -if(NOT MARIADBCLIENT_FOUND) +if(NOT TARGET MariaDBClient::MariaDBClient) core_optional_dep(MySqlClient) endif() diff --git a/cmake/modules/FindMariaDBClient.cmake b/cmake/modules/FindMariaDBClient.cmake index 108b5eedef7d0..a34ac65aa3ab8 100644 --- a/cmake/modules/FindMariaDBClient.cmake +++ b/cmake/modules/FindMariaDBClient.cmake @@ -3,81 +3,73 @@ # --------------- # Finds the MariaDBClient library # -# This will define the following variables:: -# -# MARIADBCLIENT_FOUND - system has MariaDBClient -# MARIADBCLIENT_INCLUDE_DIRS - the MariaDBClient include directory -# MARIADBCLIENT_LIBRARIES - the MariaDBClient libraries -# MARIADBCLIENT_DEFINITIONS - the MariaDBClient compile definitions -# -# and the following imported targets:: +# This will define the following target: # # MariaDBClient::MariaDBClient - The MariaDBClient library -# Don't find system wide installed version on Windows -if(WIN32) - set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) -else() - set(EXTRA_FIND_ARGS) -endif() +if(NOT TARGET MariaDBClient::MariaDBClient) + # Don't find system wide installed version on Windows + if(WIN32) + set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) + else() + set(EXTRA_FIND_ARGS) + endif() -if(PKG_CONFIG_FOUND) - pkg_search_module(PC_MARIADBCLIENT libmariadb mariadb QUIET) -endif() + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_search_module(PC_MARIADBCLIENT libmariadb mariadb QUIET) + endif() -find_path(MARIADBCLIENT_INCLUDE_DIR NAMES mariadb/mysql.h mariadb/server/mysql.h - PATHS ${PC_MARIADBCLIENT_INCLUDEDIR}) -find_library(MARIADBCLIENT_LIBRARY_RELEASE NAMES mariadbclient mariadb libmariadb + find_path(MARIADBCLIENT_INCLUDE_DIR NAMES mariadb/mysql.h mariadb/server/mysql.h + PATHS ${PC_MARIADBCLIENT_INCLUDEDIR} + NO_CACHE) + find_library(MARIADBCLIENT_LIBRARY_RELEASE NAMES mariadbclient mariadb libmariadb + PATHS ${PC_MARIADBCLIENT_LIBDIR} + PATH_SUFFIXES mariadb + ${EXTRA_FIND_ARGS} + NO_CACHE) + find_library(MARIADBCLIENT_LIBRARY_DEBUG NAMES mariadbclient mariadb libmariadbd PATHS ${PC_MARIADBCLIENT_LIBDIR} PATH_SUFFIXES mariadb - ${EXTRA_FIND_ARGS}) -find_library(MARIADBCLIENT_LIBRARY_DEBUG NAMES mariadbclient mariadb libmariadbd - PATHS ${PC_MARIADBCLIENT_LIBDIR} - PATH_SUFFIXES mariadb - ${EXTRA_FIND_ARGS}) - -if(PC_MARIADBCLIENT_VERSION) - set(MARIADBCLIENT_VERSION_STRING ${PC_MARIADBCLIENT_VERSION}) -elseif(MARIADBCLIENT_INCLUDE_DIR AND EXISTS "${MARIADBCLIENT_INCLUDE_DIR}/mariadb/mariadb_version.h") - file(STRINGS "${MARIADBCLIENT_INCLUDE_DIR}/mariadb/mariadb_version.h" mariadb_version_str REGEX "^#define[\t ]+MARIADB_CLIENT_VERSION_STR[\t ]+\".*\".*") - string(REGEX REPLACE "^#define[\t ]+MARIADB_CLIENT_VERSION_STR[\t ]+\"([^\"]+)\".*" "\\1" MARIADBCLIENT_VERSION_STRING "${mariadb_version_str}") - unset(mariadb_version_str) -endif() - -include(SelectLibraryConfigurations) -select_library_configurations(MARIADBCLIENT) + ${EXTRA_FIND_ARGS} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MariaDBClient - REQUIRED_VARS MARIADBCLIENT_LIBRARY MARIADBCLIENT_INCLUDE_DIR - VERSION_VAR MARIADBCLIENT_VERSION_STRING) + if(PC_MARIADBCLIENT_VERSION) + set(MARIADBCLIENT_VERSION_STRING ${PC_MARIADBCLIENT_VERSION}) + elseif(MARIADBCLIENT_INCLUDE_DIR AND EXISTS "${MARIADBCLIENT_INCLUDE_DIR}/mariadb/mariadb_version.h") + file(STRINGS "${MARIADBCLIENT_INCLUDE_DIR}/mariadb/mariadb_version.h" mariadb_version_str REGEX "^#define[\t ]+MARIADB_CLIENT_VERSION_STR[\t ]+\".*\".*") + string(REGEX REPLACE "^#define[\t ]+MARIADB_CLIENT_VERSION_STR[\t ]+\"([^\"]+)\".*" "\\1" MARIADBCLIENT_VERSION_STRING "${mariadb_version_str}") + unset(mariadb_version_str) + endif() -if(MARIADBCLIENT_FOUND) - set(MARIADBCLIENT_LIBRARIES ${MARIADBCLIENT_LIBRARY}) - set(MARIADBCLIENT_INCLUDE_DIRS ${MARIADBCLIENT_INCLUDE_DIR}) - set(MARIADBCLIENT_DEFINITIONS -DHAS_MARIADB=1) + include(SelectLibraryConfigurations) + select_library_configurations(MARIADBCLIENT) - if(CORE_SYSTEM_NAME STREQUAL osx) - list(APPEND DEPLIBS "-lgssapi_krb5") - endif() + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MariaDBClient + REQUIRED_VARS MARIADBCLIENT_LIBRARY MARIADBCLIENT_INCLUDE_DIR + VERSION_VAR MARIADBCLIENT_VERSION_STRING) - if(NOT TARGET MariaDBClient::MariaDBClient) + if(MARIADBCLIENT_FOUND) add_library(MariaDBClient::MariaDBClient UNKNOWN IMPORTED) if(MARIADBCLIENT_LIBRARY_RELEASE) set_target_properties(MariaDBClient::MariaDBClient PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${MARIADBCLIENT_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${MARIADBCLIENT_LIBRARY_RELEASE}") endif() if(MARIADBCLIENT_LIBRARY_DEBUG) set_target_properties(MariaDBClient::MariaDBClient PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${MARIADBCLIENT_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${MARIADBCLIENT_LIBRARY_DEBUG}") endif() set_target_properties(MariaDBClient::MariaDBClient PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${MARIADBCLIENT_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_MARIADB=1) + if(CORE_SYSTEM_NAME STREQUAL osx) + target_link_libraries(MariaDBClient::MariaDBClient INTERFACE gssapi_krb5) + endif() + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MariaDBClient::MariaDBClient) endif() endif() - -mark_as_advanced(MARIADBCLIENT_INCLUDE_DIR MARIADBCLIENT_LIBRARY) diff --git a/xbmc/dbwrappers/CMakeLists.txt b/xbmc/dbwrappers/CMakeLists.txt index 16a03f291a6ce..743d5c9521e7a 100644 --- a/xbmc/dbwrappers/CMakeLists.txt +++ b/xbmc/dbwrappers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS Database.h qry_dat.h sqlitedataset.h) -if(MYSQLCLIENT_FOUND OR MARIADBCLIENT_FOUND) +if(MYSQLCLIENT_FOUND OR TARGET MariaDBClient::MariaDBClient) list(APPEND SOURCES mysqldataset.cpp) list(APPEND HEADERS mysqldataset.h) endif() From f4d5af82ff602407a00ccf62200eea3f6b977d37 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:15:28 +1000 Subject: [PATCH 163/811] [cmake] FindMySqlClient migrate to full TARGET usage --- cmake/modules/FindMySqlClient.cmake | 73 +++++++++++++---------------- xbmc/dbwrappers/CMakeLists.txt | 2 +- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/cmake/modules/FindMySqlClient.cmake b/cmake/modules/FindMySqlClient.cmake index 05d3f570c5936..04530912f8d43 100644 --- a/cmake/modules/FindMySqlClient.cmake +++ b/cmake/modules/FindMySqlClient.cmake @@ -3,67 +3,58 @@ # --------------- # Finds the MySqlClient library # -# This will define the following variables:: -# -# MYSQLCLIENT_FOUND - system has MySqlClient -# MYSQLCLIENT_INCLUDE_DIRS - the MySqlClient include directory -# MYSQLCLIENT_LIBRARIES - the MySqlClient libraries -# MYSQLCLIENT_DEFINITIONS - the MySqlClient compile definitions -# -# and the following imported targets:: +# This will define the following target: # # MySqlClient::MySqlClient - The MySqlClient library -# Don't find system wide installed version on Windows -if(WIN32) - set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) -else() - set(EXTRA_FIND_ARGS) -endif() +if(NOT TARGET MySqlClient::MySqlClient) + # Don't find system wide installed version on Windows + if(WIN32) + set(EXTRA_FIND_ARGS NO_SYSTEM_ENVIRONMENT_PATH) + else() + set(EXTRA_FIND_ARGS) + endif() -find_path(MYSQLCLIENT_INCLUDE_DIR NAMES mysql/mysql.h mysql/server/mysql.h) -find_library(MYSQLCLIENT_LIBRARY_RELEASE NAMES mysqlclient libmysql + find_path(MYSQLCLIENT_INCLUDE_DIR NAMES mysql/mysql.h mysql/server/mysql.h + NO_CACHE) + find_library(MYSQLCLIENT_LIBRARY_RELEASE NAMES mysqlclient libmysql + PATH_SUFFIXES mysql + ${EXTRA_FIND_ARGS} + NO_CACHE) + find_library(MYSQLCLIENT_LIBRARY_DEBUG NAMES mysqlclient libmysql PATH_SUFFIXES mysql - ${EXTRA_FIND_ARGS}) -find_library(MYSQLCLIENT_LIBRARY_DEBUG NAMES mysqlclient libmysql - PATH_SUFFIXES mysql - ${EXTRA_FIND_ARGS}) + ${EXTRA_FIND_ARGS} + NO_CACHE) -if(MYSQLCLIENT_INCLUDE_DIR AND EXISTS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h") - file(STRINGS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h" mysql_version_str REGEX "^#define[\t ]+LIBMYSQL_VERSION[\t ]+\".*\".*") - string(REGEX REPLACE "^#define[\t ]+LIBMYSQL_VERSION[\t ]+\"([^\"]+)\".*" "\\1" MYSQLCLIENT_VERSION_STRING "${mysql_version_str}") - unset(mysql_version_str) -endif() - -include(SelectLibraryConfigurations) -select_library_configurations(MYSQLCLIENT) + if(MYSQLCLIENT_INCLUDE_DIR AND EXISTS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h") + file(STRINGS "${MYSQLCLIENT_INCLUDE_DIR}/mysql/mysql_version.h" mysql_version_str REGEX "^#define[\t ]+LIBMYSQL_VERSION[\t ]+\".*\".*") + string(REGEX REPLACE "^#define[\t ]+LIBMYSQL_VERSION[\t ]+\"([^\"]+)\".*" "\\1" MYSQLCLIENT_VERSION_STRING "${mysql_version_str}") + unset(mysql_version_str) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(MySqlClient - REQUIRED_VARS MYSQLCLIENT_LIBRARY MYSQLCLIENT_INCLUDE_DIR - VERSION_VAR MYSQLCLIENT_VERSION_STRING) + include(SelectLibraryConfigurations) + select_library_configurations(MYSQLCLIENT) -if(MYSQLCLIENT_FOUND) - set(MYSQLCLIENT_LIBRARIES ${MYSQLCLIENT_LIBRARY}) - set(MYSQLCLIENT_INCLUDE_DIRS ${MYSQLCLIENT_INCLUDE_DIR}) - set(MYSQLCLIENT_DEFINITIONS -DHAS_MYSQL=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(MySqlClient + REQUIRED_VARS MYSQLCLIENT_LIBRARY MYSQLCLIENT_INCLUDE_DIR + VERSION_VAR MYSQLCLIENT_VERSION_STRING) - if(NOT TARGET MySqlClient::MySqlClient) + if(MYSQLCLIENT_FOUND) add_library(MySqlClient::MySqlClient UNKNOWN IMPORTED) if(MYSQLCLIENT_LIBRARY_RELEASE) set_target_properties(MySqlClient::MySqlClient PROPERTIES IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION "${MYSQLCLIENT_LIBRARY_RELEASE}") + IMPORTED_LOCATION_RELEASE "${MYSQLCLIENT_LIBRARY_RELEASE}") endif() if(MYSQLCLIENT_LIBRARY_DEBUG) set_target_properties(MySqlClient::MySqlClient PROPERTIES IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION "${MYSQLCLIENT_LIBRARY_DEBUG}") + IMPORTED_LOCATION_DEBUG "${MYSQLCLIENT_LIBRARY_DEBUG}") endif() set_target_properties(MySqlClient::MySqlClient PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${MYSQLCLIENT_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_MYSQL=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP MySqlClient::MySqlClient) endif() endif() - -mark_as_advanced(MYSQLCLIENT_INCLUDE_DIR MYSQLCLIENT_LIBRARY) diff --git a/xbmc/dbwrappers/CMakeLists.txt b/xbmc/dbwrappers/CMakeLists.txt index 743d5c9521e7a..2c5f0f8f09645 100644 --- a/xbmc/dbwrappers/CMakeLists.txt +++ b/xbmc/dbwrappers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS Database.h qry_dat.h sqlitedataset.h) -if(MYSQLCLIENT_FOUND OR TARGET MariaDBClient::MariaDBClient) +if(TARGET MySqlClient::MySqlClient OR TARGET MariaDBClient::MariaDBClient) list(APPEND SOURCES mysqldataset.cpp) list(APPEND HEADERS mysqldataset.h) endif() From e0d72358d46fd7bbfb5d9c3deeec26664d126b8e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:18:58 +1000 Subject: [PATCH 164/811] [cmake] FindSndio migrate to full TARGET usage --- cmake/modules/FindSndio.cmake | 41 +++++++++------------------ xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 2 files changed, 15 insertions(+), 28 deletions(-) diff --git a/cmake/modules/FindSndio.cmake b/cmake/modules/FindSndio.cmake index eda502ff4c376..c8c7161bff490 100644 --- a/cmake/modules/FindSndio.cmake +++ b/cmake/modules/FindSndio.cmake @@ -3,41 +3,28 @@ # --------- # Finds the Sndio Library # -# This will define the following variables: +# This will define the following target: # -# SNDIO_FOUND - system has sndio -# SNDIO_INCLUDE_DIRS - sndio include directory -# SNDIO_DEFINITIONS - sndio definitions +# Sndio::Sndio - the sndio library # -# and the following imported targets:: -# -# Sndio::Sndio - the sndio library -# - -find_path(SNDIO_INCLUDE_DIR sndio.h) -find_library(SNDIO_LIBRARY sndio) +if(NOT TARGET Sndio::Sndio) + find_path(SNDIO_INCLUDE_DIR sndio.h NO_CACHE) + find_library(SNDIO_LIBRARY sndio NO_CACHE) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Sndio + REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIR) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Sndio - REQUIRED_VARS SNDIO_LIBRARY SNDIO_INCLUDE_DIR) + if(SNDIO_FOUND) + list(APPEND AUDIO_BACKENDS_LIST "sndio") + set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) -if(SNDIO_FOUND) - set(SNDIO_INCLUDE_DIRS ${SNDIO_INCLUDE_DIR}) - set(SNDIO_LIBRARIES ${SNDIO_LIBRARY}) - set(SNDIO_DEFINITIONS -DHAS_SNDIO=1) - list(APPEND AUDIO_BACKENDS_LIST "sndio") - set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) - if(NOT TARGET Sndio::Sndio) add_library(Sndio::Sndio UNKNOWN IMPORTED) set_target_properties(Sndio::Sndio PROPERTIES IMPORTED_LOCATION "${SNDIO_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${SNDIO_INCLUDE_DIR}") - set_target_properties(Sndio::Sndio PROPERTIES - INTERFACE_COMPILE_DEFINITIONS -DHAS_SNDIO=1) + INTERFACE_INCLUDE_DIRECTORIES "${SNDIO_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_SNDIO=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Sndio::Sndio) endif() endif() - - -mark_as_advanced(SNDIO_INCLUDE_DIR SNDIO_LIBRARY) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 18425cc58bf84..b41ea30e50260 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -91,7 +91,7 @@ if(PIPEWIRE_FOUND) Sinks/pipewire/PipewireThreadLoop.h) endif() -if(SNDIO_FOUND) +if(TARGET Sndio::Sndio) list(APPEND SOURCES Sinks/AESinkSNDIO.cpp) list(APPEND HEADERS Sinks/AESinkSNDIO.h) endif() From 18e73c6e8c6a887882ff51784383f871f9981627 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:21:26 +1000 Subject: [PATCH 165/811] [cmake] missed ffmpeg::ffmpeg TARGET check for source include --- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index b41ea30e50260..07a00751675ac 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -96,7 +96,7 @@ if(TARGET Sndio::Sndio) list(APPEND HEADERS Sinks/AESinkSNDIO.h) endif() -if(FFMPEG_FOUND) +if(TARGET ffmpeg::ffmpeg) list(APPEND SOURCES Engines/ActiveAE/ActiveAEResampleFFMPEG.cpp) list(APPEND HEADERS Engines/ActiveAE/ActiveAEResampleFFMPEG.h) endif() From 9f075a723900aa2d9be1e81e40b4503829d4d2b0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:33:37 +1000 Subject: [PATCH 166/811] [cmake] FindBluetooth migrate fully to TARGET usage --- cmake/modules/FindBluetooth.cmake | 48 +++++++++++--------------- cmake/scripts/linux/ExtraTargets.cmake | 2 +- cmake/scripts/linux/Install.cmake | 2 +- xbmc/network/CMakeLists.txt | 3 -- 4 files changed, 23 insertions(+), 32 deletions(-) diff --git a/cmake/modules/FindBluetooth.cmake b/cmake/modules/FindBluetooth.cmake index 7ca99bb32e759..0ac691324eb34 100644 --- a/cmake/modules/FindBluetooth.cmake +++ b/cmake/modules/FindBluetooth.cmake @@ -3,42 +3,36 @@ # --------- # Finds the Bluetooth library # -# This will define the following variables:: -# -# BLUETOOTH_FOUND - system has Bluetooth -# BLUETOOTH_INCLUDE_DIRS - the Bluetooth include directory -# BLUETOOTH_LIBRARIES - the Bluetooth libraries -# -# and the following imported targets:: +# This will define the following target: # # Bluetooth::Bluetooth - The Bluetooth library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_BLUETOOTH bluez bluetooth QUIET) -endif() - -find_path(BLUETOOTH_INCLUDE_DIR NAMES bluetooth/bluetooth.h - PATHS ${PC_BLUETOOTH_INCLUDEDIR}) -find_library(BLUETOOTH_LIBRARY NAMES bluetooth libbluetooth - PATHS ${PC_BLUETOOTH_LIBDIR}) +if(NOT TARGET Bluetooth::Bluetooth) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_BLUETOOTH bluez bluetooth QUIET) + endif() -set(BLUETOOTH_VERSION ${PC_BLUETOOTH_VERSION}) + find_path(BLUETOOTH_INCLUDE_DIR NAMES bluetooth/bluetooth.h + PATHS ${PC_BLUETOOTH_INCLUDEDIR} + NO_CACHE) + find_library(BLUETOOTH_LIBRARY NAMES bluetooth libbluetooth + PATHS ${PC_BLUETOOTH_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Bluetooth - REQUIRED_VARS BLUETOOTH_LIBRARY BLUETOOTH_INCLUDE_DIR - VERSION_VAR BLUETOOTH_VERSION) + set(BLUETOOTH_VERSION ${PC_BLUETOOTH_VERSION}) -if(BLUETOOTH_FOUND) - set(BLUETOOTH_INCLUDE_DIRS ${BLUETOOTH_INCLUDE_DIR}) - set(BLUETOOTH_LIBRARIES ${BLUETOOTH_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Bluetooth + REQUIRED_VARS BLUETOOTH_LIBRARY BLUETOOTH_INCLUDE_DIR + VERSION_VAR BLUETOOTH_VERSION) - if(NOT TARGET Bluetooth::Bluetooth) + if(BLUETOOTH_FOUND) add_library(Bluetooth::Bluetooth UNKNOWN IMPORTED) set_target_properties(Bluetooth::Bluetooth PROPERTIES IMPORTED_LOCATION "${BLUETOOTH_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${BLUETOOTH_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${BLUETOOTH_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBBLUETOOTH=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Bluetooth::Bluetooth) endif() endif() - -mark_as_advanced(BLUETOOTH_INCLUDE_DIR BLUETOOTH_LIBRARY) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 62c6a400b136b..548083a5d4a39 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -7,7 +7,7 @@ if(X_FOUND AND XRANDR_FOUND) endif() # WiiRemote -if(ENABLE_EVENTCLIENTS AND BLUETOOTH_FOUND) +if(ENABLE_EVENTCLIENTS AND TARGET Bluetooth::Bluetooth) find_package(CWiid QUIET) find_package(GLU QUIET) if(CWIID_FOUND AND GLU_FOUND) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index af06021b9f19e..f15026e3b5b51 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -280,7 +280,7 @@ if(ENABLE_EVENTCLIENTS) DESTINATION ${bindir} COMPONENT kodi-eventclients-ps3) - if(BLUETOOTH_FOUND AND CWIID_FOUND AND GLU_FOUND) + if(TARGET Bluetooth::Bluetooth AND CWIID_FOUND AND GLU_FOUND) # Install kodi-eventclients-wiiremote install(PROGRAMS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/WiiRemote/${APP_NAME_LC}-wiiremote DESTINATION ${bindir} diff --git a/xbmc/network/CMakeLists.txt b/xbmc/network/CMakeLists.txt index 50757a6c89d86..147af290b685e 100644 --- a/xbmc/network/CMakeLists.txt +++ b/xbmc/network/CMakeLists.txt @@ -51,9 +51,6 @@ if(MICROHTTPD_FOUND) endif() core_add_library(network) -if(BLUETOOTH_FOUND) - target_compile_definitions(${CORE_LIBRARY} PRIVATE -DHAVE_LIBBLUETOOTH=1) -endif() if(ENABLE_STATIC_LIBS AND ENABLE_UPNP) target_link_libraries(${CORE_LIBRARY} PRIVATE upnp) From 7f33c6e125407edb03bf10ebd19645baa8847f22 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:36:19 +1000 Subject: [PATCH 167/811] [cmake] FindXRandR migrate to full TARGET usage --- cmake/modules/FindXRandR.cmake | 48 +++++++++++--------------- cmake/scripts/linux/ExtraTargets.cmake | 4 +-- cmake/scripts/linux/Install.cmake | 2 +- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/cmake/modules/FindXRandR.cmake b/cmake/modules/FindXRandR.cmake index 12f3478107baf..7a8f1816eccf6 100644 --- a/cmake/modules/FindXRandR.cmake +++ b/cmake/modules/FindXRandR.cmake @@ -3,45 +3,39 @@ # ---------- # Finds the XRandR library # -# This will define the following variables:: -# -# XRANDR_FOUND - system has XRANDR -# XRANDR_INCLUDE_DIRS - the XRANDR include directory -# XRANDR_LIBRARIES - the XRANDR libraries -# XRANDR_DEFINITIONS - the XRANDR definitions -# -# and the following imported targets:: +# This will define the following target: # # XRandR::XRandR - The XRANDR library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_XRANDR xrandr QUIET) -endif() +if(NOT TARGET XRandR::XRandR) -find_path(XRANDR_INCLUDE_DIR NAMES X11/extensions/Xrandr.h - PATHS ${PC_XRANDR_INCLUDEDIR}) -find_library(XRANDR_LIBRARY NAMES Xrandr - PATHS ${PC_XRANDR_LIBDIR}) + find_package(PkgConfig) -set(XRANDR_VERSION ${PC_XRANDR_VERSION}) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_XRANDR xrandr QUIET) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(XRandR - REQUIRED_VARS XRANDR_LIBRARY XRANDR_INCLUDE_DIR - VERSION_VAR XRANDR_VERSION) + find_path(XRANDR_INCLUDE_DIR NAMES X11/extensions/Xrandr.h + PATHS ${PC_XRANDR_INCLUDEDIR} + NO_CACHE) + find_library(XRANDR_LIBRARY NAMES Xrandr + PATHS ${PC_XRANDR_LIBDIR} + NO_CACHE) -if(XRANDR_FOUND) - set(XRANDR_LIBRARIES ${XRANDR_LIBRARY}) - set(XRANDR_INCLUDE_DIRS ${XRANDR_INCLUDE_DIR}) - set(XRANDR_DEFINITIONS -DHAVE_LIBXRANDR=1) + set(XRANDR_VERSION ${PC_XRANDR_VERSION}) - if(NOT TARGET XRandR::XRandR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(XRandR + REQUIRED_VARS XRANDR_LIBRARY XRANDR_INCLUDE_DIR + VERSION_VAR XRANDR_VERSION) + + if(XRANDR_FOUND) add_library(XRandR::XRandR UNKNOWN IMPORTED) set_target_properties(XRandR::XRandR PROPERTIES IMPORTED_LOCATION "${XRANDR_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${XRANDR_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBXRANDR=1) + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XRandR::XRandR) endif() endif() - -mark_as_advanced(XRANDR_INCLUDE_DIR XRANDR_LIBRARY) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index 548083a5d4a39..aa8d6182aa444 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -1,9 +1,9 @@ # xrandr -if(X_FOUND AND XRANDR_FOUND) +if(X_FOUND AND TARGET XRandR::XRandR) find_package(X QUIET) find_package(XRandR QUIET) add_executable(${APP_NAME_LC}-xrandr ${CMAKE_SOURCE_DIR}/xbmc-xrandr.c) - target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${X_LIBRARIES} m ${XRANDR_LIBRARIES}) + target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${X_LIBRARIES} m XRandR::XRandR) endif() # WiiRemote diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index f15026e3b5b51..b8a94fa73cc7c 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -59,7 +59,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.metainfo.xml.in install(TARGETS ${APP_NAME_LC} DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) -if(X_FOUND AND XRANDR_FOUND) +if(X_FOUND AND TARGET XRandR::XRandR) install(TARGETS ${APP_NAME_LC}-xrandr DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) From 7c0c7f5bd1485ee84d3a599be81e7ba4b78d03c4 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:45:51 +1000 Subject: [PATCH 168/811] [cmake] FindX migrate to full TARGET usage --- cmake/modules/FindX.cmake | 67 +++++++++++--------------- cmake/scripts/linux/ExtraTargets.cmake | 4 +- cmake/scripts/linux/Install.cmake | 2 +- 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/cmake/modules/FindX.cmake b/cmake/modules/FindX.cmake index a52f86f912dc5..8f19b92178746 100644 --- a/cmake/modules/FindX.cmake +++ b/cmake/modules/FindX.cmake @@ -3,55 +3,46 @@ # ----- # Finds the X11 library # -# This will define the following variables:: -# -# X_FOUND - system has X11 -# X_INCLUDE_DIRS - the X11 include directory -# X_LIBRARIES - the X11 libraries -# X_DEFINITIONS - the X11 definitions -# -# and the following imported targets:: +# This will define the following targets: # # X::X - The X11 library # X::Xext - The X11 extension library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_X x11 xext QUIET) -endif() - -find_path(X_INCLUDE_DIR NAMES X11/Xlib.h - PATHS ${PC_X_x11_INCLUDEDIR}) -find_library(X_LIBRARY NAMES X11 - PATHS ${PC_X_x11_LIBDIR}) -find_library(X_EXT_LIBRARY NAMES Xext - PATHS ${PC_X_xext_LIBDIR}) +if(NOT TARGET X::X) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_X x11 xext QUIET) + endif() -set(X_VERSION ${PC_X_x11_VERSION}) + find_path(X_INCLUDE_DIR NAMES X11/Xlib.h + PATHS ${PC_X_x11_INCLUDEDIR} + NO_CACHE) + find_library(X_LIBRARY NAMES X11 + PATHS ${PC_X_x11_LIBDIR} + NO_CACHE) + find_library(X_EXT_LIBRARY NAMES Xext + PATHS ${PC_X_xext_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(X - REQUIRED_VARS X_LIBRARY X_EXT_LIBRARY X_INCLUDE_DIR - VERSION_VAR X_VERSION) + set(X_VERSION ${PC_X_x11_VERSION}) -if(X_FOUND) - set(X_LIBRARIES ${X_LIBRARY} ${X_EXT_LIBRARY}) - set(X_INCLUDE_DIRS ${X_INCLUDE_DIR}) - set(X_DEFINITIONS -DHAVE_X11=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(X + REQUIRED_VARS X_LIBRARY X_EXT_LIBRARY X_INCLUDE_DIR + VERSION_VAR X_VERSION) - if(NOT TARGET X::X) + if(X_FOUND) + add_library(X::Xext UNKNOWN IMPORTED) + set_target_properties(X::Xext PROPERTIES + IMPORTED_LOCATION "${X_EXT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}") add_library(X::X UNKNOWN IMPORTED) set_target_properties(X::X PROPERTIES IMPORTED_LOCATION "${X_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_X11=1) - endif() - if(NOT TARGET X::Xext) - add_library(X::Xext UNKNOWN IMPORTED) - set_target_properties(X::Xext PROPERTIES - IMPORTED_LOCATION "${X_EXT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${X_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES X::X) + INTERFACE_COMPILE_DEFINITIONS HAVE_X11=1 + INTERFACE_LINK_LIBRARIES X::Xext) + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP X::X) endif() endif() - -mark_as_advanced(X_INCLUDE_DIR X_LIBRARY X_EXT_LIBRARY) diff --git a/cmake/scripts/linux/ExtraTargets.cmake b/cmake/scripts/linux/ExtraTargets.cmake index aa8d6182aa444..ee13ae6f08cb6 100644 --- a/cmake/scripts/linux/ExtraTargets.cmake +++ b/cmake/scripts/linux/ExtraTargets.cmake @@ -1,9 +1,9 @@ # xrandr -if(X_FOUND AND TARGET XRandR::XRandR) +if(TARGET X::X AND TARGET XRandR::XRandR) find_package(X QUIET) find_package(XRandR QUIET) add_executable(${APP_NAME_LC}-xrandr ${CMAKE_SOURCE_DIR}/xbmc-xrandr.c) - target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} ${X_LIBRARIES} m XRandR::XRandR) + target_link_libraries(${APP_NAME_LC}-xrandr ${SYSTEM_LDFLAGS} X::X m XRandR::XRandR) endif() # WiiRemote diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index b8a94fa73cc7c..3f6c7ac6dac01 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -59,7 +59,7 @@ configure_file(${CMAKE_SOURCE_DIR}/tools/Linux/kodi.metainfo.xml.in install(TARGETS ${APP_NAME_LC} DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) -if(X_FOUND AND TARGET XRandR::XRandR) +if(TARGET X::X AND TARGET XRandR::XRandR) install(TARGETS ${APP_NAME_LC}-xrandr DESTINATION ${libdir}/${APP_NAME_LC} COMPONENT kodi-bin) From a56173230a87579945db044e91d68642b88e7e69 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:47:26 +1000 Subject: [PATCH 169/811] [cmake] FindCAP migrate to full TARGET usage --- cmake/modules/FindCAP.cmake | 48 ++++++++++++++++--------------------- xbmc/CMakeLists.txt | 3 --- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/cmake/modules/FindCAP.cmake b/cmake/modules/FindCAP.cmake index 04e8378ea4ff3..2f11161b45ff3 100644 --- a/cmake/modules/FindCAP.cmake +++ b/cmake/modules/FindCAP.cmake @@ -3,42 +3,36 @@ # ----------- # Finds the POSIX 1003.1e capabilities library # -# This will define the following variables:: -# -# CAP_FOUND - system has LibCap -# CAP_INCLUDE_DIRS - the LibCap include directory -# CAP_LIBRARIES - the LibCap libraries -# -# and the following imported targets:: +# This will define the following target: # # CAP::CAP - The LibCap library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_CAP libcap QUIET) -endif() - -find_path(CAP_INCLUDE_DIR NAMES sys/capability.h - PATHS ${PC_CAP_INCLUDEDIR}) -find_library(CAP_LIBRARY NAMES cap libcap - PATHS ${PC_CAP_LIBDIR}) +if(NOT TARGET CAP::CAP) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_CAP libcap QUIET) + endif() -set(CAP_VERSION ${PC_CAP_VERSION}) + find_path(CAP_INCLUDE_DIR NAMES sys/capability.h + PATHS ${PC_CAP_INCLUDEDIR} + NO_CACHE) + find_library(CAP_LIBRARY NAMES cap libcap + PATHS ${PC_CAP_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(CAP - REQUIRED_VARS CAP_LIBRARY CAP_INCLUDE_DIR - VERSION_VAR CAP_VERSION) + set(CAP_VERSION ${PC_CAP_VERSION}) -if(CAP_FOUND) - set(CAP_LIBRARIES ${CAP_LIBRARY}) - set(CAP_INCLUDE_DIRS ${CAP_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(CAP + REQUIRED_VARS CAP_LIBRARY CAP_INCLUDE_DIR + VERSION_VAR CAP_VERSION) - if(NOT TARGET CAP::CAP) + if(CAP_FOUND) add_library(CAP::CAP UNKNOWN IMPORTED) set_target_properties(CAP::CAP PROPERTIES IMPORTED_LOCATION "${CAP_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${CAP_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${CAP_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAVE_LIBCAP=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CAP::CAP) endif() endif() - -mark_as_advanced(CAP_INCLUDE_DIR CAP_LIBRARY) diff --git a/xbmc/CMakeLists.txt b/xbmc/CMakeLists.txt index 4c69707dd5f9d..b7c838b3da56a 100644 --- a/xbmc/CMakeLists.txt +++ b/xbmc/CMakeLists.txt @@ -82,9 +82,6 @@ if(ENABLE_OPTICAL) endif() core_add_library(xbmc) -if(CAP_FOUND) - target_compile_definitions(${CORE_LIBRARY} PRIVATE -DHAVE_LIBCAP=1) -endif() if(CORE_SYSTEM_NAME STREQUAL windowsstore) set_target_properties(${CORE_LIBRARY} PROPERTIES STATIC_LIBRARY_FLAGS "/ignore:4264") From cbd4a760e4e58091c539ddfd675fd1330e89436c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:54:18 +1000 Subject: [PATCH 170/811] [cmake] FindEGL migrate to TARGET usage --- cmake/modules/FindEGL.cmake | 59 ++++++++----------- xbmc/cores/RetroPlayer/buffers/CMakeLists.txt | 2 +- .../rendering/VideoRenderers/CMakeLists.txt | 2 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 2 +- xbmc/utils/CMakeLists.txt | 4 +- xbmc/windowing/linux/CMakeLists.txt | 2 +- xbmc/windowing/wayland/CMakeLists.txt | 2 +- 7 files changed, 33 insertions(+), 40 deletions(-) diff --git a/cmake/modules/FindEGL.cmake b/cmake/modules/FindEGL.cmake index a68a8db901222..7c048a24f3759 100644 --- a/cmake/modules/FindEGL.cmake +++ b/cmake/modules/FindEGL.cmake @@ -3,52 +3,45 @@ # ------- # Finds the EGL library # -# This will define the following variables:: -# -# EGL_FOUND - system has EGL -# EGL_INCLUDE_DIRS - the EGL include directory -# EGL_LIBRARIES - the EGL libraries -# EGL_DEFINITIONS - the EGL definitions -# HAVE_EGLEXTANGLE - if eglext_angle.h exists else use eglextchromium.h -# -# and the following imported targets:: +# This will define the following target: # # EGL::EGL - The EGL library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_EGL egl QUIET) -endif() +if(NOT TARGET EGL::EGL) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_EGL egl QUIET) + endif() -find_path(EGL_INCLUDE_DIR EGL/egl.h - PATHS ${PC_EGL_INCLUDEDIR}) + find_path(EGL_INCLUDE_DIR EGL/egl.h + PATHS ${PC_EGL_INCLUDEDIR} + NO_CACHE) -find_library(EGL_LIBRARY NAMES EGL egl - PATHS ${PC_EGL_LIBDIR}) + find_library(EGL_LIBRARY NAMES EGL egl + PATHS ${PC_EGL_LIBDIR} + NO_CACHE) -set(EGL_VERSION ${PC_EGL_VERSION}) + set(EGL_VERSION ${PC_EGL_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(EGL - REQUIRED_VARS EGL_LIBRARY EGL_INCLUDE_DIR - VERSION_VAR EGL_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(EGL + REQUIRED_VARS EGL_LIBRARY EGL_INCLUDE_DIR + VERSION_VAR EGL_VERSION) -if(EGL_FOUND) - set(EGL_LIBRARIES ${EGL_LIBRARY}) - set(EGL_INCLUDE_DIRS ${EGL_INCLUDE_DIR}) - set(EGL_DEFINITIONS -DHAS_EGL=1) - include(CheckIncludeFiles) - check_include_files("EGL/egl.h;EGL/eglext.h;EGL/eglext_angle.h" HAVE_EGLEXTANGLE) - if(HAVE_EGLEXTANGLE) - list(APPEND EGL_DEFINITIONS "-DHAVE_EGLEXTANGLE=1") - endif() + if(EGL_FOUND) + include(CheckIncludeFiles) + check_include_files("EGL/egl.h;EGL/eglext.h;EGL/eglext_angle.h" HAVE_EGLEXTANGLE) - if(NOT TARGET EGL::EGL) add_library(EGL::EGL UNKNOWN IMPORTED) set_target_properties(EGL::EGL PROPERTIES IMPORTED_LOCATION "${EGL_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${EGL_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAS_EGL=1) + + if(HAVE_EGLEXTANGLE) + set_property(TARGET EGL::EGL APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAVE_EGLEXTANGLE=1) + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP EGL::EGL) endif() endif() - -mark_as_advanced(EGL_INCLUDE_DIR EGL_LIBRARY) diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt index 6f770f5bab5f5..c2702248158f4 100644 --- a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt @@ -24,7 +24,7 @@ if(OPENGL_FOUND) RenderBufferPoolOpenGL.h) endif() -if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND EGL_FOUND) +if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND TARGET EGL::EGL) list(APPEND SOURCES RenderBufferDMA.cpp RenderBufferPoolDMA.cpp) list(APPEND HEADERS RenderBufferDMA.h diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt index 6f9d9086ccb7a..ce9553852f305 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt @@ -18,7 +18,7 @@ if(OPENGL_FOUND) list(APPEND HEADERS RPRendererOpenGL.h) endif() -if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND EGL_FOUND) +if(("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_LC) AND TARGET EGL::EGL) list(APPEND SOURCES RPRendererDMA.cpp) list(APPEND HEADERS RPRendererDMA.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index 64453373ad0ba..54d04c33f2069 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -14,7 +14,7 @@ if(VAAPI_FOUND) list(APPEND SOURCES RendererVAAPIGLES.cpp) list(APPEND HEADERS RendererVAAPIGLES.h) endif() - if(EGL_FOUND) + if(TARGET EGL::EGL) list(APPEND SOURCES VaapiEGL.cpp) list(APPEND HEADERS VaapiEGL.h) endif() diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 073d6c450de05..fd06507015fef 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -187,7 +187,7 @@ if(TARGET XSLT::XSLT) list(APPEND SOURCES XSLTUtils.cpp) list(APPEND HEADERS XSLTUtils.h) endif() -if(EGL_FOUND) +if(TARGET EGL::EGL) list(APPEND SOURCES EGLUtils.cpp EGLFence.cpp) list(APPEND HEADERS EGLUtils.h @@ -230,7 +230,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L list(APPEND HEADERS GBMBufferObject.h) endif() - if(EGL_FOUND) + if(TARGET EGL::EGL) list(APPEND SOURCES EGLImage.cpp) list(APPEND HEADERS EGLImage.h) endif() diff --git a/xbmc/windowing/linux/CMakeLists.txt b/xbmc/windowing/linux/CMakeLists.txt index 7b002c22dac51..b6349047d3332 100644 --- a/xbmc/windowing/linux/CMakeLists.txt +++ b/xbmc/windowing/linux/CMakeLists.txt @@ -6,7 +6,7 @@ if(TARGET DBus::DBus) list(APPEND HEADERS OSScreenSaverFreedesktop.h) endif() -if(EGL_FOUND) +if(TARGET EGL::EGL) list(APPEND SOURCES WinSystemEGL.cpp) list(APPEND HEADERS WinSystemEGL.h) endif() diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt index 4f12018ec2181..8d3e01518b11e 100644 --- a/xbmc/windowing/wayland/CMakeLists.txt +++ b/xbmc/windowing/wayland/CMakeLists.txt @@ -49,7 +49,7 @@ set(HEADERS Connection.h WinSystemWayland.h XkbcommonKeymap.h) -if(EGL_FOUND) +if(TARGET EGL::EGL) list(APPEND SOURCES WinSystemWaylandEGLContext.cpp) list(APPEND HEADERS WinSystemWaylandEGLContext.h) endif() From d42ee77ffb489f7165fd3cc64dbc784c4a5b48c3 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:58:33 +1000 Subject: [PATCH 171/811] [cmake] FindLibDRM migrate to full TARGET usage --- cmake/modules/FindLibDRM.cmake | 100 ++++++++++++++++----------------- xbmc/utils/CMakeLists.txt | 2 +- 2 files changed, 48 insertions(+), 54 deletions(-) diff --git a/cmake/modules/FindLibDRM.cmake b/cmake/modules/FindLibDRM.cmake index f3ad769de6fd7..efb7003c97b7c 100644 --- a/cmake/modules/FindLibDRM.cmake +++ b/cmake/modules/FindLibDRM.cmake @@ -3,65 +3,59 @@ # ---------- # Finds the LibDRM library # -# This will define the following variables:: -# -# LIBDRM_FOUND - system has LibDRM -# LIBDRM_INCLUDE_DIRS - the LibDRM include directory -# LIBDRM_LIBRARIES - the LibDRM libraries -# LIBDRM_DEFINITIONS - the LibDRM definitions -# -# and the following imported targets:: +# This will define the following target: # # LibDRM::LibDRM - The LibDRM library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LIBDRM libdrm>=2.4.95 QUIET) -endif() - -find_path(LIBDRM_INCLUDE_DIR NAMES drm.h - PATH_SUFFIXES libdrm drm - PATHS ${PC_LIBDRM_INCLUDEDIR}) -find_library(LIBDRM_LIBRARY NAMES drm - PATHS ${PC_LIBDRM_LIBDIR}) - -set(LIBDRM_VERSION ${PC_LIBDRM_VERSION}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LibDRM - REQUIRED_VARS LIBDRM_LIBRARY LIBDRM_INCLUDE_DIR - VERSION_VAR LIBDRM_VERSION) - -include(CheckCSourceCompiles) -set(CMAKE_REQUIRED_INCLUDES ${LIBDRM_INCLUDE_DIR}) -check_c_source_compiles("#include - - int main() - { - struct hdr_output_metadata test; - return test.metadata_type; - } - " LIBDRM_HAS_HDR_OUTPUT_METADATA) - -include(CheckSymbolExists) -set(CMAKE_REQUIRED_LIBRARIES ${LIBDRM_LIBRARY}) -check_symbol_exists(drmGetFormatModifierName xf86drm.h LIBDRM_HAS_MODIFIER_NAME) - -if(LIBDRM_FOUND) - set(LIBDRM_LIBRARIES ${LIBDRM_LIBRARY}) - set(LIBDRM_INCLUDE_DIRS ${LIBDRM_INCLUDE_DIR}) - if(LIBDRM_HAS_HDR_OUTPUT_METADATA) - set(LIBDRM_DEFINITIONS -DHAVE_HDR_OUTPUT_METADATA=1) - endif() - if(LIBDRM_HAS_MODIFIER_NAME) - list(APPEND LIBDRM_DEFINITIONS -DHAVE_DRM_MODIFIER_NAME=1) +if(NOT TARGET LIBDRM::LIBDRM) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LIBDRM libdrm>=2.4.95 QUIET) endif() - if(NOT TARGET LIBDRM::LIBDRM) + find_path(LIBDRM_INCLUDE_DIR NAMES drm.h + PATH_SUFFIXES libdrm drm + PATHS ${PC_LIBDRM_INCLUDEDIR} + NO_CACHE) + find_library(LIBDRM_LIBRARY NAMES drm + PATHS ${PC_LIBDRM_LIBDIR} + NO_CACHE) + + set(LIBDRM_VERSION ${PC_LIBDRM_VERSION}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LibDRM + REQUIRED_VARS LIBDRM_LIBRARY LIBDRM_INCLUDE_DIR + VERSION_VAR LIBDRM_VERSION) + + include(CheckCSourceCompiles) + set(CMAKE_REQUIRED_INCLUDES ${LIBDRM_INCLUDE_DIR}) + check_c_source_compiles("#include + + int main() + { + struct hdr_output_metadata test; + return test.metadata_type; + } + " LIBDRM_HAS_HDR_OUTPUT_METADATA) + + include(CheckSymbolExists) + set(CMAKE_REQUIRED_LIBRARIES ${LIBDRM_LIBRARY}) + check_symbol_exists(drmGetFormatModifierName xf86drm.h LIBDRM_HAS_MODIFIER_NAME) + + if(LIBDRM_FOUND) add_library(LIBDRM::LIBDRM UNKNOWN IMPORTED) set_target_properties(LIBDRM::LIBDRM PROPERTIES - IMPORTED_LOCATION "${LIBDRM_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${LIBDRM_INCLUDE_DIR}") + IMPORTED_LOCATION "${LIBDRM_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${LIBDRM_INCLUDE_DIR}") + if(LIBDRM_HAS_HDR_OUTPUT_METADATA) + set_property(TARGET LIBDRM::LIBDRM APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAVE_HDR_OUTPUT_METADATA=1) + endif() + if(LIBDRM_HAS_MODIFIER_NAME) + set_property(TARGET LIBDRM::LIBDRM APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAVE_DRM_MODIFIER_NAME=1) + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LIBDRM::LIBDRM) endif() endif() - -mark_as_advanced(LIBDRM_INCLUDE_DIR LIBDRM_LIBRARY) diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index fd06507015fef..c7c053b82139a 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -235,7 +235,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L list(APPEND HEADERS EGLImage.h) endif() - if(LIBDRM_FOUND) + if(TARGET LIBDRM::LIBDRM) list(APPEND SOURCES DRMHelpers.cpp) list(APPEND HEADERS DRMHelpers.h) endif() From 104b208db30e313b67b2adeff8eddd58aba0191c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 16:59:14 +1000 Subject: [PATCH 172/811] [cmake] FindLibDVD* update comments to match created objects --- cmake/modules/FindLibDvdCSS.cmake | 8 +------- cmake/modules/FindLibDvdNav.cmake | 8 +------- cmake/modules/FindLibDvdRead.cmake | 8 +------- 3 files changed, 3 insertions(+), 21 deletions(-) diff --git a/cmake/modules/FindLibDvdCSS.cmake b/cmake/modules/FindLibDvdCSS.cmake index df7ec6ad1f1b4..742efd55836e0 100644 --- a/cmake/modules/FindLibDvdCSS.cmake +++ b/cmake/modules/FindLibDvdCSS.cmake @@ -3,13 +3,7 @@ # ---------- # Finds the libdvdcss library # -# This will define the following variables:: -# -# LIBDVDCSS_FOUND - system has LibDvdCSS -# LIBDVDCSS_INCLUDE_DIRS - the LibDvdCSS include directory -# LIBDVDCSS_LIBRARIES - the LibDvdCSS libraries -# -# and the following imported targets:: +# This will define the following target: # # LibDvdCSS::LibDvdCSS - The LibDvdCSS library diff --git a/cmake/modules/FindLibDvdNav.cmake b/cmake/modules/FindLibDvdNav.cmake index 4a07d3ca56e57..ffd8d9d4d3b8f 100644 --- a/cmake/modules/FindLibDvdNav.cmake +++ b/cmake/modules/FindLibDvdNav.cmake @@ -3,13 +3,7 @@ # ---------- # Finds the dvdnav library # -# This will define the following variables:: -# -# LIBDVDNAV_FOUND - system has LibDvdNav -# LIBDVDNAV_INCLUDE_DIRS - the LibDvdNav include directory -# LIBDVDNAV_LIBRARIES - the LibDvdNav libraries -# -# and the following imported targets:: +# This will define the following target: # # LibDvdNav::LibDvdNav - The LibDvdNav library diff --git a/cmake/modules/FindLibDvdRead.cmake b/cmake/modules/FindLibDvdRead.cmake index c9ffb3cc12ab4..b67ff904eda9b 100644 --- a/cmake/modules/FindLibDvdRead.cmake +++ b/cmake/modules/FindLibDvdRead.cmake @@ -3,13 +3,7 @@ # ---------- # Finds the dvdread library # -# This will define the following variables:: -# -# LIBDVDREAD_FOUND - system has LibDvdRead -# LIBDVDREAD_INCLUDE_DIRS - the LibDvdRead include directory -# LIBDVDREAD_LIBRARIES - the LibDvdRead libraries -# -# and the following imported targets:: +# This will define the following target: # # LibDvdRead::LibDvdRead - The LibDvdRead library From 899e6170b080de4762fb6bdf4bfb7847b013fd3e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:01:21 +1000 Subject: [PATCH 173/811] [cmake] FindSqlite3 minor consistency fixes --- cmake/modules/FindSqlite3.cmake | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cmake/modules/FindSqlite3.cmake b/cmake/modules/FindSqlite3.cmake index 9b85e34f41a71..f1e50e44a74e0 100644 --- a/cmake/modules/FindSqlite3.cmake +++ b/cmake/modules/FindSqlite3.cmake @@ -9,15 +9,17 @@ # if(NOT TARGET SQLite3::SQLite3) - + find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_SQLITE3 sqlite3 QUIET) endif() find_path(SQLITE3_INCLUDE_DIR NAMES sqlite3.h - PATHS ${PC_SQLITE3_INCLUDEDIR}) + PATHS ${PC_SQLITE3_INCLUDEDIR} + NO_CACHE) find_library(SQLITE3_LIBRARY NAMES sqlite3 - PATHS ${PC_SQLITE3_LIBDIR}) + PATHS ${PC_SQLITE3_LIBDIR} + NO_CACHE) set(SQLITE3_VERSION ${PC_SQLITE3_VERSION}) @@ -34,5 +36,4 @@ if(NOT TARGET SQLite3::SQLite3) set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP SQLite3::SQLite3) endif() - mark_as_advanced(SQLITE3_INCLUDE_DIR SQLITE3_LIBRARY) endif() From 222b4210e18f8d7234548dfb5bfb78d173786d04 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:03:31 +1000 Subject: [PATCH 174/811] [cmake] FindPlayerAPIS migrate to full TARGET usage --- cmake/modules/FindPlayerAPIs.cmake | 50 +++++++++++++----------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindPlayerAPIs.cmake b/cmake/modules/FindPlayerAPIs.cmake index 6d86c77c1205a..1a4913b4901aa 100644 --- a/cmake/modules/FindPlayerAPIs.cmake +++ b/cmake/modules/FindPlayerAPIs.cmake @@ -3,43 +3,35 @@ # -------- # Finds the PlayerAPIs library # -# This will define the following variables:: -# -# PLAYERAPIS_FOUND - system has PlayerAPIs -# PLAYERAPIS_INCLUDE_DIRS - the PlayerAPIs include directory -# PLAYERAPIS_LIBRARIES - the PlayerAPIs libraries -# PLAYERAPIS_DEFINITIONS - the PlayerAPIs compile definitions -# -# and the following imported targets:: +# This will define the following target: # # PLAYERAPIS::PLAYERAPIS - The playerAPIs library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PLAYERAPIS libplayerAPIs>=1.0.0 QUIET) -endif() - -find_path(PLAYERAPIS_INCLUDE_DIR NAMES starfish-media-pipeline/StarfishMediaAPIs.h - PATHS ${PC_PLAYERAPIS_INCLUDEDIR}) -find_library(PLAYERAPIS_LIBRARY NAMES playerAPIs - PATHS ${PC_PLAYERAPIS_LIBDIR}) +if(NOT TARGET PLAYERAPIS::PLAYERAPIS) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PLAYERAPIS libplayerAPIs>=1.0.0 QUIET) + endif() -set(PLAYERAPIS_VERSION ${PC_PLAYERAPIS_VERSION}) + find_path(PLAYERAPIS_INCLUDE_DIR NAMES starfish-media-pipeline/StarfishMediaAPIs.h + PATHS ${PC_PLAYERAPIS_INCLUDEDIR} + NO_CACHE) + find_library(PLAYERAPIS_LIBRARY NAMES playerAPIs + PATHS ${PC_PLAYERAPIS_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PlayerAPIs - REQUIRED_VARS PLAYERAPIS_LIBRARY PLAYERAPIS_INCLUDE_DIR - VERSION_VAR PLAYERAPIS_VERSION) + set(PLAYERAPIS_VERSION ${PC_PLAYERAPIS_VERSION}) -if(PLAYERAPIS_FOUND) - set(PLAYERAPIS_INCLUDE_DIRS ${PLAYERAPIS_INCLUDE_DIR}) - set(PLAYERAPIS_LIBRARIES ${PLAYERAPIS_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PlayerAPIs + REQUIRED_VARS PLAYERAPIS_LIBRARY PLAYERAPIS_INCLUDE_DIR + VERSION_VAR PLAYERAPIS_VERSION) - if(NOT TARGET PLAYERAPIS::PLAYERAPIS) + if(PLAYERAPIS_FOUND) add_library(PLAYERAPIS::PLAYERAPIS UNKNOWN IMPORTED) set_target_properties(PLAYERAPIS::PLAYERAPIS PROPERTIES - IMPORTED_LOCATION "${PLAYERAPIS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PLAYERAPIS_INCLUDE_DIR}") + IMPORTED_LOCATION "${PLAYERAPIS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PLAYERAPIS_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PLAYERAPIS::PLAYERAPIS) endif() endif() - -mark_as_advanced(PLAYERAPIS_INCLUDE_DIR PLAYERAPIS_LIBRARY) From 8148d54d06cdc593c0d4fa7dc5a3c8def485c5ce Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:04:49 +1000 Subject: [PATCH 175/811] [cmake] FindPlayerFactory migrate to TARGET usage --- cmake/modules/FindPlayerFactory.cmake | 50 +++++++++++---------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindPlayerFactory.cmake b/cmake/modules/FindPlayerFactory.cmake index 6840c3df05f5a..1665d6c58b4f8 100644 --- a/cmake/modules/FindPlayerFactory.cmake +++ b/cmake/modules/FindPlayerFactory.cmake @@ -3,43 +3,35 @@ # -------- # Finds the PlayerFactory library # -# This will define the following variables:: -# -# PLAYERFACTORY_FOUND - system has PlayerFactory -# PLAYERFACTORY_INCLUDE_DIRS - the PlayerFactory include directory -# PLAYERFACTORY_LIBRARIES - the PlayerFactory libraries -# PLAYERFACTORY_DEFINITIONS - the PlayerFactory compile definitions -# -# and the following imported targets:: +# This will define the following target: # # PLAYERFACTORY::PLAYERFACTORY - The PlayerFactory library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PLAYERFACTORY libpf-1.0>=1.0.0 QUIET) -endif() - -find_path(PLAYERFACTORY_INCLUDE_DIR NAMES player-factory/common.hpp - PATHS ${PC_PLAYERFACTORY_INCLUDEDIR}) -find_library(PLAYERFACTORY_LIBRARY NAMES pf-1.0 - PATHS ${PC_PLAYERFACTORY_LIBDIR}) +if(NOT TARGET PLAYERFACTORY::PLAYERFACTORY) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PLAYERFACTORY libpf-1.0>=1.0.0 QUIET) + endif() -set(PLAYERFACTORY_VERSION ${PC_PLAYERFACTORY_VERSION}) + find_path(PLAYERFACTORY_INCLUDE_DIR NAMES player-factory/common.hpp + PATHS ${PC_PLAYERFACTORY_INCLUDEDIR} + NO_CACHE) + find_library(PLAYERFACTORY_LIBRARY NAMES pf-1.0 + PATHS ${PC_PLAYERFACTORY_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PlayerFactory - REQUIRED_VARS PLAYERFACTORY_LIBRARY PLAYERFACTORY_INCLUDE_DIR - VERSION_VAR PLAYERFACTORY_VERSION) + set(PLAYERFACTORY_VERSION ${PC_PLAYERFACTORY_VERSION}) -if(PLAYERFACTORY_FOUND) - set(PLAYERFACTORY_INCLUDE_DIRS ${PLAYERFACTORY_INCLUDE_DIR}) - set(PLAYERFACTORY_LIBRARIES ${PLAYERFACTORY_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PlayerFactory + REQUIRED_VARS PLAYERFACTORY_LIBRARY PLAYERFACTORY_INCLUDE_DIR + VERSION_VAR PLAYERFACTORY_VERSION) - if(NOT TARGET PLAYERFACTORY::PLAYERFACTORY) + if(PLAYERFACTORY_FOUND) add_library(PLAYERFACTORY::PLAYERFACTORY UNKNOWN IMPORTED) set_target_properties(PLAYERFACTORY::PLAYERFACTORY PROPERTIES - IMPORTED_LOCATION "${PLAYERFACTORY_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${PLAYERFACTORY_INCLUDE_DIR}") + IMPORTED_LOCATION "${PLAYERFACTORY_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${PLAYERFACTORY_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PLAYERFACTORY::PLAYERFACTORY) endif() endif() - -mark_as_advanced(PLAYERFACTORY_INCLUDE_DIR PLAYERFACTORY_LIBRARY) From 1705a09657a20618d8f8ca13c0bef5b7be48b0ce Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:06:31 +1000 Subject: [PATCH 176/811] [cmake] FindXkbcommon migrate to full TARGET usage --- cmake/modules/FindXkbcommon.cmake | 48 ++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/cmake/modules/FindXkbcommon.cmake b/cmake/modules/FindXkbcommon.cmake index 40cbb314d7902..49906f9475604 100644 --- a/cmake/modules/FindXkbcommon.cmake +++ b/cmake/modules/FindXkbcommon.cmake @@ -2,39 +2,35 @@ # ----------- # Finds the libxkbcommon library # -# This will define the following variables:: +# This will define the following target: # -# XKBCOMMON_FOUND - the system has libxkbcommon -# XKBCOMMON_INCLUDE_DIRS - the libxkbcommon include directory -# XKBCOMMON_LIBRARIES - the libxkbcommon libraries +# XKBCOMMON::XKBCOMMON - The libxkbcommon library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_XKBCOMMON xkbcommon QUIET) -endif() - - -find_path(XKBCOMMON_INCLUDE_DIR NAMES xkbcommon/xkbcommon.h - PATHS ${PC_XKBCOMMON_INCLUDEDIR}) -find_library(XKBCOMMON_LIBRARY NAMES xkbcommon - PATHS ${PC_XKBCOMMON_LIBDIR}) +if(NOT TARGET XKBCOMMON::XKBCOMMON) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_XKBCOMMON xkbcommon QUIET) + endif() -set(XKBCOMMON_VERSION ${PC_XKBCOMMON_VERSION}) + find_path(XKBCOMMON_INCLUDE_DIR NAMES xkbcommon/xkbcommon.h + PATHS ${PC_XKBCOMMON_INCLUDEDIR} + NO_CACHE) + find_library(XKBCOMMON_LIBRARY NAMES xkbcommon + PATHS ${PC_XKBCOMMON_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Xkbcommon - REQUIRED_VARS XKBCOMMON_LIBRARY XKBCOMMON_INCLUDE_DIR - VERSION_VAR XKBCOMMON_VERSION) + set(XKBCOMMON_VERSION ${PC_XKBCOMMON_VERSION}) -if(XKBCOMMON_FOUND) - set(XKBCOMMON_INCLUDE_DIRS ${XKBCOMMON_INCLUDE_DIR}) - set(XKBCOMMON_LIBRARIES ${XKBCOMMON_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Xkbcommon + REQUIRED_VARS XKBCOMMON_LIBRARY XKBCOMMON_INCLUDE_DIR + VERSION_VAR XKBCOMMON_VERSION) - if(NOT TARGET XKBCOMMON::XKBCOMMON) + if(XKBCOMMON_FOUND) add_library(XKBCOMMON::XKBCOMMON UNKNOWN IMPORTED) set_target_properties(XKBCOMMON::XKBCOMMON PROPERTIES - IMPORTED_LOCATION "${XKBCOMMON_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${XKBCOMMON_INCLUDE_DIR}") + IMPORTED_LOCATION "${XKBCOMMON_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${XKBCOMMON_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP XKBCOMMON::XKBCOMMON) endif() endif() - -mark_as_advanced(XKBCOMMON_INCLUDE_DIR XKBCOMMON_LIBRARY) From 12e6fc6eff19b78f9495931e8333e73a4336379f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:07:40 +1000 Subject: [PATCH 177/811] [cmake] FindWebOSHelpers migrate to full TARGET usage --- cmake/modules/FindWebOSHelpers.cmake | 50 ++++++++++++---------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindWebOSHelpers.cmake b/cmake/modules/FindWebOSHelpers.cmake index 665efbd4e4bb0..e17f4b9ebc98b 100644 --- a/cmake/modules/FindWebOSHelpers.cmake +++ b/cmake/modules/FindWebOSHelpers.cmake @@ -3,43 +3,35 @@ # -------- # Finds the WebOSHelpers library # -# This will define the following variables:: -# -# WEBOSHELPERS_FOUND - system has WebOSHelpers -# WEBOSHELPERS_INCLUDE_DIRS - the WebOSHelpers include directory -# WEBOSHELPERS_LIBRARIES - the WebOSHelpers libraries -# WEBOSHELPERS_DEFINITIONS - the WebOSHelpers compile definitions -# -# and the following imported targets:: +# This will define the following target: # # WEBOSHELPERS::WEBOSHELPERS - The webOS helpers library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_WEBOSHELPERS helpers>=2.0.0 QUIET) -endif() - -find_path(WEBOSHELPERS_INCLUDE_DIR NAMES webos-helpers/libhelpers.h - PATHS ${PC_WEBOSHELPERS_INCLUDEDIR}) -find_library(WEBOSHELPERS_LIBRARY NAMES helpers - PATHS ${PC_WEBOSHELPERS_LIBDIR}) +if(NOT TARGET WEBOSHELPERS::WEBOSHELPERS) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_WEBOSHELPERS helpers>=2.0.0 QUIET) + endif() -set(WEBOSHELPERS_VERSION ${PC_WEBOSHELPERS_VERSION}) + find_path(WEBOSHELPERS_INCLUDE_DIR NAMES webos-helpers/libhelpers.h + PATHS ${PC_WEBOSHELPERS_INCLUDEDIR} + NO_CACHE) + find_library(WEBOSHELPERS_LIBRARY NAMES helpers + PATHS ${PC_WEBOSHELPERS_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WebOSHelpers - REQUIRED_VARS WEBOSHELPERS_LIBRARY WEBOSHELPERS_INCLUDE_DIR - VERSION_VAR WEBOSHELPERS_VERSION) + set(WEBOSHELPERS_VERSION ${PC_WEBOSHELPERS_VERSION}) -if(WEBOSHELPERS_FOUND) - set(WEBOSHELPERS_INCLUDE_DIRS ${WEBOSHELPERS_INCLUDE_DIR}) - set(WEBOSHELPERS_LIBRARIES ${WEBOSHELPERS_LIBRARY}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(WebOSHelpers + REQUIRED_VARS WEBOSHELPERS_LIBRARY WEBOSHELPERS_INCLUDE_DIR + VERSION_VAR WEBOSHELPERS_VERSION) - if(NOT TARGET WEBOSHELPERS::WEBOSHELPERS) + if(WEBOSHELPERS_FOUND) add_library(WEBOSHELPERS::WEBOSHELPERS UNKNOWN IMPORTED) set_target_properties(WEBOSHELPERS::WEBOSHELPERS PROPERTIES - IMPORTED_LOCATION "${WEBOSHELPERS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${WEBOSHELPERS_INCLUDE_DIR}") + IMPORTED_LOCATION "${WEBOSHELPERS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${WEBOSHELPERS_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP WEBOSHELPERS::WEBOSHELPERS) endif() endif() - -mark_as_advanced(WEBOSHELPERS_INCLUDE_DIR WEBOSHELPERS_LIBRARY) From b148211b493ab81cfddfd6cf3b81a18d33a7d1af Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:10:53 +1000 Subject: [PATCH 178/811] [cmake] FindVDPAU migrate to full TARGET usage --- cmake/modules/FindVDPAU.cmake | 47 ++++++++----------- .../DVDCodecs/Video/CMakeLists.txt | 2 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 2 +- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/cmake/modules/FindVDPAU.cmake b/cmake/modules/FindVDPAU.cmake index b2c5482343e58..bd246bdc74ce9 100644 --- a/cmake/modules/FindVDPAU.cmake +++ b/cmake/modules/FindVDPAU.cmake @@ -3,45 +3,36 @@ # --------- # Finds the VDPAU library # -# This will define the following variables:: -# -# VDPAU_FOUND - system has VDPAU -# VDPAU_INCLUDE_DIRS - the VDPAU include directory -# VDPAU_LIBRARIES - the VDPAU libraries -# VDPAU_DEFINITIONS - the VDPAU definitions -# -# and the following imported targets:: +# This will define the following target: # # VDPAU::VDPAU - The VDPAU library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_VDPAU vdpau QUIET) -endif() - -find_path(VDPAU_INCLUDE_DIR NAMES vdpau/vdpau.h vdpau/vdpau_x11.h - PATHS ${PC_VDPAU_INCLUDEDIR}) -find_library(VDPAU_LIBRARY NAMES vdpau - PATHS ${PC_VDPAU_LIBDIR}) +if(NOT TARGET VDPAU::VDPAU) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_VDPAU vdpau QUIET) + endif() -set(VDPAU_VERSION ${PC_VDPAU_VERSION}) + find_path(VDPAU_INCLUDE_DIR NAMES vdpau/vdpau.h vdpau/vdpau_x11.h + PATHS ${PC_VDPAU_INCLUDEDIR} + NO_CACHE) + find_library(VDPAU_LIBRARY NAMES vdpau + PATHS ${PC_VDPAU_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(VDPAU - REQUIRED_VARS VDPAU_LIBRARY VDPAU_INCLUDE_DIR - VERSION_VAR VDPAU_VERSION) + set(VDPAU_VERSION ${PC_VDPAU_VERSION}) -if(VDPAU_FOUND) - set(VDPAU_INCLUDE_DIRS ${VDPAU_INCLUDE_DIR}) - set(VDPAU_LIBRARIES ${VDPAU_LIBRARY}) - set(VDPAU_DEFINITIONS -DHAVE_LIBVDPAU=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(VDPAU + REQUIRED_VARS VDPAU_LIBRARY VDPAU_INCLUDE_DIR + VERSION_VAR VDPAU_VERSION) - if(NOT TARGET VDPAU::VDPAU) + if(VDPAU_FOUND) add_library(VDPAU::VDPAU UNKNOWN IMPORTED) set_target_properties(VDPAU::VDPAU PROPERTIES IMPORTED_LOCATION "${VDPAU_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${VDPAU_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBVDPAU=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP VDPAU::VDPAU) endif() endif() - -mark_as_advanced(VDPAU_INCLUDE_DIR VDPAU_LIBRARY) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt index 366decbdd5682..452ed873be3be 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/CMakeLists.txt @@ -16,7 +16,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND HEADERS DXVA.h) endif() -if(VDPAU_FOUND) +if(TARGET VDPAU::VDPAU) list(APPEND SOURCES VDPAU.cpp) list(APPEND HEADERS VDPAU.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index 54d04c33f2069..e4da9252674e9 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -20,7 +20,7 @@ if(VAAPI_FOUND) endif() endif() -if(VDPAU_FOUND) +if(TARGET VDPAU::VDPAU) list(APPEND SOURCES RendererVDPAU.cpp VdpauGL.cpp) list(APPEND HEADERS RendererVDPAU.h From cf099447f755aec16ac0d0b33798eff57f7786ca Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:12:55 +1000 Subject: [PATCH 179/811] [cmake] FindSpdlog minor consistency fixups --- cmake/modules/FindSpdlog.cmake | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index 520c8abba757e..b1beabea3b72c 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -75,6 +75,7 @@ if(NOT TARGET spdlog::spdlog) add_dependencies(${MODULE_LC} fmt::fmt) else() if(NOT TARGET spdlog::spdlog) + find_package(PkgConfig) # Fallback to pkg-config and individual lib/include file search if(PKG_CONFIG_FOUND) pkg_check_modules(PC_SPDLOG spdlog QUIET) @@ -82,12 +83,15 @@ if(NOT TARGET spdlog::spdlog) endif() find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h - PATHS ${PC_SPDLOG_INCLUDEDIR}) + PATHS ${PC_SPDLOG_INCLUDEDIR} + NO_CACHE) find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog - PATHS ${PC_SPDLOG_LIBDIR}) + PATHS ${PC_SPDLOG_LIBDIR} + NO_CACHE) find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd - PATHS ${PC_SPDLOG_LIBDIR}) + PATHS ${PC_SPDLOG_LIBDIR} + NO_CACHE) # Only add -D definitions. Skip -I include as we do a find_path for the header anyway foreach(_spdlog_cflag IN LISTS PC_SPDLOG_CFLAGS) @@ -153,11 +157,10 @@ if(NOT TARGET spdlog::spdlog) INTERFACE_COMPILE_DEFINITIONS "${_spdlog_definitions}") endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP spdlog::spdlog) + if(TARGET spdlog) add_dependencies(spdlog::spdlog spdlog) endif() endif() - - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP spdlog::spdlog) - endif() From b960b163090722284f4ad8c01e55867ebf75223d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:14:05 +1000 Subject: [PATCH 180/811] [cmake] FindRapidJSON minor consistency fixups --- cmake/modules/FindRapidJSON.cmake | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 4a84365748ce7..5f6b290879f98 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -52,7 +52,8 @@ if(NOT TARGET RapidJSON::RapidJSON) endif() find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h - PATHS ${PC_RapidJSON_INCLUDEDIR}) + PATHS ${PC_RapidJSON_INCLUDEDIR} + NO_CACHE) endif() include(FindPackageHandleStandardArgs) @@ -61,18 +62,13 @@ if(NOT TARGET RapidJSON::RapidJSON) VERSION_VAR RapidJSON_VERSION) if(RAPIDJSON_FOUND) - if(NOT TARGET RapidJSON::RapidJSON) - add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) - - set_target_properties(RapidJSON::RapidJSON PROPERTIES - FOLDER "External Projects" - INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") - endif() + add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) + set_target_properties(RapidJSON::RapidJSON PROPERTIES + FOLDER "External Projects" + INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") if(TARGET rapidjson) add_dependencies(RapidJSON::RapidJSON rapidjson) endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP RapidJSON::RapidJSON) endif() - - mark_as_advanced(RapidJSON_INCLUDE_DIR) endif() From 0306b3eb471222aa3373a1ca627ea8aac93bb1a6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:15:46 +1000 Subject: [PATCH 181/811] [cmake] FindAvahi migrate to full TARGET usage --- cmake/modules/FindAvahi.cmake | 81 ++++++++----------- .../linux/network/zeroconf/CMakeLists.txt | 2 +- 2 files changed, 36 insertions(+), 47 deletions(-) diff --git a/cmake/modules/FindAvahi.cmake b/cmake/modules/FindAvahi.cmake index e0e6689b52141..b79756840faff 100644 --- a/cmake/modules/FindAvahi.cmake +++ b/cmake/modules/FindAvahi.cmake @@ -3,61 +3,50 @@ # --------- # Finds the avahi library # -# This will define the following variables:: +# This will define the following target: # -# AVAHI_FOUND - system has avahi -# AVAHI_INCLUDE_DIRS - the avahi include directory -# AVAHI_LIBRARIES - the avahi libraries -# AVAHI_DEFINITIONS - the avahi definitions -# -# and the following imported targets:: -# -# Avahi::Avahi - The avahi library - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_AVAHI avahi-client QUIET) -endif() +# Avahi::Avahi - The avahi client library +# Avahi::AvahiCommon - The avahi common library -find_path(AVAHI_CLIENT_INCLUDE_DIR NAMES avahi-client/client.h - PATHS ${PC_AVAHI_INCLUDEDIR}) -find_path(AVAHI_COMMON_INCLUDE_DIR NAMES avahi-common/defs.h - PATHS ${PC_AVAHI_INCLUDEDIR}) -find_library(AVAHI_CLIENT_LIBRARY NAMES avahi-client - PATHS ${PC_AVAHI_LIBDIR}) -find_library(AVAHI_COMMON_LIBRARY NAMES avahi-common - PATHS ${PC_AVAHI_LIBDIR}) +if(NOT TARGET Avahi::Avahi) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_AVAHI avahi-client QUIET) + endif() -set(AVAHI_VERSION ${PC_AVAHI_VERSION}) + find_path(AVAHI_CLIENT_INCLUDE_DIR NAMES avahi-client/client.h + PATHS ${PC_AVAHI_INCLUDEDIR} + NO_CACHE) + find_path(AVAHI_COMMON_INCLUDE_DIR NAMES avahi-common/defs.h + PATHS ${PC_AVAHI_INCLUDEDIR} + NO_CACHE) + find_library(AVAHI_CLIENT_LIBRARY NAMES avahi-client + PATHS ${PC_AVAHI_LIBDIR} + NO_CACHE) + find_library(AVAHI_COMMON_LIBRARY NAMES avahi-common + PATHS ${PC_AVAHI_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Avahi - REQUIRED_VARS AVAHI_CLIENT_LIBRARY AVAHI_COMMON_LIBRARY - AVAHI_CLIENT_INCLUDE_DIR AVAHI_COMMON_INCLUDE_DIR - VERSION_VAR AVAHI_VERSION) + set(AVAHI_VERSION ${PC_AVAHI_VERSION}) -if(AVAHI_FOUND) - set(AVAHI_INCLUDE_DIRS ${AVAHI_CLIENT_INCLUDE_DIR} - ${AVAHI_COMMON_INCLUDE_DIR}) - set(AVAHI_LIBRARIES ${AVAHI_CLIENT_LIBRARY} - ${AVAHI_COMMON_LIBRARY}) - set(AVAHI_DEFINITIONS -DHAS_AVAHI=1 -DHAS_ZEROCONF=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Avahi + REQUIRED_VARS AVAHI_CLIENT_LIBRARY AVAHI_COMMON_LIBRARY + AVAHI_CLIENT_INCLUDE_DIR AVAHI_COMMON_INCLUDE_DIR + VERSION_VAR AVAHI_VERSION) - if(NOT TARGET Avahi::Avahi) - add_library(Avahi::Avahi UNKNOWN IMPORTED) - set_target_properties(Avahi::Avahi PROPERTIES - IMPORTED_LOCATION "${AVAHI_CLIENT_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${AVAHI_CLIENT_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_AVAHI=1) - endif() - if(NOT TARGET Avahi::AvahiCommon) + if(AVAHI_FOUND) add_library(Avahi::AvahiCommon UNKNOWN IMPORTED) set_target_properties(Avahi::AvahiCommon PROPERTIES IMPORTED_LOCATION "${AVAHI_COMMON_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${AVAHI_COMMON_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAS_AVAHI=1 - INTERFACE_LINK_LIBRARIES Avahi::Avahi) + INTERFACE_COMPILE_DEFINITIONS "HAS_AVAHI=1;HAS_ZEROCONF=1") + add_library(Avahi::Avahi UNKNOWN IMPORTED) + set_target_properties(Avahi::Avahi PROPERTIES + IMPORTED_LOCATION "${AVAHI_CLIENT_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${AVAHI_CLIENT_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "HAS_AVAHI=1;HAS_ZEROCONF=1" + INTERFACE_LINK_LIBRARIES Avahi::AvahiCommon) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP Avahi::Avahi) endif() endif() - -mark_as_advanced(AVAHI_CLIENT_INCLUDE_DIR AVAHI_COMMON_INCLUDE_DIR - AVAHI_CLIENT_LIBRARY AVAHI_COMMON_LIBRARY) diff --git a/xbmc/platform/linux/network/zeroconf/CMakeLists.txt b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt index b31ab7359eafa..2ad37796e4fed 100644 --- a/xbmc/platform/linux/network/zeroconf/CMakeLists.txt +++ b/xbmc/platform/linux/network/zeroconf/CMakeLists.txt @@ -1,4 +1,4 @@ -if(AVAHI_FOUND) +if(TARGET Avahi::Avahi) set(SOURCES ZeroconfAvahi.cpp ZeroconfBrowserAvahi.cpp) set(HEADERS ZeroconfAvahi.h From 8a0d57289146ced3ef28d1434e4c735f2450000d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:51:41 +1000 Subject: [PATCH 182/811] [cmake] FindFreeType migrate to full TARGET usage --- cmake/modules/FindFreeType.cmake | 49 ++++++++++++++------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/cmake/modules/FindFreeType.cmake b/cmake/modules/FindFreeType.cmake index 771819e902401..3b73bc53f8ee1 100644 --- a/cmake/modules/FindFreeType.cmake +++ b/cmake/modules/FindFreeType.cmake @@ -3,44 +3,37 @@ # ------------ # Finds the FreeType library # -# This will define the following variables:: -# -# FREETYPE_FOUND - system has FreeType -# FREETYPE_INCLUDE_DIRS - the FreeType include directory -# FREETYPE_LIBRARIES - the FreeType libraries -# -# and the following imported targets:: +# This will define the following target: # # FreeType::FreeType - The FreeType library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_FREETYPE freetype2 QUIET) -endif() - -find_path(FREETYPE_INCLUDE_DIR NAMES freetype/freetype.h freetype.h - PATHS ${PC_FREETYPE_INCLUDEDIR} - ${PC_FREETYPE_INCLUDE_DIRS} - PATH_SUFFIXES freetype2) -find_library(FREETYPE_LIBRARY NAMES freetype freetype246MT - PATHS ${PC_FREETYPE_LIBDIR}) +if(NOT TARGET FreeType::FreeType) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_FREETYPE freetype2 QUIET) + endif() -set(FREETYPE_VERSION ${PC_FREETYPE_VERSION}) + find_path(FREETYPE_INCLUDE_DIR NAMES freetype/freetype.h freetype.h + PATHS ${PC_FREETYPE_INCLUDEDIR} + ${PC_FREETYPE_INCLUDE_DIRS} + PATH_SUFFIXES freetype2 + NO_CACHE) + find_library(FREETYPE_LIBRARY NAMES freetype freetype246MT + PATHS ${PC_FREETYPE_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(FreeType - REQUIRED_VARS FREETYPE_LIBRARY FREETYPE_INCLUDE_DIR - VERSION_VAR FREETYPE_VERSION) + set(FREETYPE_VERSION ${PC_FREETYPE_VERSION}) -if(FREETYPE_FOUND) - set(FREETYPE_LIBRARIES ${FREETYPE_LIBRARY}) - set(FREETYPE_INCLUDE_DIRS ${FREETYPE_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(FreeType + REQUIRED_VARS FREETYPE_LIBRARY FREETYPE_INCLUDE_DIR + VERSION_VAR FREETYPE_VERSION) - if(NOT TARGET FreeType::FreeType) + if(FREETYPE_FOUND) add_library(FreeType::FreeType UNKNOWN IMPORTED) set_target_properties(FreeType::FreeType PROPERTIES IMPORTED_LOCATION "${FREETYPE_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${FREETYPE_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP FreeType::FreeType) endif() endif() - -mark_as_advanced(FREETYPE_INCLUDE_DIR FREETYPE_LIBRARY) From 8ba612c86bdf090e8b7a960daf02a4908e12b078 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:52:11 +1000 Subject: [PATCH 183/811] [cmake] FindGBM migrate to full TARGET usage --- cmake/modules/FindGBM.cmake | 91 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 48 deletions(-) diff --git a/cmake/modules/FindGBM.cmake b/cmake/modules/FindGBM.cmake index 37a26a7bc4490..f98a55cbc55f0 100644 --- a/cmake/modules/FindGBM.cmake +++ b/cmake/modules/FindGBM.cmake @@ -2,67 +2,62 @@ # ---------- # Finds the GBM library # -# This will define the following variables:: -# -# GBM_FOUND - system has GBM -# GBM_INCLUDE_DIRS - the GBM include directory -# GBM_LIBRARIES - the GBM libraries -# GBM_DEFINITIONS - the GBM definitions -# -# and the following imported targets:: +# This will define the following target: # # GBM::GBM - The GBM library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_GBM gbm QUIET) -endif() +if(NOT TARGET GBM::GBM) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_GBM gbm QUIET) + endif() -find_path(GBM_INCLUDE_DIR NAMES gbm.h - PATHS ${PC_GBM_INCLUDEDIR}) -find_library(GBM_LIBRARY NAMES gbm - PATHS ${PC_GBM_LIBDIR}) + find_path(GBM_INCLUDE_DIR NAMES gbm.h + PATHS ${PC_GBM_INCLUDEDIR} + NO_CACHE) + find_library(GBM_LIBRARY NAMES gbm + PATHS ${PC_GBM_LIBDIR} + NO_CACHE) -set(GBM_VERSION ${PC_GBM_VERSION}) + set(GBM_VERSION ${PC_GBM_VERSION}) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(GBM - REQUIRED_VARS GBM_LIBRARY GBM_INCLUDE_DIR - VERSION_VAR GBM_VERSION) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GBM + REQUIRED_VARS GBM_LIBRARY GBM_INCLUDE_DIR + VERSION_VAR GBM_VERSION) -include(CheckCSourceCompiles) -set(CMAKE_REQUIRED_LIBRARIES ${GBM_LIBRARY}) -check_c_source_compiles("#include + include(CheckCSourceCompiles) + set(CMAKE_REQUIRED_LIBRARIES ${GBM_LIBRARY}) + check_c_source_compiles("#include - int main() - { - gbm_bo_map(NULL, 0, 0, 0, 0, GBM_BO_TRANSFER_WRITE, NULL, NULL); - } - " GBM_HAS_BO_MAP) + int main() + { + gbm_bo_map(NULL, 0, 0, 0, 0, GBM_BO_TRANSFER_WRITE, NULL, NULL); + } + " GBM_HAS_BO_MAP) -check_c_source_compiles("#include + check_c_source_compiles("#include - int main() - { - gbm_surface_create_with_modifiers(NULL, 0, 0, 0, NULL, 0); - } - " GBM_HAS_MODIFIERS) + int main() + { + gbm_surface_create_with_modifiers(NULL, 0, 0, 0, NULL, 0); + } + " GBM_HAS_MODIFIERS) -if(GBM_FOUND) - set(GBM_LIBRARIES ${GBM_LIBRARY}) - set(GBM_INCLUDE_DIRS ${GBM_INCLUDE_DIR}) - set(GBM_DEFINITIONS -DHAVE_GBM=1) - if(GBM_HAS_BO_MAP) - list(APPEND GBM_DEFINITIONS -DHAS_GBM_BO_MAP=1) - endif() - if(GBM_HAS_MODIFIERS) - list(APPEND GBM_DEFINITIONS -DHAS_GBM_MODIFIERS=1) - endif() - if(NOT TARGET GBM::GBM) + if(GBM_FOUND) add_library(GBM::GBM UNKNOWN IMPORTED) set_target_properties(GBM::GBM PROPERTIES IMPORTED_LOCATION "${GBM_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${GBM_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${GBM_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS "HAVE_GBM=1") + if(GBM_HAS_BO_MAP) + set_property(TARGET GBM::GBM APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAS_GBM_BO_MAP=1) + endif() + if(GBM_HAS_MODIFIERS) + set_property(TARGET GBM::GBM APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS HAS_GBM_MODIFIERS=1) + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP GBM::GBM) endif() endif() - -mark_as_advanced(GBM_INCLUDE_DIR GBM_LIBRARY) From 6e431dab8b56177c77cb4c3efc616e8579a5b92f Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:55:31 +1000 Subject: [PATCH 184/811] [cmake] FindGIF migrate fully to TARGET usage --- cmake/modules/FindGIF.cmake | 33 +++++++++------------------------ 1 file changed, 9 insertions(+), 24 deletions(-) diff --git a/cmake/modules/FindGIF.cmake b/cmake/modules/FindGIF.cmake index 7564a58bc7893..93f9af40fc400 100644 --- a/cmake/modules/FindGIF.cmake +++ b/cmake/modules/FindGIF.cmake @@ -3,38 +3,23 @@ # ------- # Finds the libgif library # -# This will define the following variables:: -# -# GIF_FOUND - system has libgif -# GIF_INCLUDE_DIRS - the libgif include directory -# GIF_LIBRARIES - the libgif libraries -# -# and the following imported targets:: +# This will define the following target: # # GIF::GIF - The libgif library -find_path(GIF_INCLUDE_DIR gif_lib.h) +if(NOT TARGET GIF::GIF) + find_library(GIF_LIBRARY NAMES gif NO_CACHE) + find_path(GIF_INCLUDE_DIR gif_lib.h NO_CACHE) -include(FindPackageHandleStandardArgs) -find_library(GIF_LIBRARY NAMES gif) -find_package_handle_standard_args(GIF - REQUIRED_VARS GIF_LIBRARY GIF_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(GIF + REQUIRED_VARS GIF_LIBRARY GIF_INCLUDE_DIR) -if(GIF_FOUND) - set(GIF_LIBRARIES ${GIF_LIBRARY}) - set(GIF_INCLUDE_DIRS ${GIF_INCLUDE_DIR}) - set(GIF_DEFINITIONS -DHAVE_LIBGIF=1) - - if(NOT TARGET GIF::GIF) + if(GIF_FOUND) add_library(GIF::GIF UNKNOWN IMPORTED) - if(GIF_LIBRARY) - set_target_properties(GIF::GIF PROPERTIES - IMPORTED_LOCATION "${GIF_LIBRARY}") - endif() set_target_properties(GIF::GIF PROPERTIES + IMPORTED_LOCATION "${GIF_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${GIF_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBGIF=1) endif() endif() - -mark_as_advanced(GIF_INCLUDE_DIR GIF_LIBRARY) From 8328c6f82ae7ff068f7ff32b40818bb833fb7290 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:56:11 +1000 Subject: [PATCH 185/811] [cmake] Remove FindGIF Doesnt appear to be used currently --- cmake/modules/FindGIF.cmake | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 cmake/modules/FindGIF.cmake diff --git a/cmake/modules/FindGIF.cmake b/cmake/modules/FindGIF.cmake deleted file mode 100644 index 93f9af40fc400..0000000000000 --- a/cmake/modules/FindGIF.cmake +++ /dev/null @@ -1,25 +0,0 @@ -#.rst: -# FindGIF -# ------- -# Finds the libgif library -# -# This will define the following target: -# -# GIF::GIF - The libgif library - -if(NOT TARGET GIF::GIF) - find_library(GIF_LIBRARY NAMES gif NO_CACHE) - find_path(GIF_INCLUDE_DIR gif_lib.h NO_CACHE) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(GIF - REQUIRED_VARS GIF_LIBRARY GIF_INCLUDE_DIR) - - if(GIF_FOUND) - add_library(GIF::GIF UNKNOWN IMPORTED) - set_target_properties(GIF::GIF PROPERTIES - IMPORTED_LOCATION "${GIF_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${GIF_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LIBGIF=1) - endif() -endif() From e11a290d9831da39c96caab1f5d268113e1a9a77 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 6 Sep 2023 08:14:57 +0200 Subject: [PATCH 186/811] [Estuary][PVR] Revert showing 'in progress' movies/episodes along with the watched status. --- addons/skin.estuary/xml/Variables.xml | 4 ++-- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/addons/skin.estuary/xml/Variables.xml b/addons/skin.estuary/xml/Variables.xml index 6e3fa51369155..b0a7461f279e9 100644 --- a/addons/skin.estuary/xml/Variables.xml +++ b/addons/skin.estuary/xml/Variables.xml @@ -287,8 +287,8 @@ $INFO[ListItem.Art(tvshow.poster)] - $INFO[ListItem.Property(InProgressEpisodes)]$INFO[ListItem.Property(WatchedEpisodes),|,]$INFO[ListItem.Property(TotalEpisodes),|,] - $INFO[ListItem.Property(InProgress)]$INFO[ListItem.Property(Watched),|,]$INFO[ListItem.Property(Total),|,] + $INFO[ListItem.Property(WatchedEpisodes)]$INFO[ListItem.Property(TotalEpisodes), / ,] + $INFO[ListItem.Property(Watched)]$INFO[ListItem.Property(Total), / ,] 2x diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index 5a4c37d5ab132..cf7baf0576634 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -345,9 +345,7 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath, { item->IncrementProperty("inprogressepisodes", 1); } - item->SetLabel2(StringUtils::Format("{}|{}|{}", - item->GetProperty("inprogressepisodes").asString(), - item->GetProperty("watchedepisodes").asString(), + item->SetLabel2(StringUtils::Format("{} / {}", item->GetProperty("watchedepisodes").asString(), item->GetProperty("totalepisodes").asString())); item->IncrementProperty("sizeinbytes", recording->GetSizeInBytes()); From 9b4d80da5484e2b0d0e200643530ba8454910920 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 4 Sep 2023 08:19:38 +0200 Subject: [PATCH 187/811] [video] Art selection dialog: Set default icon for 'No graphic' entry according to item's folder attribute. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index f33515a2bc261..63adb8e3fb477 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1973,7 +1973,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetLabel(g_localizeStrings.Get(13512)); items.Add(item); } - noneitem->SetArt("icon", "DefaultFolder.png"); + noneitem->SetArt("icon", item->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"); noneitem->SetLabel(g_localizeStrings.Get(13515)); std::string embeddedArt; @@ -2324,7 +2324,7 @@ bool CGUIDialogVideoInfo::OnGetFanart(const std::shared_ptr& videoIte // add the none option { CFileItemPtr itemNone(new CFileItem("fanart://None", false)); - itemNone->SetArt("icon", "DefaultVideo.png"); + itemNone->SetArt("icon", videoItem->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"); itemNone->SetLabel(g_localizeStrings.Get(20439)); items.Add(itemNone); } From 40455afc5ab1de8a2b6c01513494a85fbac3077e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 4 Sep 2023 08:16:40 +0200 Subject: [PATCH 188/811] [video] Art selection dialog: Fix 'Embedded art' missing in dialog. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 63adb8e3fb477..e13afa90e6586 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -1985,10 +1985,10 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt("thumb", embeddedArt); itemF->SetLabel(g_localizeStrings.Get(13519)); items.Add(itemF); From 66e97ba8e1a6f10b5c112cb7772378810295999e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 4 Sep 2023 12:03:02 +0200 Subject: [PATCH 189/811] [video] Art selection dialog: Fix support for embedded art. More video formats are supported, not just mkv, but embedded art is only available if 'use video tags' setting is activated. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 80 +++++++++++++---------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index e13afa90e6586..233fe7606a420 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -892,6 +892,38 @@ void CGUIDialogVideoInfo::OnGetArt() }; } +namespace +{ +std::string GetEmbeddedArt(const std::string& fileNameAndPath, const std::string& artType) +{ + std::string embeddedArt; + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MYVIDEOS_USETAGS)) + { + if (URIUtils::HasExtension(fileNameAndPath, ".mkv") || + URIUtils::HasExtension(fileNameAndPath, ".mp4") || + URIUtils::HasExtension(fileNameAndPath, ".avi") || + URIUtils::HasExtension(fileNameAndPath, ".m4v")) + { + CFileItem item(fileNameAndPath, false); + CVideoTagLoaderFFmpeg loader(item, nullptr, false); + CVideoInfoTag tag; + loader.Load(tag, false, nullptr); + for (const auto& it : tag.m_coverArt) + { + if (it.m_type == artType) + { + embeddedArt = CTextureUtils::GetWrappedImageURL(fileNameAndPath, "video_" + artType); + break; + } + } + } + } + return embeddedArt; +} + +} // unnamed namespace + // Allow user to select a Fanart void CGUIDialogVideoInfo::OnGetFanart() { @@ -909,24 +941,14 @@ void CGUIDialogVideoInfo::OnGetFanart() items.Add(itemCurrent); } - std::string embeddedArt; - if (URIUtils::HasExtension(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) + const std::string embeddedArt = + GetEmbeddedArt(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, "fanart"); + if (!embeddedArt.empty()) { - CFileItem item(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, false); - CVideoTagLoaderFFmpeg loader(item, nullptr, false); - CVideoInfoTag tag; - loader.Load(tag, false, nullptr); - for (const auto& it : tag.m_coverArt) - { - if (it.m_type == "fanart") - { - CFileItemPtr itemF(new CFileItem("fanart://Embedded", false)); - embeddedArt = CTextureUtils::GetWrappedImageURL(item.GetPath(), "video_fanart"); - itemF->SetArt("thumb", embeddedArt); - itemF->SetLabel(g_localizeStrings.Get(13520)); - items.Add(itemF); - } - } + const auto itemEmbedded = std::make_shared("fanart://Embedded", false); + itemEmbedded->SetArt("thumb", embeddedArt); + itemEmbedded->SetLabel(g_localizeStrings.Get(13520)); + items.Add(itemEmbedded); } // Grab the thumbnails from the web @@ -1976,24 +1998,14 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrSetArt("icon", item->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"); noneitem->SetLabel(g_localizeStrings.Get(13515)); - std::string embeddedArt; - if (URIUtils::HasExtension(item->GetVideoInfoTag()->m_strFileNameAndPath, ".mkv")) + const std::string embeddedArt = + GetEmbeddedArt(item->GetVideoInfoTag()->m_strFileNameAndPath, artType); + if (!embeddedArt.empty()) { - CFileItem itemCopy(item->GetVideoInfoTag()->m_strFileNameAndPath, false); - CVideoTagLoaderFFmpeg loader(itemCopy, nullptr, false); - CVideoInfoTag tag; - loader.Load(tag, false, nullptr); - for (const auto& it : tag.m_coverArt) - { - if (it.m_type == artType) - { - CFileItemPtr itemF(new CFileItem("thumb://Embedded", false)); - embeddedArt = CTextureUtils::GetWrappedImageURL(itemCopy.GetPath(), "video_" + artType); - itemF->SetArt("thumb", embeddedArt); - itemF->SetLabel(g_localizeStrings.Get(13519)); - items.Add(itemF); - } - } + const auto itemEmbedded = std::make_shared("thumb://Embedded", false); + itemEmbedded->SetArt("thumb", embeddedArt); + itemEmbedded->SetLabel(g_localizeStrings.Get(13519)); + items.Add(itemEmbedded); } std::string localThumb; From acdd771101b0a21cbc9876c7b02e1952fd4860a6 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Wed, 6 Sep 2023 16:17:46 +0200 Subject: [PATCH 190/811] [ActiveAE] Fix parsing audio devices names when names contains ':' character --- xbmc/cores/AudioEngine/AESinkFactory.cpp | 2 +- xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/AudioEngine/AESinkFactory.cpp b/xbmc/cores/AudioEngine/AESinkFactory.cpp index 6f27372de7786..2504e032dea98 100644 --- a/xbmc/cores/AudioEngine/AESinkFactory.cpp +++ b/xbmc/cores/AudioEngine/AESinkFactory.cpp @@ -63,7 +63,7 @@ AESinkDevice CAESinkFactory::ParseDevice(const std::string& device) if (!found) dev.driver.clear(); - pos = dev.name.find_first_of(':'); + pos = dev.name.find_last_of('|'); if (pos != std::string::npos) { diff --git a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp index 8f8f21d97fa2f..87dc678f5f3ff 100644 --- a/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp +++ b/xbmc/cores/AudioEngine/Utils/AEDeviceInfo.cpp @@ -76,7 +76,7 @@ std::string CAEDeviceInfo::ToDeviceString(const std::string& driver) const const std::string fn = GetFriendlyName(); if (!fn.empty()) - device += ":" + fn; + device += "|" + fn; return device; } From 3a794597e7d806f7ee78299f8c8e5a8d0132589d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 22 Aug 2023 11:31:33 +1000 Subject: [PATCH 191/811] [Omega] Alpha3 --- tools/Linux/kodi.metainfo.xml.in | 1 + version.txt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/Linux/kodi.metainfo.xml.in b/tools/Linux/kodi.metainfo.xml.in index 6c354a39d6e03..4c78665728836 100644 --- a/tools/Linux/kodi.metainfo.xml.in +++ b/tools/Linux/kodi.metainfo.xml.in @@ -78,6 +78,7 @@ + https://kodi.tv/article/kodi-omega-alpha-3/ https://kodi.tv/article/kodi-omega-alpha-2/ https://kodi.tv/article/kodi-omega-alpha-1/ https://kodi.tv/article/kodi-20-0-nexus-release/ diff --git a/version.txt b/version.txt index 6ab2a21618b2a..d68e225fbfd34 100644 --- a/version.txt +++ b/version.txt @@ -8,9 +8,9 @@ COPYRIGHT_YEARS 2005-2021 WEBSITE http://kodi.tv VERSION_MAJOR 21 VERSION_MINOR 0 -VERSION_TAG ALPHA2 -VERSION_CODE 20.90.201 -ADDON_API 20.90.201 +VERSION_TAG ALPHA3 +VERSION_CODE 20.90.301 +ADDON_API 20.90.301 ADDON_REPOS repository.xbmc.org|https://mirrors.kodi.tv APP_PACKAGE org.xbmc.kodi PACKAGE_IDENTITY XBMCFoundation.Kodi From 6060ec057e7a4375a021862a9887a92935c431cd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 7 Sep 2023 19:41:02 +0200 Subject: [PATCH 192/811] [video] Fix 'Scan to library' on a directory containing a movie file doesn't work anymore. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 2a2a482bd203d..2d9d522a05a31 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -263,8 +263,6 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) item.ClearArt(); item.GetVideoInfoTag()->m_iDbId = item.GetVideoInfoTag()->m_iIdShow; } - item.SetProperty("original_listitem_url", item.GetPath()); - item.SetPath(item.GetVideoInfoTag()->GetPath()); } else { @@ -314,7 +312,7 @@ bool CGUIWindowVideoBase::OnItemInfo(const CFileItem& fileItem) if (fileItem.m_bIsFolder) item.SetProperty("set_folder_thumb", fileItem.GetPath()); - return ShowInfo(std::make_shared(fileItem), scraper); + return ShowInfo(std::make_shared(item), scraper); } // ShowInfo is called as follows: From c54af4a82b046348e5bafdeaa2ffbac6a7cbec8b Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 8 Sep 2023 18:23:03 +1000 Subject: [PATCH 193/811] [doxygen] System.HasNetwork is not implemented. Document this System.HasNetwork is not actually implemented. Add note to doxygen comment to let users know this builtin always returns true. --- xbmc/GUIInfoManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 0c689a6cbe9b5..ac4e0f7d413c3 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -1236,6 +1236,7 @@ const infomap weather[] = {{ "isfetched", WEATHER_IS_FETCHED }, /// \anchor System_HasNetwork /// _boolean_, /// @return **True** if the Kodi host has a network available. +/// @note This feature is NOT implemented. Always returns true ///

/// } /// \table_row3{ `System.HasMediadvd`, From f160d52babaac78404f7c4cc5d6bba19a835aaab Mon Sep 17 00:00:00 2001 From: Dobroslaw Kijowski Date: Fri, 1 Sep 2023 13:19:51 +0200 Subject: [PATCH 194/811] [pipewire] Fix heap-use-after-free in AE::SINK::CAESinkPipewire::EnumerateDevicesEx * Heap-use-after-free [1] happens when EnumerateDevicesEx calls `GetName` on the registry instance. The string view containing `m_name` in CPipewireGlobal has been already freed by the pipewire library in `connection_ensure_size` function [2]. * In order to mitigate the issue copy the strings returned from pipewire. [1]: ================================================================= ==14082==ERROR: AddressSanitizer: heap-use-after-free on address 0x633000010e60 at pc 0x7effc8461003 bp 0x7effa7bb1e50 sp 0x7effa7bb15f8 READ of size 55 at 0x633000010e60 thread T19 #0 0x7effc8461002 in __interceptor_memcpy /usr/src/debug/gcc/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:899 #1 0x7effc6f11222 in std::__cxx11::basic_string, std::allocator >::_M_mutate(unsigned long, unsigned long, char const*, unsigned long) (/usr/lib/libtinyxml.so.0+0xf222) (BuildId: 2f5d236264d4d695dbe432f41e1eb46c7bc2d5d4) #2 0x7effc575a8eb in std::__cxx11::basic_string, std::allocator >::_M_replace(unsigned long, unsigned long, char const*, unsigned long) /usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/basic_string.tcc:543 #3 0x55921037c9e7 in std::enable_if > const&, std::basic_string_view > >, std::__not_ > const*, std::__cxx11::basic_string, std::allocator > const*> >, std::__not_ > const&, char const*> > >::value, std::__cxx11::basic_string, std::allocator >&>::type std::__cxx11::basic_string, std::allocator >::assign > >(std::basic_string_view > const&) /usr/include/c++/13.2.1/bits/basic_string.h:1733 #4 0x55921037b622 in std::enable_if > const&, std::basic_string_view > >, std::__not_ > const*, std::__cxx11::basic_string, std::allocator > const*> >, std::__not_ > const&, char const*> > >::value, std::__cxx11::basic_string, std::allocator >&>::type std::__cxx11::basic_string, std::allocator >::operator= > >(std::basic_string_view > const&) /usr/include/c++/13.2.1/bits/basic_string.h:925 #5 0x559213183577 in AE::SINK::CAESinkPipewire::EnumerateDevicesEx(std::vector >&, bool) /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/Sinks/pipewire/AESinkPipewire.cpp:310 #6 0x55921316198a in void std::__invoke_impl >&, bool), std::vector >&, bool>(std::__invoke_other, void (*&)(std::vector >&, bool), std::vector >&, bool&&) (/usr/lib/kodi/kodi.bin+0x623998a) (BuildId: a994426076ec43899fd3927b99c3ccdf5393f60f) #7 0x55921316015a in std::enable_if >&, bool), std::vector >&, bool>, void>::type std::__invoke_r >&, bool), std::vector >&, bool>(void (*&)(std::vector >&, bool), std::vector >&, bool&&) /usr/include/c++/13.2.1/bits/invoke.h:111 #8 0x55921315befe in std::_Function_handler >&, bool), void (*)(std::vector >&, bool)>::_M_invoke(std::_Any_data const&, std::vector >&, bool&&) /usr/include/c++/13.2.1/bits/std_function.h:290 #9 0x5592130a86bf in std::function >&, bool)>::operator()(std::vector >&, bool) const /usr/include/c++/13.2.1/bits/std_function.h:591 #10 0x5592130a6e5a in AE::CAESinkFactory::EnumerateEx(std::vector >&, bool, std::__cxx11::basic_string, std::allocator > const&) /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/AESinkFactory.cpp:101 #11 0x559213110f45 in ActiveAE::CActiveAESink::EnumerateSinkList(bool, std::__cxx11::basic_string, std::allocator >) /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp:702 #12 0x5592130bdfc2 in ActiveAE::CActiveAE::StateMachine(int, Actor::Protocol*, Actor::Message*) /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp:517 #13 0x5592130c2baa in ActiveAE::CActiveAE::Process() /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp:1070 #14 0x55921106f9e2 in CThread::Action() /home/dobo/kodi/xbmc/xbmc/threads/Thread.cpp:283 #15 0x55921106e300 in operator() /home/dobo/kodi/xbmc/xbmc/threads/Thread.cpp:152 #16 0x559211070410 in __invoke_impl)>, CThread*, std::promise > /usr/include/c++/13.2.1/bits/invoke.h:61 #17 0x5592110702c9 in __invoke)>, CThread*, std::promise > /usr/include/c++/13.2.1/bits/invoke.h:96 #18 0x5592110701fc in _M_invoke<0, 1, 2> /usr/include/c++/13.2.1/bits/std_thread.h:292 #19 0x559211070199 in operator() /usr/include/c++/13.2.1/bits/std_thread.h:299 #20 0x55921107017d in _M_run /usr/include/c++/13.2.1/bits/std_thread.h:244 #21 0x7effc56e1942 in execute_native_thread_routine /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++11/thread.cc:104 #22 0x7effc628c9ea (/usr/lib/libc.so.6+0x8c9ea) (BuildId: 316d0d3666387f0e8fb98773f51aa1801027c5ab) #23 0x7effc6310dfb (/usr/lib/libc.so.6+0x110dfb) (BuildId: 316d0d3666387f0e8fb98773f51aa1801027c5ab) 0x633000010e60 is located 67168 bytes inside of 98304-byte region [0x633000000800,0x633000018800) freed by thread T3 here: #0 0x7effc84e007a in __interceptor_realloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:85 #1 0x7effbee91c2f in connection_ensure_size ../pipewire/src/modules/module-protocol-native/connection.c:143 previously allocated by thread T3 here: #0 0x7effc84e007a in __interceptor_realloc /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_malloc_linux.cpp:85 #1 0x7effbee91c2f in connection_ensure_size ../pipewire/src/modules/module-protocol-native/connection.c:143 Thread T19 created by T0 here: #0 0x7effc844a497 in __interceptor_pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:208 #1 0x7effc56e1a29 in __gthread_create /usr/src/debug/gcc/gcc-build/x86_64-pc-linux-gnu/libstdc++-v3/include/x86_64-pc-linux-gnu/bits/gthr-default.h:663 #2 0x7effc56e1a29 in std::thread::_M_start_thread(std::unique_ptr >, void (*)()) /usr/src/debug/gcc/gcc/libstdc++-v3/src/c++11/thread.cc:172 #3 0x55921106ee30 in CThread::Create(bool) /home/dobo/kodi/xbmc/xbmc/threads/Thread.cpp:175 #4 0x5592130d96cd in ActiveAE::CActiveAE::Start() /home/dobo/kodi/xbmc/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp:2675 #5 0x5592117bc377 in CApplication::Initialize() /home/dobo/kodi/xbmc/xbmc/application/Application.cpp:610 #6 0x559211124646 in XBMC_Run /home/dobo/kodi/xbmc/xbmc/platform/xbmc.cpp:43 #7 0x55920fd30a70 in main /home/dobo/kodi/xbmc/xbmc/platform/posix/main.cpp:77 #8 0x7effc6227ccf (/usr/lib/libc.so.6+0x27ccf) (BuildId: 316d0d3666387f0e8fb98773f51aa1801027c5ab) Thread T3 created by T0 here: #0 0x7effc844a497 in __interceptor_pthread_create /usr/src/debug/gcc/gcc/libsanitizer/asan/asan_interceptors.cpp:208 #1 0x7effc7e73e5f in impl_create ../pipewire/src/pipewire/thread.c:68 SUMMARY: AddressSanitizer: heap-use-after-free /usr/src/debug/gcc/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:899 in __interceptor_memcpy Shadow bytes around the buggy address: 0x633000010b80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010c00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010c80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010d00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010d80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd =>0x633000010e00: fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd 0x633000010e80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010f00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000010f80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000011000: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd 0x633000011080: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==14082==ABORTING [2]: https://github.com/PipeWire/pipewire/blob/b5c3f217926f9066a1afbee7eb20967dd6896c56/src/modules/module-protocol-native/connection.c#L143C8-L143C15 --- .../Sinks/pipewire/PipewireGlobal.h | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h index 7b1021ae3853f..67f40c72203fe 100644 --- a/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h +++ b/xbmc/cores/AudioEngine/Sinks/pipewire/PipewireGlobal.h @@ -29,21 +29,21 @@ class CPipewireGlobal CPipewireGlobal() = default; ~CPipewireGlobal(); - CPipewireGlobal& SetName(std::string_view name) + CPipewireGlobal& SetName(const std::string& name) { m_name = name; return *this; } - std::string_view GetName() const { return m_name; } + std::string GetName() const { return m_name; } - CPipewireGlobal& SetDescription(std::string_view description) + CPipewireGlobal& SetDescription(const std::string& description) { m_description = description; return *this; } - std::string_view GetDescription() const { return m_description; } + std::string GetDescription() const { return m_description; } CPipewireGlobal& SetID(uint32_t id) { @@ -61,13 +61,13 @@ class CPipewireGlobal uint32_t GetPermissions() const { return m_permissions; } - CPipewireGlobal& SetType(std::string_view type) + CPipewireGlobal& SetType(const std::string& type) { m_type = type; return *this; } - std::string_view GetType() const { return m_type; } + std::string GetType() const { return m_type; } CPipewireGlobal& SetVersion(uint32_t version) { @@ -95,11 +95,11 @@ class CPipewireGlobal CPipewireNode& GetNode() const { return *m_node; } private: - std::string_view m_name; - std::string_view m_description; + std::string m_name; + std::string m_description; uint32_t m_id; uint32_t m_permissions; - std::string_view m_type; + std::string m_type; uint32_t m_version; std::unique_ptr m_properties; std::unique_ptr m_node; From b782f95be689b02a5645f0c22a6036f30a905b6d Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 9 Sep 2023 11:43:46 +0200 Subject: [PATCH 195/811] GUIWindowMusicNav: replace if nest with map --- xbmc/music/windows/GUIWindowMusicNav.cpp | 53 ++++++++++-------------- 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index 344931f8b49d3..f59e9fbf99336 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -921,35 +921,26 @@ void CGUIWindowMusicNav::AddSearchFolder() std::string CGUIWindowMusicNav::GetStartFolder(const std::string &dir) { - std::string lower(dir); StringUtils::ToLower(lower); - if (lower == "genres") - return "musicdb://genres/"; - else if (lower == "artists") - return "musicdb://artists/"; - else if (lower == "albums") - return "musicdb://albums/"; - else if (lower == "singles") - return "musicdb://singles/"; - else if (lower == "songs") - return "musicdb://songs/"; - else if (lower == "top100") - return "musicdb://top100/"; - else if (lower == "top100songs") - return "musicdb://top100/songs/"; - else if (lower == "top100albums") - return "musicdb://top100/albums/"; - else if (lower == "recentlyaddedalbums") - return "musicdb://recentlyaddedalbums/"; - else if (lower == "recentlyplayedalbums") - return "musicdb://recentlyplayedalbums/"; - else if (lower == "compilations") - return "musicdb://compilations/"; - else if (lower == "years") - return "musicdb://years/"; - else if (lower == "files") - return "sources://music/"; - else if (lower == "boxsets") - return "musicdb://boxsets/"; - - return CGUIWindowMusicBase::GetStartFolder(dir); + static const auto map = std::map{ + {"albums", "musicdb://albums/"}, + {"artists", "musicdb://artists/"}, + {"boxsets", "musicdb://boxsets/"}, + {"compilations", "musicdb://compilations/"}, + {"files", "sources://music/"}, + {"genres", "musicdb://genres/"}, + {"recentlyaddedalbums", "musicdb://recentlyaddedalbums/"}, + {"recentlyplayedalbums", "musicdb://recentlyplayedalbums/"}, + {"singles", "musicdb://singles/"}, + {"songs", "musicdb://songs/"}, + {"top100", "musicdb://top100/"}, + {"top100albums", "musicdb://top100/albums/"}, + {"top100songs", "musicdb://top100/songs/"}, + {"years", "musicdb://years/"}, + }; + + const auto it = map.find(StringUtils::ToLower(dir)); + if (it == map.end()) + return CGUIWindowMusicBase::GetStartFolder(dir); + else + return it->second; } From 9960cf79cf35d86a550fa4bdc7cb95edd53d6634 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 9 Sep 2023 11:43:46 +0200 Subject: [PATCH 196/811] GUIWindowVideoNav: replace if nest with map --- xbmc/video/windows/GUIWindowVideoNav.cpp | 103 +++++++++-------------- 1 file changed, 39 insertions(+), 64 deletions(-) diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index 07f23e8a5ce63..b15e82f224feb 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -1024,70 +1024,45 @@ bool CGUIWindowVideoNav::OnClick(int iItem, const std::string &player) std::string CGUIWindowVideoNav::GetStartFolder(const std::string &dir) { - std::string lower(dir); StringUtils::ToLower(lower); - if (lower == "moviegenres") - return "videodb://movies/genres/"; - else if (lower == "movietitles") - return "videodb://movies/titles/"; - else if (lower == "movieyears") - return "videodb://movies/years/"; - else if (lower == "movieactors") - return "videodb://movies/actors/"; - else if (lower == "moviedirectors") - return "videodb://movies/directors/"; - else if (lower == "moviestudios") - return "videodb://movies/studios/"; - else if (lower == "moviesets") - return "videodb://movies/sets/"; - else if (lower == "moviecountries") - return "videodb://movies/countries/"; - else if (lower == "movietags") - return "videodb://movies/tags/"; - else if (lower == "movies") - return "videodb://movies/"; - else if (lower == "tvshowgenres") - return "videodb://tvshows/genres/"; - else if (lower == "tvshowtitles") - return "videodb://tvshows/titles/"; - else if (lower == "tvshowyears") - return "videodb://tvshows/years/"; - else if (lower == "tvshowactors") - return "videodb://tvshows/actors/"; - else if (lower == "tvshowstudios") - return "videodb://tvshows/studios/"; - else if (lower == "tvshowtags") - return "videodb://tvshows/tags/"; - else if (lower == "tvshows") - return "videodb://tvshows/"; - else if (lower == "musicvideogenres") - return "videodb://musicvideos/genres/"; - else if (lower == "musicvideotitles") - return "videodb://musicvideos/titles/"; - else if (lower == "musicvideoyears") - return "videodb://musicvideos/years/"; - else if (lower == "musicvideoartists") - return "videodb://musicvideos/artists/"; - else if (lower == "musicvideoalbums") - return "videodb://musicvideos/albums/"; - else if (lower == "musicvideodirectors") - return "videodb://musicvideos/directors/"; - else if (lower == "musicvideostudios") - return "videodb://musicvideos/studios/"; - else if (lower == "musicvideotags") - return "videodb://musicvideos/tags/"; - else if (lower == "musicvideos") - return "videodb://musicvideos/"; - else if (lower == "recentlyaddedmovies") - return "videodb://recentlyaddedmovies/"; - else if (lower == "recentlyaddedepisodes") - return "videodb://recentlyaddedepisodes/"; - else if (lower == "recentlyaddedmusicvideos") - return "videodb://recentlyaddedmusicvideos/"; - else if (lower == "inprogresstvshows") - return "videodb://inprogresstvshows/"; - else if (lower == "files") - return "sources://video/"; - return CGUIWindowVideoBase::GetStartFolder(dir); + static const auto map = std::map{ + {"files", "sources://video/"}, + {"inprogresstvshows", "videodb://inprogresstvshows/"}, + {"movieactors", "videodb://movies/actors/"}, + {"moviecountries", "videodb://movies/countries/"}, + {"moviedirectors", "videodb://movies/directors/"}, + {"moviegenres", "videodb://movies/genres/"}, + {"movies", "videodb://movies/"}, + {"moviesets", "videodb://movies/sets/"}, + {"moviestudios", "videodb://movies/studios/"}, + {"movietags", "videodb://movies/tags/"}, + {"movietitles", "videodb://movies/titles/"}, + {"movieyears", "videodb://movies/years/"}, + {"musicvideoalbums", "videodb://musicvideos/albums/"}, + {"musicvideoartists", "videodb://musicvideos/artists/"}, + {"musicvideodirectors", "videodb://musicvideos/directors/"}, + {"musicvideogenres", "videodb://musicvideos/genres/"}, + {"musicvideos", "videodb://musicvideos/"}, + {"musicvideostudios", "videodb://musicvideos/studios/"}, + {"musicvideotags", "videodb://musicvideos/tags/"}, + {"musicvideotitles", "videodb://musicvideos/titles/"}, + {"musicvideoyears", "videodb://musicvideos/years/"}, + {"recentlyaddedepisodes", "videodb://recentlyaddedepisodes/"}, + {"recentlyaddedmovies", "videodb://recentlyaddedmovies/"}, + {"recentlyaddedmusicvideos", "videodb://recentlyaddedmusicvideos/"}, + {"tvshowactors", "videodb://tvshows/actors/"}, + {"tvshowgenres", "videodb://tvshows/genres/"}, + {"tvshows", "videodb://tvshows/"}, + {"tvshowstudios", "videodb://tvshows/studios/"}, + {"tvshowtags", "videodb://tvshows/tags/"}, + {"tvshowtitles", "videodb://tvshows/titles/"}, + {"tvshowyears", "videodb://tvshows/years/"}, + }; + + const auto it = map.find(StringUtils::ToLower(dir)); + if (it == map.end()) + return CGUIWindowVideoBase::GetStartFolder(dir); + else + return it->second; } bool CGUIWindowVideoNav::ApplyWatchedFilter(CFileItemList &items) From 5f5f2ccacfb407f832f35b42827cdb664f050752 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 9 Sep 2023 12:27:59 +0200 Subject: [PATCH 197/811] GUIWindowVideoNav::Search: use callback table and for_each --- xbmc/video/windows/GUIWindowVideoNav.cpp | 131 ++++++++++++++--------- 1 file changed, 79 insertions(+), 52 deletions(-) diff --git a/xbmc/video/windows/GUIWindowVideoNav.cpp b/xbmc/video/windows/GUIWindowVideoNav.cpp index b15e82f224feb..0a626e90d2da4 100644 --- a/xbmc/video/windows/GUIWindowVideoNav.cpp +++ b/xbmc/video/windows/GUIWindowVideoNav.cpp @@ -579,62 +579,89 @@ bool CGUIWindowVideoNav::GetFilteredItems(const std::string &filter, CFileItemLi void CGUIWindowVideoNav::DoSearch(const std::string& strSearch, CFileItemList& items) { CFileItemList tempItems; - const std::string& strGenre = g_localizeStrings.Get(515); // Genre - const std::string& strActor = g_localizeStrings.Get(20337); // Actor - const std::string& strDirector = g_localizeStrings.Get(20339); // Director - //get matching names - m_database.GetMoviesByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20338) + "] ", items); - - m_database.GetEpisodesByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20359) + "] ", items); - - m_database.GetTvShowsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20364) + "] ", items); - - m_database.GetMusicVideosByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20391) + "] ", items); - - m_database.GetMusicVideosByAlbum(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(558) + "] ", items); - - // get matching genres - m_database.GetMovieGenresByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20342) + "] ", items); - - m_database.GetTvShowGenresByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20343) + "] ", items); - - m_database.GetMusicVideoGenresByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strGenre + " - " + g_localizeStrings.Get(20389) + "] ", items); - - //get actors/artists - m_database.GetMovieActorsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20342) + "] ", items); - - m_database.GetTvShowsActorsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20343) + "] ", items); - - m_database.GetMusicVideoArtistsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strActor + " - " + g_localizeStrings.Get(20389) + "] ", items); - - //directors - m_database.GetMovieDirectorsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20342) + "] ", items); - - m_database.GetTvShowsDirectorsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20343) + "] ", items); + struct Entry + { + int id; + std::function func; + int prefix = 0; + }; - m_database.GetMusicVideoDirectorsByName(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + strDirector + " - " + g_localizeStrings.Get(20389) + "] ", items); + const auto entries = std::array{ + Entry{20338, + [&strSearch, &tempItems, this]() { m_database.GetMoviesByName(strSearch, tempItems); }}, + Entry{20359, [&strSearch, &tempItems, + this]() { m_database.GetEpisodesByName(strSearch, tempItems); }}, + Entry{20364, [&strSearch, &tempItems, + this]() { m_database.GetTvShowsByName(strSearch, tempItems); }}, + Entry{20391, [&strSearch, &tempItems, + this]() { m_database.GetMusicVideosByName(strSearch, tempItems); }}, + Entry{558, [&strSearch, &tempItems, + this]() { m_database.GetMusicVideosByAlbum(strSearch, tempItems); }}, + Entry{20342, + [&strSearch, &tempItems, this]() { + m_database.GetMovieGenresByName(strSearch, tempItems); + }, + 515}, + Entry{20343, + [&strSearch, &tempItems, this]() { + m_database.GetTvShowGenresByName(strSearch, tempItems); + }, + 515}, + Entry{20389, + [&strSearch, &tempItems, this]() { + m_database.GetMusicVideoGenresByName(strSearch, tempItems); + }, + 515}, + Entry{20342, + [&strSearch, &tempItems, this]() { + m_database.GetMovieActorsByName(strSearch, tempItems); + }, + 20337}, + Entry{20343, + [&strSearch, &tempItems, this]() { + m_database.GetTvShowsActorsByName(strSearch, tempItems); + }, + 20337}, + Entry{20389, + [&strSearch, &tempItems, this]() { + m_database.GetMusicVideoArtistsByName(strSearch, tempItems); + }, + 20337}, + Entry{20342, + [&strSearch, &tempItems, this]() { + m_database.GetMovieDirectorsByName(strSearch, tempItems); + }, + 20339}, + Entry{20343, + [&strSearch, &tempItems, this]() { + m_database.GetTvShowsDirectorsByName(strSearch, tempItems); + }, + 20339}, + Entry{20389, + [&strSearch, &tempItems, this]() { + m_database.GetMusicVideoDirectorsByName(strSearch, tempItems); + }, + 20339}, + Entry{20365, [&strSearch, &tempItems, + this]() { m_database.GetEpisodesByPlot(strSearch, tempItems); }}, + Entry{20323, + [&strSearch, &tempItems, this]() { m_database.GetMoviesByPlot(strSearch, tempItems); }}, + }; - //plot - m_database.GetEpisodesByPlot(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20365) + "] ", items); + std::for_each(entries.begin(), entries.end(), [this, &items, &tempItems](const auto& entry) { + entry.func(); + std::string msg; + if (entry.prefix > 0) + { + msg = fmt::format("[{} - {}] ", g_localizeStrings.Get(entry.prefix), + g_localizeStrings.Get(entry.id)); + } + else + msg = fmt::format("[{}] ", g_localizeStrings.Get(entry.id)); - m_database.GetMoviesByPlot(strSearch, tempItems); - AppendAndClearSearchItems(tempItems, "[" + g_localizeStrings.Get(20323) + "] ", items); + this->AppendAndClearSearchItems(tempItems, msg, items); + }); } void CGUIWindowVideoNav::PlayItem(int iItem) From 83b371a2216106623fe2885ea35a525f10044533 Mon Sep 17 00:00:00 2001 From: Arne Morten Kvarving Date: Sat, 9 Sep 2023 12:37:54 +0200 Subject: [PATCH 198/811] GUIWindowMusicNav: replace some if nests with switch --- xbmc/music/windows/GUIWindowMusicNav.cpp | 116 ++++++++++++++--------- 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/xbmc/music/windows/GUIWindowMusicNav.cpp b/xbmc/music/windows/GUIWindowMusicNav.cpp index f59e9fbf99336..530e89bedfbd6 100644 --- a/xbmc/music/windows/GUIWindowMusicNav.cpp +++ b/xbmc/music/windows/GUIWindowMusicNav.cpp @@ -389,57 +389,81 @@ bool CGUIWindowMusicNav::GetDirectory(const std::string &strDirectory, CFileItem { CVideoDatabaseDirectory dir; VIDEODATABASEDIRECTORY::NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); - if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_MUSICVIDEOS || - node == VIDEODATABASEDIRECTORY::NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS) - items.SetContent("musicvideos"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE) - items.SetContent("genres"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_COUNTRY) - items.SetContent("countries"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR) - items.SetContent("artists"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_DIRECTOR) - items.SetContent("directors"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_STUDIO) - items.SetContent("studios"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_YEAR) - items.SetContent("years"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_MUSICVIDEOS_ALBUM) - items.SetContent("albums"); - else if (node == VIDEODATABASEDIRECTORY::NODE_TYPE_TAGS) - items.SetContent("tags"); - else - items.SetContent(""); + switch (node) + { + case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_MUSICVIDEOS: + case VIDEODATABASEDIRECTORY::NODE_TYPE_RECENTLY_ADDED_MUSICVIDEOS: + items.SetContent("musicvideos"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE: + items.SetContent("genres"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_COUNTRY: + items.SetContent("countries"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR: + items.SetContent("artists"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_DIRECTOR: + items.SetContent("directors"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_STUDIO: + items.SetContent("studios"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_YEAR: + items.SetContent("years"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_MUSICVIDEOS_ALBUM: + items.SetContent("albums"); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_TAGS: + items.SetContent("tags"); + break; + default: + items.SetContent(""); + break; + } } else if (StringUtils::StartsWithNoCase(strDirectory, "musicdb://") || items.IsMusicDb()) { CMusicDatabaseDirectory dir; NODE_TYPE node = dir.GetDirectoryChildType(items.GetPath()); - if (node == NODE_TYPE_ALBUM || - node == NODE_TYPE_ALBUM_RECENTLY_ADDED || - node == NODE_TYPE_ALBUM_RECENTLY_PLAYED || - node == NODE_TYPE_ALBUM_TOP100 || - node == NODE_TYPE_DISC) // ! @todo: own content type "discs"?? - items.SetContent("albums"); - else if (node == NODE_TYPE_ARTIST) - items.SetContent("artists"); - else if (node == NODE_TYPE_SONG || - node == NODE_TYPE_SONG_TOP100 || - node == NODE_TYPE_SINGLES || - node == NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS || - node == NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS || - node == NODE_TYPE_ALBUM_TOP100_SONGS) - items.SetContent("songs"); - else if (node == NODE_TYPE_GENRE) - items.SetContent("genres"); - else if (node == NODE_TYPE_SOURCE) - items.SetContent("sources"); - else if (node == NODE_TYPE_ROLE) - items.SetContent("roles"); - else if (node == NODE_TYPE_YEAR) - items.SetContent("years"); - else - items.SetContent(""); + switch (node) + { + case NODE_TYPE_ALBUM: + case NODE_TYPE_ALBUM_RECENTLY_ADDED: + case NODE_TYPE_ALBUM_RECENTLY_PLAYED: + case NODE_TYPE_ALBUM_TOP100: + case NODE_TYPE_DISC: // ! @todo: own content type "discs"?? + items.SetContent("albums"); + break; + case NODE_TYPE_ARTIST: + items.SetContent("artists"); + break; + case NODE_TYPE_SONG: + case NODE_TYPE_SONG_TOP100: + case NODE_TYPE_SINGLES: + case NODE_TYPE_ALBUM_RECENTLY_ADDED_SONGS: + case NODE_TYPE_ALBUM_RECENTLY_PLAYED_SONGS: + case NODE_TYPE_ALBUM_TOP100_SONGS: + items.SetContent("songs"); + break; + case NODE_TYPE_GENRE: + items.SetContent("genres"); + break; + case NODE_TYPE_SOURCE: + items.SetContent("sources"); + break; + case NODE_TYPE_ROLE: + items.SetContent("roles"); + break; + case NODE_TYPE_YEAR: + items.SetContent("years"); + break; + default: + items.SetContent(""); + break; + } } else if (items.IsPlayList()) items.SetContent("songs"); From 13d7d7a93ed9163252a5574102621e28007993f7 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 5 Sep 2023 12:01:43 +0100 Subject: [PATCH 199/811] [macos] Rename NetworkOsx to NetworkMacOS. Move to objc. --- .../darwin/osx/network/CMakeLists.txt | 6 ++--- .../network/{NetworkOsx.h => NetworkMacOS.h} | 16 +++++------ .../{NetworkOsx.cpp => NetworkMacOS.mm} | 27 +++++++++---------- 3 files changed, 24 insertions(+), 25 deletions(-) rename xbmc/platform/darwin/osx/network/{NetworkOsx.h => NetworkMacOS.h} (65%) rename xbmc/platform/darwin/osx/network/{NetworkOsx.cpp => NetworkMacOS.mm} (86%) diff --git a/xbmc/platform/darwin/osx/network/CMakeLists.txt b/xbmc/platform/darwin/osx/network/CMakeLists.txt index c6686b30d84cb..cc00bf57ba30e 100644 --- a/xbmc/platform/darwin/osx/network/CMakeLists.txt +++ b/xbmc/platform/darwin/osx/network/CMakeLists.txt @@ -1,4 +1,4 @@ -set(SOURCES NetworkOsx.cpp) -set(HEADERS NetworkOsx.h) +set(SOURCES NetworkMacOS.mm) +set(HEADERS NetworkMacOS.h) -core_add_library(platform_osx_network) +core_add_library(platform_macos_network) diff --git a/xbmc/platform/darwin/osx/network/NetworkOsx.h b/xbmc/platform/darwin/osx/network/NetworkMacOS.h similarity index 65% rename from xbmc/platform/darwin/osx/network/NetworkOsx.h rename to xbmc/platform/darwin/osx/network/NetworkMacOS.h index 0d9884d186e42..450d68537598a 100644 --- a/xbmc/platform/darwin/osx/network/NetworkOsx.h +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.h @@ -13,23 +13,23 @@ #include #include -class CNetworkInterfaceOsx : public CNetworkInterfacePosix +class CNetworkInterfaceMacOS : public CNetworkInterfacePosix { public: - CNetworkInterfaceOsx(CNetworkPosix* network, - const std::string& interfaceName, - char interfaceMacAddrRaw[6]); - ~CNetworkInterfaceOsx() override = default; + CNetworkInterfaceMacOS(CNetworkPosix* network, + const std::string& interfaceName, + char interfaceMacAddrRaw[6]); + ~CNetworkInterfaceMacOS() override = default; std::string GetCurrentDefaultGateway() const override; bool GetHostMacAddress(unsigned long host, std::string& mac) const override; }; -class CNetworkOsx : public CNetworkPosix +class CNetworkMacOS : public CNetworkPosix { public: - CNetworkOsx(); - ~CNetworkOsx() override = default; + CNetworkMacOS(); + ~CNetworkMacOS() override = default; bool PingHost(unsigned long host, unsigned int timeout_ms = 2000) override; std::vector GetNameServers() override; diff --git a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm similarity index 86% rename from xbmc/platform/darwin/osx/network/NetworkOsx.cpp rename to xbmc/platform/darwin/osx/network/NetworkMacOS.mm index 61b74e5f711f7..6be043e8cf122 100644 --- a/xbmc/platform/darwin/osx/network/NetworkOsx.cpp +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm @@ -6,7 +6,7 @@ * See LICENSES/README.md for more information. */ -#include "NetworkOsx.h" +#include "NetworkmacOS.h" #include "utils/StringUtils.h" #include "utils/log.h" @@ -25,14 +25,14 @@ #include #include -CNetworkInterfaceOsx::CNetworkInterfaceOsx(CNetworkPosix* network, - const std::string& interfaceName, - char interfaceMacAddrRaw[6]) +CNetworkInterfaceMacOS::CNetworkInterfaceMacOS(CNetworkPosix* network, + const std::string& interfaceName, + char interfaceMacAddrRaw[6]) : CNetworkInterfacePosix(network, interfaceName, interfaceMacAddrRaw) { } -std::string CNetworkInterfaceOsx::GetCurrentDefaultGateway() const +std::string CNetworkInterfaceMacOS::GetCurrentDefaultGateway() const { std::string result; @@ -56,7 +56,7 @@ std::string CNetworkInterfaceOsx::GetCurrentDefaultGateway() const return result; } -bool CNetworkInterfaceOsx::GetHostMacAddress(unsigned long host_ip, std::string& mac) const +bool CNetworkInterfaceMacOS::GetHostMacAddress(unsigned long host_ip, std::string& mac) const { bool ret = false; size_t needed; @@ -100,16 +100,15 @@ bool CNetworkInterfaceOsx::GetHostMacAddress(unsigned long host_ip, std::string& std::unique_ptr CNetworkBase::GetNetwork() { - return std::make_unique(); + return std::make_unique(); } -CNetworkOsx::CNetworkOsx() : CNetworkPosix() +CNetworkMacOS::CNetworkMacOS() : CNetworkPosix() { queryInterfaceList(); } - -void CNetworkOsx::GetMacAddress(const std::string& interfaceName, char rawMac[6]) +void CNetworkMacOS::GetMacAddress(const std::string& interfaceName, char rawMac[6]) { memset(rawMac, 0, 6); @@ -149,7 +148,7 @@ void CNetworkOsx::GetMacAddress(const std::string& interfaceName, char rawMac[6] freeifaddrs(list); } -void CNetworkOsx::queryInterfaceList() +void CNetworkMacOS::queryInterfaceList() { char macAddrRaw[6]; m_interfaces.clear(); @@ -171,13 +170,13 @@ void CNetworkOsx::queryInterfaceList() if (macAddrRaw[0] || macAddrRaw[1] || macAddrRaw[2] || macAddrRaw[3] || macAddrRaw[4] || macAddrRaw[5]) // Add the interface. - m_interfaces.push_back(new CNetworkInterfaceOsx(this, cur->ifa_name, macAddrRaw)); + m_interfaces.push_back(new CNetworkInterfaceMacOS(this, cur->ifa_name, macAddrRaw)); } freeifaddrs(list); } -std::vector CNetworkOsx::GetNameServers() +std::vector CNetworkMacOS::GetNameServers() { std::vector result; @@ -222,7 +221,7 @@ std::vector CNetworkOsx::GetNameServers() return result; } -bool CNetworkOsx::PingHost(unsigned long remote_ip, unsigned int timeout_ms) +bool CNetworkMacOS::PingHost(unsigned long remote_ip, unsigned int timeout_ms) { char cmd_line[64]; From 39330b90efc8b61ed82e55c1f37cd40d80d5dfeb Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 9 Sep 2023 10:29:10 +0100 Subject: [PATCH 200/811] [macos][Network] Fix warning --- xbmc/platform/darwin/osx/network/NetworkMacOS.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm index 6be043e8cf122..7cfd83af42243 100644 --- a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm @@ -226,7 +226,7 @@ char cmd_line[64]; struct in_addr host_ip; - host_ip.s_addr = remote_ip; + host_ip.s_addr = static_cast(remote_ip); snprintf(cmd_line, sizeof(cmd_line), "ping -c 1 -t %d %s", timeout_ms / 1000 + (timeout_ms % 1000) != 0, inet_ntoa(host_ip)); From 5e0450709b12dcb143d307996a28bdf39302689c Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 9 Sep 2023 10:30:27 +0100 Subject: [PATCH 201/811] [macOS][network] Use SystemConfiguration for DNS server list query --- .../darwin/osx/network/NetworkMacOS.mm | 52 +++++++++---------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm index 7cfd83af42243..6f85772cbd58e 100644 --- a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm @@ -14,6 +14,8 @@ #include #include +#import +#import #include #include #include @@ -178,46 +180,40 @@ std::vector CNetworkMacOS::GetNameServers() { - std::vector result; - - FILE* pipe = popen("scutil --dns | grep -F \"nameserver\" | uniq | tail -n2", "r"); - - if (pipe) + __block std::vector result; + @autoreleasepool { - int fineNoPipe = ::fileno(pipe); - - struct pollfd pollFd; - pollFd.fd = fineNoPipe; - pollFd.events = POLLIN; - pollFd.revents = 0; + // Create an SCPreferencesRef object for the system preferences + SCPreferencesRef preferences = SCPreferencesCreate(nullptr, CFSTR("GetDNSInfo"), nullptr); + if (!preferences) + { + CLog::Log(LOGWARNING, "Unable to determine nameservers"); + return result; + } - int pollResult = poll(&pollFd, 1, 1000); // Poll for 1 second - if (pollResult > 0 && (pollFd.revents & POLLIN)) + // Get the current DNS configuration from system preferences + CFDictionaryRef currentDNSConfig = + static_cast(SCPreferencesGetValue(preferences, kSCPrefNetworkServices)); + if (currentDNSConfig) { - char buffer[256] = {'\0'}; - if (fgets(buffer, sizeof(buffer), pipe)) - { - // result looks like this - > ' nameserver[0] : 192.168.1.1' - // 2 blank spaces + 13 in 'nameserver[0]' + blank + ':' + blank == 18 :) - const unsigned int nameserverLineSize = 18; - std::vector tmpStr = StringUtils::Split(buffer, "\n"); - for (const std::string& str : tmpStr) + NSDictionary* networkServices = (__bridge NSDictionary*)currentDNSConfig; + [networkServices enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { + NSDictionary* service = networkServices[key]; + NSDictionary* dnsSettings = service[static_cast(kSCEntNetDNS)]; + NSArray* dnsServers = dnsSettings[static_cast(kSCPropNetDNSServerAddresses)]; + for (NSString* dnsServer in dnsServers) { - if (str.length() >= nameserverLineSize) - { - result.push_back(str.substr(nameserverLineSize)); - } + result.emplace_back(dnsServer.UTF8String); } - } + }]; + CFRelease(preferences); } - pclose(pipe); } if (result.empty()) { CLog::Log(LOGWARNING, "Unable to determine nameservers"); } - return result; } From c91ca42cf60a1fb5b35b7bcbd35e215de6c9372c Mon Sep 17 00:00:00 2001 From: gemedet Date: Sat, 9 Sep 2023 23:00:38 -0700 Subject: [PATCH 202/811] update aspect ratio of InfoWallMovieLayout --- addons/skin.estuary/xml/Includes_Home.xml | 2 +- addons/skin.estuary/xml/View_51_Poster.xml | 4 ++-- addons/skin.estuary/xml/View_54_InfoWall.xml | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/addons/skin.estuary/xml/Includes_Home.xml b/addons/skin.estuary/xml/Includes_Home.xml index f608f49bfe2e9..7924a46f0ecca 100644 --- a/addons/skin.estuary/xml/Includes_Home.xml +++ b/addons/skin.estuary/xml/Includes_Home.xml @@ -41,7 +41,7 @@ 115 Integer.IsGreater(Container($PARAM[list_id]).NumItems,0) | Container($PARAM[list_id]).IsUpdating 0 - 503 + 518 $PARAM[onclick_action] diff --git a/addons/skin.estuary/xml/View_51_Poster.xml b/addons/skin.estuary/xml/View_51_Poster.xml index b4a94a696abc6..f397859a0d613 100644 --- a/addons/skin.estuary/xml/View_51_Poster.xml +++ b/addons/skin.estuary/xml/View_51_Poster.xml @@ -15,9 +15,9 @@ 720 0 - 4 + 8 100% - 410 + 415 WindowOpen WindowClose VisibleChange diff --git a/addons/skin.estuary/xml/View_54_InfoWall.xml b/addons/skin.estuary/xml/View_54_InfoWall.xml index 86ecc8a8b8f36..a4f49dcd5c305 100644 --- a/addons/skin.estuary/xml/View_54_InfoWall.xml +++ b/addons/skin.estuary/xml/View_54_InfoWall.xml @@ -238,7 +238,7 @@ 15 -10 290 - 400 + 415 dialogs/dialog-bg-nobo.png overlays/shadow.png 20 @@ -247,7 +247,7 @@ 15 -10 290 - 400 + 415 colors/grey.png 20 $PARAM[focused] @@ -281,7 +281,7 @@ 11 -14 298 - 408 + 423 colors/grey.png overlays/shadow.png 20 @@ -292,7 +292,7 @@ 15 -10 290 - 400 + 415 $INFO[ListItem.Art(poster)] scale overlays/shadow.png @@ -300,7 +300,7 @@ 35 - 290 + 305 80 80 overlays/overlay-bg.png @@ -311,7 +311,7 @@ String.IsEqual(ListItem.DBtype,tvshow) | String.IsEqual(ListItem.DBtype,set) | String.IsEqual(ListItem.DBType,season) 35 - 320 + 335 250 50 overlays/overlayfade.png @@ -319,7 +319,7 @@ 0 - 340 + 355 244 font20_title @@ -328,7 +328,7 @@ 254 - 343 + 358 24 24 lists/played-total.png @@ -336,7 +336,7 @@ 35 - 338 + 353 32 32 $VAR[WallWatchedIconVar] @@ -348,7 +348,7 @@ 35 - 350 + 365 250 1 From dc5a21395db374af99366aa66aee39981e8f8072 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 9 Sep 2023 20:40:25 +0100 Subject: [PATCH 203/811] [info] Split platform code for GPUInfo --- xbmc/guilib/guiinfo/SystemGUIInfo.cpp | 51 ++------------ xbmc/guilib/guiinfo/SystemGUIInfo.h | 6 +- xbmc/platform/android/CMakeLists.txt | 2 + xbmc/platform/android/GPUInfoAndroid.cpp | 24 +++++++ xbmc/platform/android/GPUInfoAndroid.h | 22 +++++++ .../platform/darwin/ios-common/CMakeLists.txt | 2 + .../darwin/ios-common/GPUInfoDarwinEmbed.cpp | 24 +++++++ .../darwin/ios-common/GPUInfoDarwinEmbed.h | 22 +++++++ xbmc/platform/darwin/osx/CMakeLists.txt | 2 + xbmc/platform/darwin/osx/GPUInfoMacOS.cpp | 32 +++++++++ xbmc/platform/darwin/osx/GPUInfoMacOS.h | 22 +++++++ xbmc/platform/freebsd/CMakeLists.txt | 2 + xbmc/platform/freebsd/GPUInfoFreebsd.cpp | 24 +++++++ xbmc/platform/freebsd/GPUInfoFreebsd.h | 22 +++++++ xbmc/platform/linux/CMakeLists.txt | 2 + xbmc/platform/linux/GPUInfoLinux.cpp | 24 +++++++ xbmc/platform/linux/GPUInfoLinux.h | 22 +++++++ xbmc/platform/posix/CMakeLists.txt | 2 + xbmc/platform/posix/GPUInfoPosix.cpp | 52 +++++++++++++++ xbmc/platform/posix/GPUInfoPosix.h | 22 +++++++ xbmc/platform/win10/CMakeLists.txt | 2 + xbmc/platform/win10/GPUInfoWin10.cpp | 35 ++++++++++ xbmc/platform/win10/GPUInfoWin10.h | 25 +++++++ xbmc/platform/win32/CMakeLists.txt | 2 + xbmc/platform/win32/GPUInfoWin32.cpp | 35 ++++++++++ xbmc/platform/win32/GPUInfoWin32.h | 25 +++++++ xbmc/utils/CMakeLists.txt | 2 + xbmc/utils/GpuInfo.cpp | 35 ++++++++++ xbmc/utils/GpuInfo.h | 66 +++++++++++++++++++ 29 files changed, 562 insertions(+), 46 deletions(-) create mode 100644 xbmc/platform/android/GPUInfoAndroid.cpp create mode 100644 xbmc/platform/android/GPUInfoAndroid.h create mode 100644 xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.cpp create mode 100644 xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.h create mode 100644 xbmc/platform/darwin/osx/GPUInfoMacOS.cpp create mode 100644 xbmc/platform/darwin/osx/GPUInfoMacOS.h create mode 100644 xbmc/platform/freebsd/GPUInfoFreebsd.cpp create mode 100644 xbmc/platform/freebsd/GPUInfoFreebsd.h create mode 100644 xbmc/platform/linux/GPUInfoLinux.cpp create mode 100644 xbmc/platform/linux/GPUInfoLinux.h create mode 100644 xbmc/platform/posix/GPUInfoPosix.cpp create mode 100644 xbmc/platform/posix/GPUInfoPosix.h create mode 100644 xbmc/platform/win10/GPUInfoWin10.cpp create mode 100644 xbmc/platform/win10/GPUInfoWin10.h create mode 100644 xbmc/platform/win32/GPUInfoWin32.cpp create mode 100644 xbmc/platform/win32/GPUInfoWin32.h create mode 100644 xbmc/utils/GpuInfo.cpp create mode 100644 xbmc/utils/GpuInfo.h diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp index 408522ea3e5fa..d6a428fab675c 100644 --- a/xbmc/guilib/guiinfo/SystemGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/SystemGUIInfo.cpp @@ -20,10 +20,6 @@ #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" -#include "network/Network.h" -#if defined(TARGET_DARWIN_OSX) -#include "platform/darwin/osx/smc.h" -#endif #include "guilib/guiinfo/GUIInfo.h" #include "guilib/guiinfo/GUIInfoHelper.h" #include "guilib/guiinfo/GUIInfoLabels.h" @@ -38,6 +34,7 @@ #include "storage/discs/IDiscDriveHandler.h" #include "utils/AlarmClock.h" #include "utils/CPUInfo.h" +#include "utils/GpuInfo.h" #include "utils/HDRCapabilities.h" #include "utils/MemUtils.h" #include "utils/StringUtils.h" @@ -50,7 +47,7 @@ using namespace KODI::GUILIB; using namespace KODI::GUILIB::GUIINFO; CSystemGUIInfo::CSystemGUIInfo() -: m_lastSysHeatInfoTime(-SYSTEM_HEAT_UPDATE_INTERVAL) + : m_gpuInfo(CGPUInfo::GetGPUInfo()), m_lastSysHeatInfoTime(-SYSTEM_HEAT_UPDATE_INTERVAL) { } @@ -59,10 +56,11 @@ std::string CSystemGUIInfo::GetSystemHeatInfo(int info) const if (CTimeUtils::GetFrameTime() - m_lastSysHeatInfoTime >= SYSTEM_HEAT_UPDATE_INTERVAL) { m_lastSysHeatInfoTime = CTimeUtils::GetFrameTime(); -#if defined(TARGET_POSIX) CServiceBroker::GetCPUInfo()->GetTemperature(m_cpuTemp); - m_gpuTemp = GetGPUTemperature(); -#endif + if (m_gpuInfo) + { + m_gpuInfo->GetTemperature(m_gpuTemp); + } } std::string text; @@ -89,43 +87,6 @@ std::string CSystemGUIInfo::GetSystemHeatInfo(int info) const return text; } -CTemperature CSystemGUIInfo::GetGPUTemperature() const -{ - int value = 0; - char scale = 0; - -#if defined(TARGET_DARWIN_OSX) - value = SMCGetTemperature(SMC_KEY_GPU_TEMP); - auto temperature = CTemperature::CreateFromCelsius(value); - if (temperature == CTemperature::CreateFromCelsius(0.0)) - { - temperature.SetValid(false); - } - return temperature; -#elif defined(TARGET_WINDOWS_STORE) - return CTemperature::CreateFromCelsius(0); -#else - std::string cmd = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_gpuTempCmd; - int ret = 0; - FILE* p = NULL; - - if (cmd.empty() || !(p = popen(cmd.c_str(), "r"))) - return CTemperature(); - - ret = fscanf(p, "%d %c", &value, &scale); - pclose(p); - - if (ret != 2) - return CTemperature(); -#endif - - if (scale == 'C' || scale == 'c') - return CTemperature::CreateFromCelsius(value); - if (scale == 'F' || scale == 'f') - return CTemperature::CreateFromFahrenheit(value); - return CTemperature(); -} - void CSystemGUIInfo::UpdateFPS() { m_frameCounter++; diff --git a/xbmc/guilib/guiinfo/SystemGUIInfo.h b/xbmc/guilib/guiinfo/SystemGUIInfo.h index ed2ea03b2ebf2..43c4f281c4105 100644 --- a/xbmc/guilib/guiinfo/SystemGUIInfo.h +++ b/xbmc/guilib/guiinfo/SystemGUIInfo.h @@ -9,8 +9,11 @@ #pragma once #include "guilib/guiinfo/GUIInfoProvider.h" +#include "utils/GpuInfo.h" #include "utils/Temperature.h" +#include + namespace KODI { namespace GUILIB @@ -37,7 +40,8 @@ class CSystemGUIInfo : public CGUIInfoProvider private: std::string GetSystemHeatInfo(int info) const; - CTemperature GetGPUTemperature() const; + + std::unique_ptr m_gpuInfo; static const int SYSTEM_HEAT_UPDATE_INTERVAL = 60000; diff --git a/xbmc/platform/android/CMakeLists.txt b/xbmc/platform/android/CMakeLists.txt index 200729cb9a716..d4d65d5ad24a1 100644 --- a/xbmc/platform/android/CMakeLists.txt +++ b/xbmc/platform/android/CMakeLists.txt @@ -1,8 +1,10 @@ set(SOURCES CPUInfoAndroid.cpp + GPUInfoAndroid.cpp MemUtils.cpp PlatformAndroid.cpp) set(HEADERS CPUInfoAndroid.h + GPUInfoAndroid.h PlatformAndroid.h) core_add_library(androidsupport) diff --git a/xbmc/platform/android/GPUInfoAndroid.cpp b/xbmc/platform/android/GPUInfoAndroid.cpp new file mode 100644 index 0000000000000..9fa41906f8df3 --- /dev/null +++ b/xbmc/platform/android/GPUInfoAndroid.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoAndroid.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoAndroid::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoAndroid::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} diff --git a/xbmc/platform/android/GPUInfoAndroid.h b/xbmc/platform/android/GPUInfoAndroid.h new file mode 100644 index 0000000000000..0dbd46890cfac --- /dev/null +++ b/xbmc/platform/android/GPUInfoAndroid.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/GPUInfoPosix.h" + +class CGPUInfoAndroid : public CGPUInfoPosix +{ +public: + CGPUInfoAndroid() = default; + ~CGPUInfoAndroid() = default; + +private: + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; +}; diff --git a/xbmc/platform/darwin/ios-common/CMakeLists.txt b/xbmc/platform/darwin/ios-common/CMakeLists.txt index 9a95136da41d4..44476d24f7d2c 100644 --- a/xbmc/platform/darwin/ios-common/CMakeLists.txt +++ b/xbmc/platform/darwin/ios-common/CMakeLists.txt @@ -4,6 +4,7 @@ set(SOURCES AnnounceReceiver.mm DarwinEmbedKeyboardView.mm DarwinEmbedNowPlayingInfoManager.mm DarwinEmbedUtils.mm + GPUInfoDarwinEmbed.cpp NSData+GZIP.m PlatformDarwinEmbedded.cpp) @@ -13,6 +14,7 @@ set(HEADERS AnnounceReceiver.h DarwinEmbedKeyboardView.h DarwinEmbedNowPlayingInfoManager.h DarwinEmbedUtils.h + GPUInfoDarwinEmbed.h NSData+GZIP.h PlatformDarwinEmbedded.h) diff --git a/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.cpp b/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.cpp new file mode 100644 index 0000000000000..a9e3b3ac0765c --- /dev/null +++ b/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoDarwinEmbed.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoDarwinEmbed::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoDarwinEmbed::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} diff --git a/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.h b/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.h new file mode 100644 index 0000000000000..7225e2ff5ed9c --- /dev/null +++ b/xbmc/platform/darwin/ios-common/GPUInfoDarwinEmbed.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/GPUInfoPosix.h" + +class CGPUInfoDarwinEmbed : public CGPUInfoPosix +{ +public: + CGPUInfoDarwinEmbed() = default; + ~CGPUInfoDarwinEmbed() = default; + +private: + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; +}; diff --git a/xbmc/platform/darwin/osx/CMakeLists.txt b/xbmc/platform/darwin/osx/CMakeLists.txt index 4e3a41de989be..01db68f8e89b8 100644 --- a/xbmc/platform/darwin/osx/CMakeLists.txt +++ b/xbmc/platform/darwin/osx/CMakeLists.txt @@ -1,11 +1,13 @@ set(SOURCES CocoaInterface.mm CPUInfoOsx.cpp + GPUInfoMacOS.cpp HotKeyController.m PlatformDarwinOSX.cpp smc.c) set(HEADERS CocoaInterface.h CPUInfoOsx.h + GPUInfoMacOS.h HotKeyController.h PlatformDarwinOSX.h smc.h) diff --git a/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp b/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp new file mode 100644 index 0000000000000..5594d9bbf2730 --- /dev/null +++ b/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoMacOS.h" + +#include "platform/darwin/osx/smc.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoMacOS::SupportsPlatformTemperature() const +{ + return true; +} + +bool CGPUInfoMacOS::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + double temperatureValue = SMCGetTemperature(SMC_KEY_GPU_TEMP); + if (temperatureValue <= 0.0) + { + return false; + } + temperature = CTemperature::CreateFromCelsius(temperatureValue); + return true; +} diff --git a/xbmc/platform/darwin/osx/GPUInfoMacOS.h b/xbmc/platform/darwin/osx/GPUInfoMacOS.h new file mode 100644 index 0000000000000..8ceb02d578a75 --- /dev/null +++ b/xbmc/platform/darwin/osx/GPUInfoMacOS.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/GPUInfoPosix.h" + +class CGPUInfoMacOS : public CGPUInfoPosix +{ +public: + CGPUInfoMacOS() = default; + ~CGPUInfoMacOS() = default; + +private: + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; +}; diff --git a/xbmc/platform/freebsd/CMakeLists.txt b/xbmc/platform/freebsd/CMakeLists.txt index 9b8782f4cdad4..6a8834737a0c1 100644 --- a/xbmc/platform/freebsd/CMakeLists.txt +++ b/xbmc/platform/freebsd/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES ../linux/AppParamParserLinux.cpp CPUInfoFreebsd.cpp + GPUInfoFreebsd.cpp OptionalsReg.cpp ../linux/OptionalsReg.cpp ../linux/TimeUtils.cpp @@ -8,6 +9,7 @@ set(SOURCES ../linux/AppParamParserLinux.cpp set(HEADERS ../linux/AppParamParserLinux.cpp CPUInfoFreebsd.h + GPUInfoFreebsd.h OptionalsReg.h ../linux/OptionalsReg.h ../linux/TimeUtils.h diff --git a/xbmc/platform/freebsd/GPUInfoFreebsd.cpp b/xbmc/platform/freebsd/GPUInfoFreebsd.cpp new file mode 100644 index 0000000000000..030b652d23fc0 --- /dev/null +++ b/xbmc/platform/freebsd/GPUInfoFreebsd.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoFreebsd.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoFreebsd::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoFreebsd::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} diff --git a/xbmc/platform/freebsd/GPUInfoFreebsd.h b/xbmc/platform/freebsd/GPUInfoFreebsd.h new file mode 100644 index 0000000000000..62016ecc4079c --- /dev/null +++ b/xbmc/platform/freebsd/GPUInfoFreebsd.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/GPUInfoPosix.h" + +class CGPUInfoFreebsd : public CGPUInfoPosix +{ +public: + CGPUInfoFreebsd() = default; + ~CGPUInfoFreebsd() = default; + +private: + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; +}; diff --git a/xbmc/platform/linux/CMakeLists.txt b/xbmc/platform/linux/CMakeLists.txt index 54a3b25d28ff7..3c683a16335b5 100644 --- a/xbmc/platform/linux/CMakeLists.txt +++ b/xbmc/platform/linux/CMakeLists.txt @@ -1,5 +1,6 @@ set(SOURCES AppParamParserLinux.cpp CPUInfoLinux.cpp + GPUInfoLinux.cpp MemUtils.cpp OptionalsReg.cpp PlatformLinux.cpp @@ -8,6 +9,7 @@ set(SOURCES AppParamParserLinux.cpp set(HEADERS AppParamParserLinux.h CPUInfoLinux.h + GPUInfoLinux.h OptionalsReg.h PlatformLinux.h SysfsPath.h diff --git a/xbmc/platform/linux/GPUInfoLinux.cpp b/xbmc/platform/linux/GPUInfoLinux.cpp new file mode 100644 index 0000000000000..dd8585a204de6 --- /dev/null +++ b/xbmc/platform/linux/GPUInfoLinux.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoLinux.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoLinux::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoLinux::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} diff --git a/xbmc/platform/linux/GPUInfoLinux.h b/xbmc/platform/linux/GPUInfoLinux.h new file mode 100644 index 0000000000000..152068fa0fc1d --- /dev/null +++ b/xbmc/platform/linux/GPUInfoLinux.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "platform/posix/GPUInfoPosix.h" + +class CGPUInfoLinux : public CGPUInfoPosix +{ +public: + CGPUInfoLinux() = default; + ~CGPUInfoLinux() = default; + +private: + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; +}; diff --git a/xbmc/platform/posix/CMakeLists.txt b/xbmc/platform/posix/CMakeLists.txt index 2d8d4dbd63453..7907159cf9b94 100644 --- a/xbmc/platform/posix/CMakeLists.txt +++ b/xbmc/platform/posix/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES ConvUtils.cpp CPUInfoPosix.cpp Filesystem.cpp + GPUInfoPosix.cpp MessagePrinter.cpp PlatformPosix.cpp PosixMountProvider.cpp @@ -11,6 +12,7 @@ set(SOURCES ConvUtils.cpp set(HEADERS ConvUtils.h CPUInfoPosix.h + GPUInfoPosix.h PlatformDefs.h PlatformPosix.h PosixMountProvider.h diff --git a/xbmc/platform/posix/GPUInfoPosix.cpp b/xbmc/platform/posix/GPUInfoPosix.cpp new file mode 100644 index 0000000000000..1f4a957041e4a --- /dev/null +++ b/xbmc/platform/posix/GPUInfoPosix.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoPosix.h" + +#include + +bool CGPUInfoPosix::SupportsCustomTemperatureCommand() const +{ + return true; +} + +bool CGPUInfoPosix::GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const +{ + int value = 0; + char scale = 0; + int ret = 0; + FILE* p = nullptr; + + if (cmd.empty() || !(p = popen(cmd.c_str(), "r"))) + { + return false; + } + + ret = fscanf(p, "%d %c", &value, &scale); + pclose(p); + + if (ret != 2) + { + return false; + } + + if (scale == 'C' || scale == 'c') + { + temperature = CTemperature::CreateFromCelsius(value); + } + else if (scale == 'F' || scale == 'f') + { + temperature = CTemperature::CreateFromFahrenheit(value); + } + else + { + return false; + } + return true; +} diff --git a/xbmc/platform/posix/GPUInfoPosix.h b/xbmc/platform/posix/GPUInfoPosix.h new file mode 100644 index 0000000000000..b03914e73f886 --- /dev/null +++ b/xbmc/platform/posix/GPUInfoPosix.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/GpuInfo.h" + +class CGPUInfoPosix : public CGPUInfo +{ +protected: + CGPUInfoPosix() = default; + virtual ~CGPUInfoPosix() = default; + + virtual bool SupportsCustomTemperatureCommand() const override; + virtual bool GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const override; +}; diff --git a/xbmc/platform/win10/CMakeLists.txt b/xbmc/platform/win10/CMakeLists.txt index 6ddf3c8df7d6e..19bc19f0e5ced 100644 --- a/xbmc/platform/win10/CMakeLists.txt +++ b/xbmc/platform/win10/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES CPUInfoWin10.cpp input/RemoteControlXbox.cpp Environment.cpp + GPUInfoWin10.cpp Win10App.cpp MessagePrinter.cpp PlatformWin10.cpp @@ -14,6 +15,7 @@ set(SOURCES CPUInfoWin10.cpp set(HEADERS AsyncHelpers.h CPUInfoWin10.h + GPUInfoWin10.h input/RemoteControlXbox.h Win10App.h PlatformWin10.h diff --git a/xbmc/platform/win10/GPUInfoWin10.cpp b/xbmc/platform/win10/GPUInfoWin10.cpp new file mode 100644 index 0000000000000..8c6fb1a661aed --- /dev/null +++ b/xbmc/platform/win10/GPUInfoWin10.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoWin10.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoWin10::SupportsCustomTemperatureCommand() const +{ + return false; +} + +bool CGPUInfoWin10::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoWin10::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} + +bool CGPUInfoWin10::GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const +{ + return false; +} diff --git a/xbmc/platform/win10/GPUInfoWin10.h b/xbmc/platform/win10/GPUInfoWin10.h new file mode 100644 index 0000000000000..6e7d0329af464 --- /dev/null +++ b/xbmc/platform/win10/GPUInfoWin10.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/GpuInfo.h" + +class CGPUInfoWin10 : public CGPUInfo +{ +public: + CGPUInfoWin10() = default; + ~CGPUInfoWin10() = default; + +private: + bool SupportsCustomTemperatureCommand() const override; + bool SupportsPlatformTemperature() const override; + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; + bool GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const override; +}; diff --git a/xbmc/platform/win32/CMakeLists.txt b/xbmc/platform/win32/CMakeLists.txt index 57afab0e41ee8..28faedb9c0ebf 100644 --- a/xbmc/platform/win32/CMakeLists.txt +++ b/xbmc/platform/win32/CMakeLists.txt @@ -1,6 +1,7 @@ set(SOURCES CharsetConverter.cpp CPUInfoWin32.cpp Environment.cpp + GPUInfoWin32.cpp MemUtils.cpp MessagePrinter.cpp dxerr.cpp @@ -15,6 +16,7 @@ set(HEADERS CharsetConverter.h CPUInfoWin32.h dirent.h dxerr.h + GPUInfoWin32.h IMMNotificationClient.h my_ntddcdrm.h my_ntddscsi.h diff --git a/xbmc/platform/win32/GPUInfoWin32.cpp b/xbmc/platform/win32/GPUInfoWin32.cpp new file mode 100644 index 0000000000000..bdf0b0f17f70f --- /dev/null +++ b/xbmc/platform/win32/GPUInfoWin32.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GPUInfoWin32.h" + +std::unique_ptr CGPUInfo::GetGPUInfo() +{ + return std::make_unique(); +} + +bool CGPUInfoWin32::SupportsCustomTemperatureCommand() const +{ + return false; +} + +bool CGPUInfoWin32::SupportsPlatformTemperature() const +{ + return false; +} + +bool CGPUInfoWin32::GetGPUPlatformTemperature(CTemperature& temperature) const +{ + return false; +} + +bool CGPUInfoWin32::GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const +{ + return false; +} diff --git a/xbmc/platform/win32/GPUInfoWin32.h b/xbmc/platform/win32/GPUInfoWin32.h new file mode 100644 index 0000000000000..e23bccb84f39f --- /dev/null +++ b/xbmc/platform/win32/GPUInfoWin32.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/GpuInfo.h" + +class CGPUInfoWin32 : public CGPUInfo +{ +public: + CGPUInfoWin32() = default; + ~CGPUInfoWin32() = default; + +private: + bool GetGPUPlatformTemperature(CTemperature& temperature) const override; + bool GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const override; + bool SupportsCustomTemperatureCommand() const override; + bool SupportsPlatformTemperature() const override; +}; diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 327e0b43e23f3..3d2236d4f52e2 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -27,6 +27,7 @@ set(SOURCES ActorProtocol.cpp FileOperationJob.cpp FileUtils.cpp FontUtils.cpp + GpuInfo.cpp GroupUtils.cpp HTMLUtil.cpp HttpHeader.cpp @@ -111,6 +112,7 @@ set(HEADERS ActorProtocol.h FontUtils.h Geometry.h GlobalsHandling.h + GpuInfo.h GroupUtils.h HDRCapabilities.h HTMLUtil.h diff --git a/xbmc/utils/GpuInfo.cpp b/xbmc/utils/GpuInfo.cpp new file mode 100644 index 0000000000000..9d1db907bad6b --- /dev/null +++ b/xbmc/utils/GpuInfo.cpp @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GpuInfo.h" + +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +bool CGPUInfo::GetTemperature(CTemperature& temperature) const +{ + // user custom cmd takes precedence over platform implementation + if (SupportsCustomTemperatureCommand()) + { + auto cmd = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_gpuTempCmd; + if (!cmd.empty() && GetGPUTemperatureFromCommand(temperature, cmd)) + { + return true; + } + } + + if (SupportsPlatformTemperature() && GetGPUPlatformTemperature(temperature)) + { + return true; + } + + temperature = CTemperature(); + temperature.SetValid(false); + return false; +} diff --git a/xbmc/utils/GpuInfo.h b/xbmc/utils/GpuInfo.h new file mode 100644 index 0000000000000..26a71a8d28ebb --- /dev/null +++ b/xbmc/utils/GpuInfo.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2023 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#pragma once + +#include "utils/Temperature.h" + +#include +#include + +/*! \brief Class to concentrate all methods related to GPU information +* \details This is used by the Info interface to obtain the current GPU temperature +*/ +class CGPUInfo +{ +public: + CGPUInfo() = default; + virtual ~CGPUInfo() = default; + + /*! \brief Getter from the specific platform GPUInfo + \return the platform specific implementation of GPUInfo + */ + static std::unique_ptr GetGPUInfo(); + + /*! \brief Get the temperature of the GPU + \param[in,out] temperature - the temperature to fill with the result + \return true if it was possible to obtain the GPU temperature, false otherwise + */ + bool GetTemperature(CTemperature& temperature) const; + +protected: + /*! \brief Checks if the specific platform implementation supports obtaining the GPU temperature + via the execution of a custom command line command + \note this is false on the base class but may be overridden by the specific platform implementation. + Custom GPU command is defined in advancedsettings. + \return true if the implementation supports obtaining the GPU temperature from a custom command, false otherwise + */ + virtual bool SupportsCustomTemperatureCommand() const { return false; } + + /*! \brief Checks if the specific platform implementation supports obtaining the GPU temperature + from the platform SDK itself + \note this is false on the base class but may be overridden by the specific platform implementation. + \return true if the implementation supports obtaining the GPU temperature from the platform SDK, false otherwise + */ + virtual bool SupportsPlatformTemperature() const { return false; } + + /*! \brief Get the GPU temperature from the platform SDK + \note platform implementations must override this. For this to take effect SupportsPlatformTemperature must be true. + \param[in,out] temperature - the temperature to fill with the result + \return true if obtaining the GPU temperature succeeded, false otherwise + */ + virtual bool GetGPUPlatformTemperature(CTemperature& temperature) const = 0; + + /*! \brief Get the GPU temperature from a user provided command (advanced settings) + \note platform implementations must override this. For this to take effect SupportsCustomTemperatureCommand must be true. + \param[in,out] temperature - the temperature to fill with the result + \return true if obtaining the GPU temperature succeeded, false otherwise + */ + virtual bool GetGPUTemperatureFromCommand(CTemperature& temperature, + const std::string& cmd) const = 0; +}; From 4688f74bf79f7e8ddf0960e22ddbfc0579a48d87 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 10 Sep 2023 11:31:05 +0100 Subject: [PATCH 204/811] [tests][gpuinfo] Add posix unit test for advanced setting --- xbmc/utils/test/CMakeLists.txt | 1 + xbmc/utils/test/TestGPUInfo.cpp | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 xbmc/utils/test/TestGPUInfo.cpp diff --git a/xbmc/utils/test/CMakeLists.txt b/xbmc/utils/test/CMakeLists.txt index 5cff468669748..0ce2a6fafe2af 100644 --- a/xbmc/utils/test/CMakeLists.txt +++ b/xbmc/utils/test/CMakeLists.txt @@ -14,6 +14,7 @@ set(SOURCES TestAlarmClock.cpp TestFileOperationJob.cpp TestFileUtils.cpp TestGlobalsHandling.cpp + TestGPUInfo.cpp TestHTMLUtil.cpp TestHttpHeader.cpp TestHttpParser.cpp diff --git a/xbmc/utils/test/TestGPUInfo.cpp b/xbmc/utils/test/TestGPUInfo.cpp new file mode 100644 index 0000000000000..ce3d4a6d79f61 --- /dev/null +++ b/xbmc/utils/test/TestGPUInfo.cpp @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/GpuInfo.h" + +#include + +#include + +class TestGPUInfo : public ::testing::Test +{ +protected: + TestGPUInfo() = default; +}; + +#if defined(TARGET_WINDOWS) +TEST_F(TestGPUInfo, DISABLED_GetTemperatureFromCmd) +#else +TEST_F(TestGPUInfo, GetTemperature) +#endif +{ + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_gpuTempCmd = "echo '50 c'"; + std::unique_ptr gpuInfo = CGPUInfo::GetGPUInfo(); + EXPECT_NE(gpuInfo, nullptr); + CTemperature t; + bool success = gpuInfo->GetTemperature(t); + EXPECT_TRUE(t.IsValid()); + EXPECT_EQ(t.ToCelsius(), 50); +} From ffcd927bc07fab0eca2a1094758fd8b48e1b87f1 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sat, 9 Sep 2023 10:32:01 +0100 Subject: [PATCH 205/811] [macos][network] Avoid executing shell commands to find the default gateway --- .../darwin/osx/network/NetworkMacOS.mm | 109 ++++++++++++++---- 1 file changed, 85 insertions(+), 24 deletions(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm index 6f85772cbd58e..649007bc1e8ee 100644 --- a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm @@ -23,7 +23,6 @@ #include #include #include -#include #include #include @@ -36,26 +35,88 @@ std::string CNetworkInterfaceMacOS::GetCurrentDefaultGateway() const { - std::string result; + std::string gateway; + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY}; + int afinet_type[] = {AF_INET, AF_INET6}; - FILE* pipe = popen("echo \"show State:/Network/Global/IPv4\" | scutil | grep Router", "r"); - usleep(100000); - if (pipe) + for (int ip_type = 0; ip_type <= 1; ip_type++) { - std::string tmpStr; - char buffer[256] = {'\0'}; - if (fread(buffer, sizeof(char), sizeof(buffer), pipe) > 0 && !ferror(pipe)) + mib[3] = afinet_type[ip_type]; + + size_t needed = 0; + if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0) + return ""; + + char* buf; + if ((buf = new char[needed]) == 0) + return ""; + + if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0) { - tmpStr = buffer; - if (tmpStr.length() >= 11) - result = tmpStr.substr(11); + CLog::Log(LOGERROR, "sysctl: net.route.0.0.dump"); + delete[] buf; + return gateway; } - pclose(pipe); + + struct rt_msghdr* rt; + for (char* p = buf; p < buf + needed; p += rt->rtm_msglen) + { + rt = reinterpret_cast(p); + struct sockaddr* sa = reinterpret_cast(rt + 1); + struct sockaddr* sa_tab[RTAX_MAX]; + for (int i = 0; i < RTAX_MAX; i++) + { + if (rt->rtm_addrs & (1 << i)) + { + sa_tab[i] = sa; + sa = reinterpret_cast( + reinterpret_cast(sa) + + ((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long))); + } + else + { + sa_tab[i] = nullptr; + } + } + + if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) && + sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] && + sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type]) + { + if (afinet_type[ip_type] == AF_INET) + { + if ((reinterpret_cast(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) + { + char dstStr4[INET_ADDRSTRLEN]; + char srcStr4[INET_ADDRSTRLEN]; + memcpy(srcStr4, + &(reinterpret_cast(sa_tab[RTAX_GATEWAY]))->sin_addr, + sizeof(struct in_addr)); + if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr) + gateway = dstStr4; + break; + } + } + else if (afinet_type[ip_type] == AF_INET6) + { + if ((reinterpret_cast(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0) + { + char dstStr6[INET6_ADDRSTRLEN]; + char srcStr6[INET6_ADDRSTRLEN]; + memcpy(srcStr6, + &(reinterpret_cast(sa_tab[RTAX_GATEWAY]))->sin6_addr, + sizeof(struct in6_addr)); + if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr) + gateway = dstStr6; + break; + } + } + } + } + free(buf); } - if (result.empty()) - CLog::Log(LOGWARNING, "Unable to determine gateway"); - return result; + return gateway; } bool CNetworkInterfaceMacOS::GetHostMacAddress(unsigned long host_ip, std::string& mac) const @@ -197,15 +258,15 @@ if (currentDNSConfig) { NSDictionary* networkServices = (__bridge NSDictionary*)currentDNSConfig; - [networkServices enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL* stop) { - NSDictionary* service = networkServices[key]; - NSDictionary* dnsSettings = service[static_cast(kSCEntNetDNS)]; - NSArray* dnsServers = dnsSettings[static_cast(kSCPropNetDNSServerAddresses)]; - for (NSString* dnsServer in dnsServers) - { - result.emplace_back(dnsServer.UTF8String); - } - }]; + [networkServices + enumerateKeysAndObjectsUsingBlock:^(NSString* key, NSDictionary* service, BOOL* stop) { + NSDictionary* dnsSettings = service[static_cast(kSCEntNetDNS)]; + NSArray* dnsServers = dnsSettings[static_cast(kSCPropNetDNSServerAddresses)]; + for (NSString* dnsServer in dnsServers) + { + result.emplace_back(dnsServer.UTF8String); + } + }]; CFRelease(preferences); } } From a6b3930c670f4bff22bd8adf65e2f5d030c256bd Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 10 Sep 2023 18:32:14 +0100 Subject: [PATCH 206/811] [XBMCTinyXML2] Resolve warnings --- xbmc/utils/XBMCTinyXML2.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/utils/XBMCTinyXML2.cpp b/xbmc/utils/XBMCTinyXML2.cpp index ecb121ecea7d1..c49ce163480c4 100644 --- a/xbmc/utils/XBMCTinyXML2.cpp +++ b/xbmc/utils/XBMCTinyXML2.cpp @@ -44,7 +44,7 @@ bool CXBMCTinyXML2::LoadFile(FILE* file) { std::string data; char buf[BUFFER_SIZE] = {}; - int result; + size_t result; while ((result = fread(buf, 1, BUFFER_SIZE, file)) > 0) data.append(buf, result); return Parse(std::move(data)); @@ -103,7 +103,7 @@ bool CXBMCTinyXML2::ParseHelper(size_t pos, std::string&& inputdata) "^&(amp|lt|gt|quot|apos|#x[a-fA-F0-9]{1,4}|#[0-9]{1,5});.*"); do { - if (re.RegFind(inputdata, pos, MAX_ENTITY_LENGTH) < 0) + if (re.RegFind(inputdata, static_cast(pos), MAX_ENTITY_LENGTH) < 0) inputdata.insert(pos + 1, "amp;"); pos = inputdata.find('&', pos + 1); } while (pos != std::string::npos); From 396d83c82e2b9503af32118a809b1101e3cd028a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:31:40 +0200 Subject: [PATCH 207/811] [video] Unify code paths for 'choose fanart' to gain consistent behavior. Optimize and modernize code a bit. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 233 +++++++++------------- 1 file changed, 93 insertions(+), 140 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 233fe7606a420..58eafc2a0830e 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -927,136 +927,13 @@ std::string GetEmbeddedArt(const std::string& fileNameAndPath, const std::string // Allow user to select a Fanart void CGUIDialogVideoInfo::OnGetFanart() { - CFileItemList items; - - // Ensure the fanart is unpacked - m_movieItem->GetVideoInfoTag()->m_fanart.Unpack(); - - if (m_movieItem->HasArt("fanart")) - { - CFileItemPtr itemCurrent(new CFileItem("fanart://Current",false)); - itemCurrent->SetArt("thumb", m_movieItem->GetArt("fanart")); - itemCurrent->SetArt("icon", "DefaultPicture.png"); - itemCurrent->SetLabel(g_localizeStrings.Get(20440)); - items.Add(itemCurrent); - } - - const std::string embeddedArt = - GetEmbeddedArt(m_movieItem->GetVideoInfoTag()->m_strFileNameAndPath, "fanart"); - if (!embeddedArt.empty()) - { - const auto itemEmbedded = std::make_shared("fanart://Embedded", false); - itemEmbedded->SetArt("thumb", embeddedArt); - itemEmbedded->SetLabel(g_localizeStrings.Get(13520)); - items.Add(itemEmbedded); - } - - // Grab the thumbnails from the web - for (unsigned int i = 0; i < m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts(); i++) - { - if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i), "image")) - continue; - std::string strItemPath = StringUtils::Format("fanart://Remote{}", i); - CFileItemPtr item(new CFileItem(strItemPath, false)); - std::string thumb = m_movieItem->GetVideoInfoTag()->m_fanart.GetPreviewURL(i); - item->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(thumb)); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(20441)); - - //! @todo Do we need to clear the cached image? - // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); - items.Add(item); - } - - CFileItem item(*m_movieItem->GetVideoInfoTag()); - std::string strLocal = item.GetLocalFanart(); - if (!strLocal.empty()) - { - CFileItemPtr itemLocal(new CFileItem("fanart://Local",false)); - itemLocal->SetArt("thumb", strLocal); - itemLocal->SetArt("icon", "DefaultPicture.png"); - itemLocal->SetLabel(g_localizeStrings.Get(20438)); - - //! @todo Do we need to clear the cached image? - CServiceBroker::GetTextureCache()->ClearCachedImage(strLocal); - items.Add(itemLocal); - } - else - { - CFileItemPtr itemNone(new CFileItem("fanart://None", false)); - itemNone->SetArt("icon", "DefaultPicture.png"); - itemNone->SetLabel(g_localizeStrings.Get(20439)); - items.Add(itemNone); - } - - std::string result; - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); - AddItemPathToFileBrowserSources(sources, item); - CServiceBroker::GetMediaManager().GetLocalDrives(sources); - bool flip=false; - if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) || - StringUtils::EqualsNoCase(result, "fanart://Current")) - return; // user cancelled - - if (StringUtils::EqualsNoCase(result, "fanart://Local")) - result = strLocal; - - if (StringUtils::EqualsNoCase(result, "fanart://Embedded")) + if (OnGetFanart(m_movieItem)) { - unsigned int current = m_movieItem->GetVideoInfoTag()->m_fanart.GetNumFanarts(); - int found = -1; - for (size_t i = 0; i < current; ++i) - if (URIUtils::IsProtocol(m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL(), "image")) - found = i; - if (found != -1) - { - m_movieItem->GetVideoInfoTag()->m_fanart.AddFanart(embeddedArt, "", ""); - found = current; - } - - m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(found); - - CVideoDatabase db; - if (db.Open()) - { - db.UpdateFanart(*m_movieItem, m_movieItem->GetVideoContentType()); - db.Close(); - } - result = embeddedArt; - } - - if (StringUtils::StartsWith(result, "fanart://Remote")) - { - int iFanart = atoi(result.substr(15).c_str()); - // set new primary fanart, and update our database accordingly - m_movieItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(iFanart); - CVideoDatabase db; - if (db.Open()) - { - db.UpdateFanart(*m_movieItem, m_movieItem->GetVideoContentType()); - db.Close(); - } - result = m_movieItem->GetVideoInfoTag()->m_fanart.GetImageURL(); - } - else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFileUtils::Exists(result)) - result.clear(); + m_hasUpdatedThumb = true; - // set the fanart image - if (flip && !result.empty()) - result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); - CVideoDatabase db; - if (db.Open()) - { - db.SetArtForItem(m_movieItem->GetVideoInfoTag()->m_iDbId, m_movieItem->GetVideoInfoTag()->m_type, "fanart", result); - db.Close(); + // Update our screen + Update(); } - - CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show - m_movieItem->SetArt("fanart", result); - m_hasUpdatedThumb = true; - - // Update our screen - Update(); } void CGUIDialogVideoInfo::OnSetUserrating() const @@ -2315,28 +2192,70 @@ bool CGUIDialogVideoInfo::OnGetFanart(const std::shared_ptr& videoIte if (videoItem == nullptr || !videoItem->HasVideoInfoTag()) return false; - // update the db CVideoDatabase videodb; if (!videodb.Open()) return false; - CVideoThumbLoader loader; - CFileItem item(*videoItem); - loader.LoadItem(&item); + CVideoInfoTag* videoTag = videoItem->GetVideoInfoTag(); + + // Ensure the fanart is unpacked + videoTag->m_fanart.Unpack(); CFileItemList items; - if (item.HasArt("fanart")) + + if (videoItem->HasArt("fanart")) { - CFileItemPtr itemCurrent(new CFileItem("fanart://Current", false)); - itemCurrent->SetArt("thumb", item.GetArt("fanart")); + const auto itemCurrent = std::make_shared("fanart://Current", false); + itemCurrent->SetArt("thumb", videoItem->GetArt("fanart")); + itemCurrent->SetArt("icon", "DefaultPicture.png"); itemCurrent->SetLabel(g_localizeStrings.Get(20440)); items.Add(itemCurrent); } - // add the none option + const std::string embeddedArt = GetEmbeddedArt(videoTag->m_strFileNameAndPath, "fanart"); + if (!embeddedArt.empty()) + { + const auto itemEmbedded = std::make_shared("fanart://Embedded", false); + itemEmbedded->SetArt("thumb", embeddedArt); + itemEmbedded->SetLabel(g_localizeStrings.Get(13520)); + items.Add(itemEmbedded); + } + + // Grab the thumbnails from the web + for (unsigned int i = 0; i < videoTag->m_fanart.GetNumFanarts(); ++i) + { + const std::string thumb = videoTag->m_fanart.GetPreviewURL(i); + if (URIUtils::IsProtocol(thumb, "image")) + continue; + + std::string itemPath = StringUtils::Format("fanart://Remote{}", i); + const auto itemRemote = std::make_shared(itemPath, false); + itemRemote->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(thumb)); + itemRemote->SetArt("icon", "DefaultPicture.png"); + itemRemote->SetLabel(g_localizeStrings.Get(20441)); + + //! @todo Do we need to clear the cached image? + // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); + items.Add(itemRemote); + } + + CFileItem item(*videoTag); + const std::string localFanart = item.GetLocalFanart(); + if (!localFanart.empty()) { - CFileItemPtr itemNone(new CFileItem("fanart://None", false)); - itemNone->SetArt("icon", videoItem->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"); + const auto itemLocal = std::make_shared("fanart://Local", false); + itemLocal->SetArt("thumb", localFanart); + itemLocal->SetArt("icon", "DefaultPicture.png"); + itemLocal->SetLabel(g_localizeStrings.Get(20438)); + + //! @todo Do we need to clear the cached image? + CServiceBroker::GetTextureCache()->ClearCachedImage(localFanart); + items.Add(itemLocal); + } + else + { + const auto itemNone = std::make_shared("fanart://None", false); + itemNone->SetArt("icon", "DefaultPicture.png"); itemNone->SetLabel(g_localizeStrings.Get(20439)); items.Add(itemNone); } @@ -2350,15 +2269,49 @@ bool CGUIDialogVideoInfo::OnGetFanart(const std::shared_ptr& videoIte StringUtils::EqualsNoCase(result, "fanart://Current")) return false; + if (StringUtils::EqualsNoCase(result, "fanart://Local")) + result = localFanart; + + if (StringUtils::EqualsNoCase(result, "fanart://Embedded")) + { + const int current = videoTag->m_fanart.GetNumFanarts(); + int found = -1; + for (int i = 0; i < current; ++i) + { + if (URIUtils::IsProtocol(videoTag->m_fanart.GetImageURL(i), "image")) + found = i; + } + + if (found != -1) + { + videoTag->m_fanart.AddFanart(embeddedArt, "", ""); + found = current; + } + + videoTag->m_fanart.SetPrimaryFanart(found); + videodb.UpdateFanart(*videoItem, videoItem->GetVideoContentType()); + result = embeddedArt; + } + + if (StringUtils::StartsWith(result, "fanart://Remote")) + { + const int fanartIndex = std::atoi(result.substr(15).c_str()); + // set new primary fanart, and update our database accordingly + videoItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(fanartIndex); + videodb.UpdateFanart(*videoItem, videoItem->GetVideoContentType()); + result = videoTag->m_fanart.GetImageURL(); + } else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFileUtils::Exists(result)) result.clear(); + + // set the fanart image if (!result.empty() && flip) result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); - videodb.SetArtForItem(item.GetVideoInfoTag()->m_iDbId, item.GetVideoInfoTag()->m_type, "fanart", result); + videodb.SetArtForItem(videoTag->m_iDbId, videoTag->m_type, "fanart", result); - // clear view cache and reload images - CUtil::DeleteVideoDatabaseDirectoryCache(); + CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show + videoItem->SetArt("fanart", result); return true; } From bd66b7e7db6f3e8ad5fd51555f712c282a6e45fd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:39:20 +0200 Subject: [PATCH 208/811] [video] CGUIDialogVideoInfo::ManageVideoItemArtwork: Refactored. --- .../resources/strings.po | 4 +- xbmc/video/CMakeLists.txt | 2 + xbmc/video/VideoItemArtworkHandler.cpp | 454 ++++++++++++++++++ xbmc/video/VideoItemArtworkHandler.h | 46 ++ xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 321 +++---------- xbmc/video/dialogs/GUIDialogVideoInfo.h | 5 +- xbmc/video/tags/CMakeLists.txt | 4 +- xbmc/video/tags/VideoInfoTagLoaderFactory.cpp | 7 +- xbmc/video/tags/VideoTagExtractionHelper.cpp | 45 ++ xbmc/video/tags/VideoTagExtractionHelper.h | 41 ++ 10 files changed, 665 insertions(+), 264 deletions(-) create mode 100644 xbmc/video/VideoItemArtworkHandler.cpp create mode 100644 xbmc/video/VideoItemArtworkHandler.h create mode 100644 xbmc/video/tags/VideoTagExtractionHelper.cpp create mode 100644 xbmc/video/tags/VideoTagExtractionHelper.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 80e144370b419..f61915a825aec 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -13099,6 +13099,7 @@ msgid "Where no local album cover exists, online art will be used. Where neither msgstr "" #: system/settings/settings.xml +#: xbmc/video/VideoItemArtworkHandler.cpp #: xbmc/music/dialogs/GUIDialogVideoInfo.cpp msgctxt "#20226" msgid "Movie set information folder" @@ -18915,7 +18916,8 @@ msgctxt "#36040" msgid "Detected version of libCEC interface ({0:x}) is lower than the supported version {1:x}." msgstr "" -#: xbmc/video/dialogs/guidialogvideoinfo.cpp +#: xbmc/video/VideoItemArtworkHandler.cpp +#: xbmc/video/dialogs/GUIDialogVideoInfo.cpp #: xbmc/music/dialogs/GUIDialogMusicInfo.cpp msgctxt "#36041" msgid "* Item folder" diff --git a/xbmc/video/CMakeLists.txt b/xbmc/video/CMakeLists.txt index 1deed57cd0fd2..b5068599b738c 100644 --- a/xbmc/video/CMakeLists.txt +++ b/xbmc/video/CMakeLists.txt @@ -3,6 +3,7 @@ set(SOURCES Bookmark.cpp GUIViewStateVideo.cpp PlayerController.cpp Teletext.cpp + VideoItemArtworkHandler.cpp VideoDatabase.cpp VideoDbUrl.cpp VideoEmbeddedImageFileLoader.cpp @@ -22,6 +23,7 @@ set(HEADERS Bookmark.h PlayerController.h Teletext.h TeletextDefines.h + VideoItemArtworkHandler.h VideoDatabase.h VideoDbUrl.h VideoEmbeddedImageFileLoader.h diff --git a/xbmc/video/VideoItemArtworkHandler.cpp b/xbmc/video/VideoItemArtworkHandler.cpp new file mode 100644 index 0000000000000..87e70d9e53626 --- /dev/null +++ b/xbmc/video/VideoItemArtworkHandler.cpp @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2005-2023 Team Kodi + * 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 "VideoItemArtworkHandler.h" + +#include "FileItem.h" +#include "MediaSource.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "filesystem/Directory.h" +#include "guilib/LocalizeStrings.h" +#include "music/MusicDatabase.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/FileExtensionProvider.h" +#include "utils/FileUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoDatabase.h" +#include "video/VideoInfoScanner.h" +#include "video/VideoInfoTag.h" +#include "video/VideoThumbLoader.h" +#include "video/tags/VideoTagExtractionHelper.h" + +using namespace VIDEO; +using namespace XFILE; + +namespace +{ +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkHandler (Generic handler) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkHandler : public IVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkHandler(const std::shared_ptr& item, + const std::string& artType) + : m_item(item), m_artType(artType) + { + } + + std::string GetCurrentArt() const override; + std::string GetEmbeddedArt() const override; + std::vector GetRemoteArt() const override; + std::string GetLocalArt() const override; + + std::string GetDefaultIcon() const override; + + void AddItemPathToFileBrowserSources(std::vector& sources) override; + + void PersistArt(const std::string& art) override; + +protected: + void AddItemPathStringToFileBrowserSources(std::vector& sources, + const std::string& itemDir, + const std::string& label); + + const std::shared_ptr m_item; + const std::string m_artType; +}; + +std::string CVideoItemArtworkHandler::GetCurrentArt() const +{ + if (m_artType.empty()) + { + CLog::LogF(LOGERROR, "Art type not set!"); + return {}; + } + + std::string currentArt; + if (m_item->HasArt(m_artType)) + currentArt = m_item->GetArt(m_artType); + else if (m_item->HasArt("thumb") && (m_artType == "poster" || m_artType == "banner")) + currentArt = m_item->GetArt("thumb"); + + return currentArt; +} + +std::string CVideoItemArtworkHandler::GetEmbeddedArt() const +{ + if (TAGS::CVideoTagExtractionHelper::IsExtractionSupportedFor(*m_item)) + return TAGS::CVideoTagExtractionHelper::ExtractEmbeddedArtFor(*m_item, m_artType); + + return {}; +} + +std::vector CVideoItemArtworkHandler::GetRemoteArt() const +{ + std::vector remoteArt; + CVideoInfoTag tag(*m_item->GetVideoInfoTag()); + tag.m_strPictureURL.Parse(); + tag.m_strPictureURL.GetThumbUrls(remoteArt, m_artType); + return remoteArt; +} + +std::string CVideoItemArtworkHandler::GetLocalArt() const +{ + return CVideoThumbLoader::GetLocalArt(*m_item, m_artType); +} + +std::string CVideoItemArtworkHandler::GetDefaultIcon() const +{ + return m_item->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"; +} + +void CVideoItemArtworkHandler::AddItemPathToFileBrowserSources(std::vector& sources) +{ + std::string itemDir = m_item->GetVideoInfoTag()->m_basePath; + //season + if (itemDir.empty()) + itemDir = m_item->GetVideoInfoTag()->GetPath(); + + const CFileItem itemTmp(itemDir, false); + if (itemTmp.IsVideo()) + itemDir = URIUtils::GetParentPath(itemDir); + + AddItemPathStringToFileBrowserSources(sources, itemDir, + g_localizeStrings.Get(36041) /* * Item folder */); +} + +void CVideoItemArtworkHandler::PersistArt(const std::string& art) +{ + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return; + } + + videodb.SetArtForItem(m_item->GetVideoInfoTag()->m_iDbId, m_item->GetVideoInfoTag()->m_type, + m_artType, art); +} + +void CVideoItemArtworkHandler::AddItemPathStringToFileBrowserSources( + std::vector& sources, const std::string& itemDir, const std::string& label) +{ + if (!itemDir.empty() && CDirectory::Exists(itemDir)) + { + CMediaSource itemSource; + itemSource.strName = label; + itemSource.strPath = itemDir; + sources.emplace_back(itemSource); + } +} + +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkArtistHandler (Artist handler) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkArtistHandler : public CVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkArtistHandler(const std::shared_ptr& item, + const std::string& artType) + : CVideoItemArtworkHandler(item, artType) + { + } + + std::string GetCurrentArt() const override; + std::vector GetRemoteArt() const override; + std::string GetLocalArt() const override; + + std::string GetDefaultIcon() const override { return "DefaultArtist.png"; } + + void PersistArt(const std::string& art) override; +}; + +std::string CVideoItemArtworkArtistHandler::GetCurrentArt() const +{ + CMusicDatabase musicdb; + if (!musicdb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open music database!"); + return {}; + } + + std::string currentArt; + const int idArtist = musicdb.GetArtistByName(m_item->GetLabel()); + if (idArtist >= 0) + currentArt = musicdb.GetArtForItem(idArtist, MediaTypeArtist, "thumb"); + + if (currentArt.empty()) + { + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return {}; + } + + currentArt = videodb.GetArtForItem(m_item->GetVideoInfoTag()->m_iDbId, + m_item->GetVideoInfoTag()->m_type, "thumb"); + } + return currentArt; +} + +std::vector CVideoItemArtworkArtistHandler::GetRemoteArt() const +{ + return {}; +} + +std::string CVideoItemArtworkArtistHandler::GetLocalArt() const +{ + CMusicDatabase musicdb; + if (!musicdb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open music database!"); + return {}; + } + + std::string localArt; + const int idArtist = musicdb.GetArtistByName(m_item->GetLabel()); + if (idArtist >= 0) + { + // Get artist paths - possible locations for thumb - while music db open + CArtist artist; + musicdb.GetArtist(idArtist, artist); + std::string artistPath; + musicdb.GetArtistPath(artist, artistPath); // Artist path in artist info folder + + std::string thumb; + bool existsThumb = false; + + // First look for artist thumb in the primary location + if (!artistPath.empty()) + { + thumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg"); + existsThumb = CFileUtils::Exists(thumb); + } + // If not there fall back local to music files (historic location for those album artists with a unique folder) + if (!existsThumb) + { + std::string artistOldPath; + musicdb.GetOldArtistPath(idArtist, artistOldPath); // Old artist path, local to music files + if (!artistOldPath.empty()) + { + thumb = URIUtils::AddFileToFolder(artistOldPath, "folder.jpg"); + existsThumb = CFileUtils::Exists(thumb); + } + } + + if (existsThumb) + localArt = thumb; + } + return localArt; +} + +void CVideoItemArtworkArtistHandler::PersistArt(const std::string& art) +{ + CMusicDatabase musicdb; + if (!musicdb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open music database!"); + return; + } + + const int idArtist = musicdb.GetArtistByName(m_item->GetLabel()); + if (idArtist >= 0) + musicdb.SetArtForItem(idArtist, MediaTypeArtist, m_artType, art); +} + +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkActorHandler (Actor handler) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkActorHandler : public CVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkActorHandler(const std::shared_ptr& item, + const std::string& artType) + : CVideoItemArtworkHandler(item, artType) + { + } + + std::string GetCurrentArt() const override; + std::string GetLocalArt() const override; + + std::string GetDefaultIcon() const override { return "DefaultActor.png"; } +}; + +std::string CVideoItemArtworkActorHandler::GetCurrentArt() const +{ + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return {}; + } + + return videodb.GetArtForItem(m_item->GetVideoInfoTag()->m_iDbId, + m_item->GetVideoInfoTag()->m_type, "thumb"); +} + +std::string CVideoItemArtworkActorHandler::GetLocalArt() const +{ + std::string localArt; + std::string picturePath; + const std::string thumb = URIUtils::AddFileToFolder(picturePath, "folder.jpg"); + if (CFileUtils::Exists(thumb)) + localArt = thumb; + + return localArt; +} + +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkSeasonHandler (Season handler) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkSeasonHandler : public CVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkSeasonHandler(const std::shared_ptr& item, + const std::string& artType) + : CVideoItemArtworkHandler(item, artType) + { + } + + std::vector GetRemoteArt() const override; +}; + +std::vector CVideoItemArtworkSeasonHandler::GetRemoteArt() const +{ + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return {}; + } + + std::vector remoteArt; + CVideoInfoTag tag; + videodb.GetTvShowInfo("", tag, m_item->GetVideoInfoTag()->m_iIdShow); + tag.m_strPictureURL.Parse(); + tag.m_strPictureURL.GetThumbUrls(remoteArt, m_artType, m_item->GetVideoInfoTag()->m_iSeason); + return remoteArt; +} + +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkMovieSetHandler (Movie set handler) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkMovieSetHandler : public CVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkMovieSetHandler(const std::shared_ptr& item, + const std::string& artType) + : CVideoItemArtworkHandler(item, artType) + { + } + + std::vector GetRemoteArt() const override; + std::string GetLocalArt() const override; + + std::string GetDefaultIcon() const override { return "DefaultVideo.png"; } + + void AddItemPathToFileBrowserSources(std::vector& sources) override; +}; + +std::vector CVideoItemArtworkMovieSetHandler::GetRemoteArt() const +{ + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return {}; + } + + std::vector remoteArt; + const std::string baseDir = + StringUtils::Format("videodb://movies/sets/{}", m_item->GetVideoInfoTag()->m_iDbId); + CFileItemList items; + if (videodb.GetMoviesNav(baseDir, items)) + { + for (const auto& item : items) + { + CVideoInfoTag* videotag = item->GetVideoInfoTag(); + videotag->m_strPictureURL.Parse(); + videotag->m_strPictureURL.GetThumbUrls(remoteArt, "set." + m_artType, -1, true); + } + } + return remoteArt; +} + +std::string CVideoItemArtworkMovieSetHandler::GetLocalArt() const +{ + std::string localArt; + const std::string infoFolder = + VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(m_item->GetLabel()); + if (!infoFolder.empty()) + { + CFileItemList availableArtFiles; + CDirectory::GetDirectory(infoFolder, availableArtFiles, + CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), + DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); + for (const auto& artFile : availableArtFiles) + { + std::string candidate = URIUtils::GetFileName(artFile->GetDynPath()); + URIUtils::RemoveExtension(candidate); + if (StringUtils::EqualsNoCase(candidate, m_artType)) + { + localArt = artFile->GetDynPath(); + break; + } + } + } + return localArt; +} + +void CVideoItemArtworkMovieSetHandler::AddItemPathToFileBrowserSources( + std::vector& sources) +{ + AddItemPathStringToFileBrowserSources( + sources, VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(m_item->GetLabel()), + g_localizeStrings.Get(36041) /* * Item folder */); + AddItemPathStringToFileBrowserSources( + sources, + CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER), + "* " + g_localizeStrings.Get(20226) /* Movie set information folder */); +} + +} // unnamed namespace + +//------------------------------------------------------------------------------------------------- +// IVideoItemArtworkHandlerFactory +//------------------------------------------------------------------------------------------------- + +std::unique_ptr IVideoItemArtworkHandlerFactory::Create( + const std::shared_ptr& item, + const std::string& mediaType, + const std::string& artType) +{ + std::unique_ptr artHandler; + + if (mediaType == MediaTypeArtist) + artHandler = std::make_unique(item, artType); + else if (mediaType == "actor") + artHandler = std::make_unique(item, artType); + else if (mediaType == MediaTypeSeason) + artHandler = std::make_unique(item, artType); + else if (mediaType == MediaTypeVideoCollection) + artHandler = std::make_unique(item, artType); + else + artHandler = std::make_unique(item, artType); + + return artHandler; +} diff --git a/xbmc/video/VideoItemArtworkHandler.h b/xbmc/video/VideoItemArtworkHandler.h new file mode 100644 index 0000000000000..67ce54c2615c8 --- /dev/null +++ b/xbmc/video/VideoItemArtworkHandler.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2005-2023 Team Kodi + * 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 +#include +#include + +class CFileItem; +class CMediaSource; + +namespace VIDEO +{ +class IVideoItemArtworkHandler +{ +public: + virtual ~IVideoItemArtworkHandler() = default; + + virtual std::string GetCurrentArt() const = 0; + + virtual std::string GetEmbeddedArt() const { return {}; } + virtual std::vector GetRemoteArt() const { return {}; } + virtual std::string GetLocalArt() const { return {}; } + + virtual std::string GetDefaultIcon() const = 0; + + virtual void AddItemPathToFileBrowserSources(std::vector& sources) {} + + virtual void PersistArt(const std::string& art) = 0; +}; + +class IVideoItemArtworkHandlerFactory +{ +public: + static std::unique_ptr Create(const std::shared_ptr& item, + const std::string& mediaType, + const std::string& artType); +}; + +} // namespace VIDEO diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 58eafc2a0830e..9b69fdc57ae37 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -41,7 +41,6 @@ #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" #include "storage/MediaManager.h" -#include "utils/FileExtensionProvider.h" #include "utils/FileUtils.h" #include "utils/SortUtils.h" #include "utils/StringUtils.h" @@ -50,6 +49,7 @@ #include "video/VideoDbUrl.h" #include "video/VideoInfoScanner.h" #include "video/VideoInfoTag.h" +#include "video/VideoItemArtworkHandler.h" #include "video/VideoLibraryQueue.h" #include "video/VideoThumbLoader.h" #include "video/VideoUtils.h" @@ -879,17 +879,13 @@ std::string CGUIDialogVideoInfo::ChooseArtType(const CFileItem &videoItem) void CGUIDialogVideoInfo::OnGetArt() { - bool finished = false; - while (!finished) + if (ChooseAndManageVideoItemArtwork(m_movieItem)) { - if (ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type, finished)) - { - m_hasUpdatedThumb = true; + m_hasUpdatedThumb = true; - // Update our screen - Update(); - } - }; + // Update our screen + Update(); + } } namespace @@ -1136,7 +1132,7 @@ int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) break; case CONTEXT_BUTTON_SET_ART: - result = ManageVideoItemArtwork(item, type); + result = ChooseAndManageVideoItemArtwork(item); break; case CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS: @@ -1755,128 +1751,57 @@ bool CGUIDialogVideoInfo::RemoveItemsFromTag(const std::shared_ptr& t return true; } -namespace -{ -std::string FindLocalMovieSetArtworkFile(const CFileItemPtr& item, const std::string& artType) -{ - std::string infoFolder = VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel()); - if (infoFolder.empty()) - return ""; - - CFileItemList availableArtFiles; - CDirectory::GetDirectory(infoFolder, availableArtFiles, - CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(), - DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_READ_CACHE | DIR_FLAG_NO_FILE_INFO); - for (const auto& artFile : availableArtFiles) - { - std::string candidate = URIUtils::GetFileName(artFile->GetPath()); - URIUtils::RemoveExtension(candidate); - if (StringUtils::EqualsNoCase(candidate, artType)) - return artFile->GetPath(); - } - return ""; -} -} // namespace - -bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, - const MediaType& type) +bool CGUIDialogVideoInfo::ChooseAndManageVideoItemArtwork(const std::shared_ptr& item) { bool result = false; - bool finished = false; - while (!finished) + std::string artType; + do { - result = ManageVideoItemArtwork(item, type, finished); - } + artType = ChooseArtType(*item); + if (!artType.empty()) + result = ManageVideoItemArtwork(item, item->GetVideoInfoTag()->m_type, artType); + + } while (!artType.empty()); return result; } bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, - const MediaType& type, - bool& finished) + const std::string& mediaType) { - finished = true; + // When not selecting art type, default type to "thumb". + return ManageVideoItemArtwork(item, mediaType, "thumb"); +} - if (item == nullptr || !item->HasVideoInfoTag() || type.empty()) +bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, + const MediaType& mediaType, + const std::string& artType) +{ + if (item == nullptr || !item->HasVideoInfoTag() || mediaType.empty() || artType.empty()) return false; - CVideoDatabase videodb; - if (!videodb.Open()) - return false; + const std::unique_ptr artHandler = + VIDEO::IVideoItemArtworkHandlerFactory::Create(item, mediaType, artType); - // Grab the thumbnails from the web - CFileItemList items; - CFileItemPtr noneitem(new CFileItem("thumb://None", false)); - std::string currentThumb; - int idArtist = -1; - std::string artistPath; - std::string artistOldPath; - std::string artType = "thumb"; - if (type == MediaTypeArtist) - { - CMusicDatabase musicdb; - if (musicdb.Open()) - { - idArtist = musicdb.GetArtistByName(item->GetLabel()); // Fails when name not unique - if (idArtist >= 0 ) - { - // Get artist paths - possible locations for thumb - while music db open - musicdb.GetOldArtistPath(idArtist, artistOldPath); // Old artist path, local to music files - CArtist artist; - musicdb.GetArtist(idArtist, artist); // Need name and mbid for artist folder name - musicdb.GetArtistPath(artist, artistPath); // Artist path in artist info folder - - currentThumb = musicdb.GetArtForItem(idArtist, MediaTypeArtist, "thumb"); - if (currentThumb.empty()) - currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); - } - } - } - else if (type == "actor") + //! @todo get rid of this special handling of fanart + if (artType == "fanart" && mediaType != MediaTypeVideoCollection) { - currentThumb = videodb.GetArtForItem(item->GetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType); + return OnGetFanart(item); } - else - { // SEASON, SHOW, SET - - // We want to re-open the art type selection dialog after selecting an image for the type - // until user canceled the art type selection dialog, controlled via 'finished' value by - // the caller of this method. - - artType = ChooseArtType(*item); - if (artType.empty()) - { - finished = true; - return false; - } - finished = false; - - if (artType == "fanart" && type != MediaTypeVideoCollection) - { - const bool result = OnGetFanart(item); - return result; - } - - if (item->HasArt(artType)) - currentThumb = item->GetArt(artType); - else if ((artType == "poster" || artType == "banner") && item->HasArt("thumb")) - currentThumb = item->GetArt("thumb"); - } + CFileItemList items; - if (!currentThumb.empty()) + const std::string currentArt = artHandler->GetCurrentArt(); + if (!currentArt.empty()) { - CFileItemPtr item(new CFileItem("thumb://Current", false)); - item->SetArt("thumb", currentThumb); - item->SetLabel(g_localizeStrings.Get(13512)); - items.Add(item); + const auto itemCurrent = std::make_shared("thumb://Current", false); + itemCurrent->SetArt("thumb", currentArt); + itemCurrent->SetLabel(g_localizeStrings.Get(13512)); + items.Add(itemCurrent); } - noneitem->SetArt("icon", item->m_bIsFolder ? "DefaultFolder.png" : "DefaultPicture.png"); - noneitem->SetLabel(g_localizeStrings.Get(13515)); - const std::string embeddedArt = - GetEmbeddedArt(item->GetVideoInfoTag()->m_strFileNameAndPath, artType); + const std::string embeddedArt = artHandler->GetEmbeddedArt(); if (!embeddedArt.empty()) { const auto itemEmbedded = std::make_shared("thumb://Embedded", false); @@ -1885,153 +1810,47 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr thumbs; - if (type != MediaTypeArtist) + std::vector remoteArt = artHandler->GetRemoteArt(); + for (size_t i = 0; i < remoteArt.size(); ++i) { - CVideoInfoTag tag; - if (type == MediaTypeSeason) - { - videodb.GetTvShowInfo("", tag, item->GetVideoInfoTag()->m_iIdShow); - tag.m_strPictureURL.Parse(); - tag.m_strPictureURL.GetThumbUrls(thumbs, artType, item->GetVideoInfoTag()->m_iSeason); - } - else if (type == MediaTypeVideoCollection) - { - CFileItemList items; - std::string baseDir = - StringUtils::Format("videodb://movies/sets/{}", item->GetVideoInfoTag()->m_iDbId); - if (videodb.GetMoviesNav(baseDir, items)) - { - for (int i=0; i < items.Size(); i++) - { - CVideoInfoTag* pTag = items[i]->GetVideoInfoTag(); - pTag->m_strPictureURL.Parse(); - pTag->m_strPictureURL.GetThumbUrls(thumbs, "set." + artType, -1, true); - } - } - } - else - { - tag = *item->GetVideoInfoTag(); - tag.m_strPictureURL.Parse(); - tag.m_strPictureURL.GetThumbUrls(thumbs, artType); - } - - for (size_t i = 0; i < thumbs.size(); i++) - { - CFileItemPtr item(new CFileItem(StringUtils::Format("thumb://Remote{0}", i), false)); - item->SetArt("thumb", thumbs[i]); - item->SetArt("icon", "DefaultPicture.png"); - item->SetLabel(g_localizeStrings.Get(13513)); - items.Add(item); - - //! @todo Do we need to clear the cached image? - // CServiceBroker::GetTextureCache()->ClearCachedImage(thumbs[i]); - } + const auto itemRemote = + std::make_shared(StringUtils::Format("thumb://Remote{0}", i), false); + itemRemote->SetArt("thumb", remoteArt[i]); + itemRemote->SetArt("icon", "DefaultPicture.png"); + itemRemote->SetLabel(g_localizeStrings.Get(13513)); + items.Add(itemRemote); - if (type == "actor") - { - std::string picturePath; - std::string strThumb = URIUtils::AddFileToFolder(picturePath, "folder.jpg"); - if (CFileUtils::Exists(strThumb)) - { - CFileItemPtr pItem(new CFileItem(strThumb,false)); - pItem->SetLabel(g_localizeStrings.Get(13514)); - pItem->SetArt("thumb", strThumb); - items.Add(pItem); - local = true; - } - else - noneitem->SetArt("icon", "DefaultActor.png"); - } - else if (type == MediaTypeVideoCollection) - { - std::string localFile = FindLocalMovieSetArtworkFile(item, artType); - if (!localFile.empty()) - { - CFileItemPtr pItem(new CFileItem(localFile, false)); - pItem->SetLabel(g_localizeStrings.Get(13514)); - pItem->SetArt("thumb", localFile); - items.Add(pItem); - local = true; - } - else - noneitem->SetArt("icon", "DefaultVideo.png"); - } - else - { - localThumb = CVideoThumbLoader::GetLocalArt(*item, artType); - if (!localThumb.empty()) - { - const auto localitem = std::make_shared("thumb://Local", false); - localitem->SetArt("thumb", localThumb); - localitem->SetArt("icon", "DefaultPicture.png"); - localitem->SetLabel(g_localizeStrings.Get(13514)); - items.Add(localitem); - } - else - noneitem->SetArt("icon", "DefaultPicture.png"); - } + //! @todo Do we need to clear the cached image? + // CServiceBroker::GetTextureCache()->ClearCachedImage(remoteArt[i]); } - else - { - std::string strThumb; - bool existsThumb = false; - // First look for artist thumb in the primary location - if (!artistPath.empty()) - { - strThumb = URIUtils::AddFileToFolder(artistPath, "folder.jpg"); - existsThumb = CFileUtils::Exists(strThumb); - } - // If not there fall back local to music files (historic location for those album artists with a unique folder) - if (!existsThumb && !artistOldPath.empty()) - { - strThumb = URIUtils::AddFileToFolder(artistOldPath, "folder.jpg"); - existsThumb = CFileUtils::Exists(strThumb); - } - if (existsThumb) - { - CFileItemPtr pItem(new CFileItem(strThumb, false)); - pItem->SetLabel(g_localizeStrings.Get(13514)); - pItem->SetArt("thumb", strThumb); - items.Add(pItem); - local = true; - } - else - noneitem->SetArt("icon", "DefaultArtist.png"); + const std::string localArt = artHandler->GetLocalArt(); + if (!localArt.empty()) + { + const auto itemLocal = std::make_shared("thumb://Local", false); + itemLocal->SetLabel(g_localizeStrings.Get(13514)); + itemLocal->SetArt("thumb", localArt); + items.Add(itemLocal); } - if (!local) - items.Add(noneitem); + const auto itemNone = std::make_shared("thumb://None", false); + itemNone->SetLabel(g_localizeStrings.Get(13515)); + itemNone->SetArt("icon", artHandler->GetDefaultIcon()); + items.Add(itemNone); std::string result; - VECSOURCES sources=*CMediaSourceSettings::GetInstance().GetSources("video"); + std::vector sources = *CMediaSourceSettings::GetInstance().GetSources("video"); CServiceBroker::GetMediaManager().GetLocalDrives(sources); - if (type == MediaTypeVideoCollection) - { - AddItemPathStringToFileBrowserSources(sources, - VIDEO::CVideoInfoScanner::GetMovieSetInfoFolder(item->GetLabel()), - g_localizeStrings.Get(36041)); - AddItemPathStringToFileBrowserSources(sources, - CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( - CSettings::SETTING_VIDEOLIBRARY_MOVIESETSFOLDER), - "* " + g_localizeStrings.Get(20226)); - } - else - AddItemPathToFileBrowserSources(sources, *item); + artHandler->AddItemPathToFileBrowserSources(sources); if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result)) return false; // user cancelled if (result == "thumb://Current") - result = currentThumb; // user chose the one they have + result = currentArt; // user chose the one they have if (result == "thumb://Local") - result = localThumb; + result = localArt; if (result == "thumb://Embedded") result = embeddedArt; @@ -2042,23 +1861,15 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetVideoInfoTag()->m_iDbId, item->GetVideoInfoTag()->m_type, artType, result); - } - else - { - CMusicDatabase musicdb; - if (musicdb.Open()) - musicdb.SetArtForItem(idArtist, MediaTypeArtist, artType, result); - } + artHandler->PersistArt(result); item->SetArt(artType, result); + if (item->HasProperty("set_folder_thumb")) { // have a folder thumb to set as well diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.h b/xbmc/video/dialogs/GUIDialogVideoInfo.h index ef57ff68c37b4..259ee8884bf04 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.h +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.h @@ -55,6 +55,7 @@ class CGUIDialogVideoInfo : static bool AddItemsToTag(const std::shared_ptr& tagItem); static bool RemoveItemsFromTag(const std::shared_ptr& tagItem); + static bool ChooseAndManageVideoItemArtwork(const std::shared_ptr& item); static bool ManageVideoItemArtwork(const std::shared_ptr& item, const MediaType& type); static std::string GetLocalizedVideoType(const std::string &strType); @@ -113,6 +114,6 @@ class CGUIDialogVideoInfo : private: static std::string ChooseArtType(const CFileItem& item); static bool ManageVideoItemArtwork(const std::shared_ptr& item, - const MediaType& type, - bool& finished); + const MediaType& mediaType, + const std::string& artType); }; diff --git a/xbmc/video/tags/CMakeLists.txt b/xbmc/video/tags/CMakeLists.txt index ab289f864a8d2..6b23664b36812 100644 --- a/xbmc/video/tags/CMakeLists.txt +++ b/xbmc/video/tags/CMakeLists.txt @@ -1,9 +1,11 @@ -set(SOURCES VideoInfoTagLoaderFactory.cpp +set(SOURCES VideoTagExtractionHelper.cpp + VideoInfoTagLoaderFactory.cpp VideoTagLoaderFFmpeg.cpp VideoTagLoaderNFO.cpp VideoTagLoaderPlugin.cpp) set(HEADERS IVideoInfoTagLoader.h + VideoTagExtractionHelper.h VideoInfoTagLoaderFactory.h VideoTagLoaderFFmpeg.h VideoTagLoaderNFO.h diff --git a/xbmc/video/tags/VideoInfoTagLoaderFactory.cpp b/xbmc/video/tags/VideoInfoTagLoaderFactory.cpp index 00ed67a528a42..6f3c7fa00928f 100644 --- a/xbmc/video/tags/VideoInfoTagLoaderFactory.cpp +++ b/xbmc/video/tags/VideoInfoTagLoaderFactory.cpp @@ -9,12 +9,10 @@ #include "VideoInfoTagLoaderFactory.h" #include "FileItem.h" -#include "ServiceBroker.h" #include "VideoTagLoaderFFmpeg.h" #include "VideoTagLoaderNFO.h" #include "VideoTagLoaderPlugin.h" -#include "settings/Settings.h" -#include "settings/SettingsComponent.h" +#include "video/tags/VideoTagExtractionHelper.h" using namespace VIDEO; @@ -37,8 +35,7 @@ IVideoInfoTagLoader* CVideoInfoTagLoaderFactory::CreateLoader(const CFileItem& i return nfo; delete nfo; - if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_USETAGS) && - (item.IsType(".mkv") || item.IsType(".mp4") || item.IsType(".avi") || item.IsType(".m4v"))) + if (TAGS::CVideoTagExtractionHelper::IsExtractionSupportedFor(item)) { CVideoTagLoaderFFmpeg* ff = new CVideoTagLoaderFFmpeg(item, info, lookInFolder); if (ff->HasInfo()) diff --git a/xbmc/video/tags/VideoTagExtractionHelper.cpp b/xbmc/video/tags/VideoTagExtractionHelper.cpp new file mode 100644 index 0000000000000..5ce567aed92fd --- /dev/null +++ b/xbmc/video/tags/VideoTagExtractionHelper.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "VideoTagExtractionHelper.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "TextureDatabase.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/URIUtils.h" +#include "video/VideoInfoTag.h" +#include "video/tags/VideoTagLoaderFFmpeg.h" + +using namespace VIDEO::TAGS; + +bool CVideoTagExtractionHelper::IsExtractionSupportedFor(const CFileItem& item) +{ + const std::string fileNameAndPath = item.GetVideoInfoTag()->m_strFileNameAndPath; + return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MYVIDEOS_USETAGS) && + URIUtils::HasExtension(fileNameAndPath, ".mkv|.mp4|.avi|.m4v"); +} + +std::string CVideoTagExtractionHelper::ExtractEmbeddedArtFor(const CFileItem& item, + const std::string& artType) +{ + CVideoTagLoaderFFmpeg loader(item, nullptr, false); + CVideoInfoTag tag; + loader.Load(tag, false, nullptr); + for (const auto& it : tag.m_coverArt) + { + if (it.m_type == artType) + { + return CTextureUtils::GetWrappedImageURL(item.GetVideoInfoTag()->m_strFileNameAndPath, + "video_" + artType); + } + } + return {}; +} diff --git a/xbmc/video/tags/VideoTagExtractionHelper.h b/xbmc/video/tags/VideoTagExtractionHelper.h new file mode 100644 index 0000000000000..1e6823e1cfcd8 --- /dev/null +++ b/xbmc/video/tags/VideoTagExtractionHelper.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 + +class CFileItem; + +namespace VIDEO +{ +namespace TAGS +{ +class CVideoTagExtractionHelper +{ +public: + /*! + \brief Check whether tag extraction is supported for the given item. This is no + check whether the video denoted by the item actually contains any tags. There is also a Kodi + setting for enabling video tag support that is taken into consideration by the implementation of + this function. + \param item [in] the item denoting a video file. + \return True if tag support is enabled and extraction is supported for the given file type. + */ + static bool IsExtractionSupportedFor(const CFileItem& item); + + /*! + \brief Try to extract embedded art of given type from the given item denoting a video file. + \param item [in] the item. + \param artType The type of the art to extract (e.g. "fanart"). + \return The image URL of the embedded art if extraction was successful, false otherwise. + */ + static std::string ExtractEmbeddedArtFor(const CFileItem& item, const std::string& artType); +}; +} // namespace TAGS +} // namespace VIDEO From 7fe525bacadf704dae1d12c42b63760fe2d403d1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 5 Sep 2023 22:21:51 +0200 Subject: [PATCH 209/811] [video] Unify artwork selection: no more special handling for fanart. --- .../resources/strings.po | 3 +- xbmc/video/VideoItemArtworkHandler.cpp | 102 +++++++- xbmc/video/VideoItemArtworkHandler.h | 7 + xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 229 ++---------------- xbmc/video/dialogs/GUIDialogVideoInfo.h | 5 - 5 files changed, 125 insertions(+), 221 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index f61915a825aec..2bfe7e1992938 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -23068,8 +23068,9 @@ msgctxt "#39122" msgid "Library Information Sources" msgstr "" -#. Label for section of settings +#. Label for section of settings, label for artwork file browse dialog #: system/settings/settings.xml +#: xbmc/video/dialogs/GUIDialogVideoInfo.cpp msgctxt "#39123" msgid "Artwork" msgstr "" diff --git a/xbmc/video/VideoItemArtworkHandler.cpp b/xbmc/video/VideoItemArtworkHandler.cpp index 87e70d9e53626..0401c832b3ac7 100644 --- a/xbmc/video/VideoItemArtworkHandler.cpp +++ b/xbmc/video/VideoItemArtworkHandler.cpp @@ -426,6 +426,104 @@ void CVideoItemArtworkMovieSetHandler::AddItemPathToFileBrowserSources( "* " + g_localizeStrings.Get(20226) /* Movie set information folder */); } +//------------------------------------------------------------------------------------------------- +// CVideoItemArtworkFanartHandler (Handler for all media types, to manage fanart art type) +//------------------------------------------------------------------------------------------------- + +class CVideoItemArtworkFanartHandler : public CVideoItemArtworkHandler +{ +public: + explicit CVideoItemArtworkFanartHandler(const std::shared_ptr& item, + const std::string& artType) + : CVideoItemArtworkHandler(item, artType) + { + // Ensure the fanart is unpacked + m_item->GetVideoInfoTag()->m_fanart.Unpack(); + } + + std::string GetCurrentArt() const override; + std::vector GetRemoteArt() const override; + std::string GetLocalArt() const override; + + std::string GetDefaultIcon() const override { return "DefaultPicture.png"; } + bool SupportsFlippedArt() const override { return true; } + + std::string UpdateEmbeddedArt(const std::string& art) override; + std::string UpdateRemoteArt(const std::vector& art, int index) override; +}; + +std::string CVideoItemArtworkFanartHandler::GetCurrentArt() const +{ + return m_item->GetArt("fanart"); +} + +std::vector CVideoItemArtworkFanartHandler::GetRemoteArt() const +{ + std::vector remoteArt; + const CVideoInfoTag* videoTag = m_item->GetVideoInfoTag(); + for (unsigned int i = 0; i < videoTag->m_fanart.GetNumFanarts(); ++i) + { + const std::string thumb = videoTag->m_fanart.GetPreviewURL(i); + if (URIUtils::IsProtocol(thumb, "image")) + continue; + + remoteArt.emplace_back(CTextureUtils::GetWrappedThumbURL(thumb)); + } + return remoteArt; +} + +std::string CVideoItemArtworkFanartHandler::GetLocalArt() const +{ + return m_item->GetLocalFanart(); +} + +std::string CVideoItemArtworkFanartHandler::UpdateEmbeddedArt(const std::string& art) +{ + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + return art; + } + + CVideoInfoTag* videoTag = m_item->GetVideoInfoTag(); + const int currentTag = videoTag->m_fanart.GetNumFanarts(); + int matchingTag = -1; + for (int i = 0; i < currentTag; ++i) + { + if (URIUtils::IsProtocol(videoTag->m_fanart.GetImageURL(i), "image")) + matchingTag = i; + } + + if (matchingTag != -1) + { + videoTag->m_fanart.AddFanart(art, "", ""); + matchingTag = currentTag; + } + + videoTag->m_fanart.SetPrimaryFanart(matchingTag); + videodb.UpdateFanart(*m_item, m_item->GetVideoContentType()); + return art; +} + +std::string CVideoItemArtworkFanartHandler::UpdateRemoteArt(const std::vector& art, + int index) +{ + CVideoInfoTag* videoTag = m_item->GetVideoInfoTag(); + + CVideoDatabase videodb; + if (!videodb.Open()) + { + CLog::LogF(LOGERROR, "Cannot open video database!"); + } + else + { + videoTag->m_fanart.SetPrimaryFanart(index); + videodb.UpdateFanart(*m_item, m_item->GetVideoContentType()); + } + return videoTag->m_fanart.GetImageURL(); +} + } // unnamed namespace //------------------------------------------------------------------------------------------------- @@ -439,7 +537,9 @@ std::unique_ptr IVideoItemArtworkHandlerFactory::Creat { std::unique_ptr artHandler; - if (mediaType == MediaTypeArtist) + if (artType == "fanart" && mediaType != MediaTypeVideoCollection) + artHandler = std::make_unique(item, artType); + else if (mediaType == MediaTypeArtist) artHandler = std::make_unique(item, artType); else if (mediaType == "actor") artHandler = std::make_unique(item, artType); diff --git a/xbmc/video/VideoItemArtworkHandler.h b/xbmc/video/VideoItemArtworkHandler.h index 67ce54c2615c8..01613ef7d0223 100644 --- a/xbmc/video/VideoItemArtworkHandler.h +++ b/xbmc/video/VideoItemArtworkHandler.h @@ -29,9 +29,16 @@ class IVideoItemArtworkHandler virtual std::string GetLocalArt() const { return {}; } virtual std::string GetDefaultIcon() const = 0; + virtual bool SupportsFlippedArt() const { return false; } virtual void AddItemPathToFileBrowserSources(std::vector& sources) {} + virtual std::string UpdateEmbeddedArt(const std::string& art) { return art; } + virtual std::string UpdateRemoteArt(const std::vector& art, int index) + { + return art[index]; + } + virtual void PersistArt(const std::string& art) = 0; }; diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 9b69fdc57ae37..2ded28218318b 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -53,7 +53,6 @@ #include "video/VideoLibraryQueue.h" #include "video/VideoThumbLoader.h" #include "video/VideoUtils.h" -#include "video/tags/VideoTagLoaderFFmpeg.h" #include "video/windows/GUIWindowVideoNav.h" #include @@ -888,42 +887,9 @@ void CGUIDialogVideoInfo::OnGetArt() } } -namespace -{ -std::string GetEmbeddedArt(const std::string& fileNameAndPath, const std::string& artType) -{ - std::string embeddedArt; - if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( - CSettings::SETTING_MYVIDEOS_USETAGS)) - { - if (URIUtils::HasExtension(fileNameAndPath, ".mkv") || - URIUtils::HasExtension(fileNameAndPath, ".mp4") || - URIUtils::HasExtension(fileNameAndPath, ".avi") || - URIUtils::HasExtension(fileNameAndPath, ".m4v")) - { - CFileItem item(fileNameAndPath, false); - CVideoTagLoaderFFmpeg loader(item, nullptr, false); - CVideoInfoTag tag; - loader.Load(tag, false, nullptr); - for (const auto& it : tag.m_coverArt) - { - if (it.m_type == artType) - { - embeddedArt = CTextureUtils::GetWrappedImageURL(fileNameAndPath, "video_" + artType); - break; - } - } - } - } - return embeddedArt; -} - -} // unnamed namespace - -// Allow user to select a Fanart void CGUIDialogVideoInfo::OnGetFanart() { - if (OnGetFanart(m_movieItem)) + if (ManageVideoItemArtwork(m_movieItem, m_movieItem->GetVideoInfoTag()->m_type, "fanart")) { m_hasUpdatedThumb = true; @@ -978,45 +944,6 @@ std::string CGUIDialogVideoInfo::GetThumbnail() const return m_movieItem->GetArt("thumb"); } -namespace -{ -std::string GetItemPathForBrowserSource(const CFileItem& item) -{ - if (!item.HasVideoInfoTag()) - return ""; - - std::string itemDir = item.GetVideoInfoTag()->m_basePath; - //season - if (itemDir.empty()) - itemDir = item.GetVideoInfoTag()->GetPath(); - - CFileItem itemTmp(itemDir, false); - if (itemTmp.IsVideo()) - itemDir = URIUtils::GetParentPath(itemDir); - - return itemDir; -} - -void AddItemPathStringToFileBrowserSources(VECSOURCES& sources, - const std::string& itemDir, const std::string& label) -{ - if (!itemDir.empty() && CDirectory::Exists(itemDir)) - { - CMediaSource itemSource; - itemSource.strName = label; - itemSource.strPath = itemDir; - sources.push_back(itemSource); - } -} -} // namespace - -void CGUIDialogVideoInfo::AddItemPathToFileBrowserSources(VECSOURCES& sources, - const CFileItem& item) -{ - std::string itemDir = GetItemPathForBrowserSource(item); - AddItemPathStringToFileBrowserSources(sources, itemDir, g_localizeStrings.Get(36041)); -} - int CGUIDialogVideoInfo::ManageVideoItem(const std::shared_ptr& item) { if (item == nullptr || !item->IsVideoDb() || !item->HasVideoInfoTag() || item->GetVideoInfoTag()->m_iDbId < 0) @@ -1784,12 +1711,6 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr artHandler = VIDEO::IVideoItemArtworkHandlerFactory::Create(item, mediaType, artType); - //! @todo get rid of this special handling of fanart - if (artType == "fanart" && mediaType != MediaTypeVideoCollection) - { - return OnGetFanart(item); - } - CFileItemList items; const std::string currentArt = artHandler->GetCurrentArt(); @@ -1843,8 +1764,11 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrAddItemPathToFileBrowserSources(sources); - if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(13511), result)) - return false; // user cancelled + bool flip = false; + if (!CGUIDialogFileBrowser::ShowAndGetImage( + items, sources, g_localizeStrings.Get(13511) /* Choose art */, result, + artHandler->SupportsFlippedArt() ? &flip : nullptr, 39123 /* Artwork */)) + return false; // user cancelled if (result == "thumb://Current") result = currentArt; // user chose the one they have @@ -1853,18 +1777,24 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrUpdateEmbeddedArt(embeddedArt); // delete the thumbnail if that's what the user wants, else overwrite with the // new thumbnail if (result == "thumb://None") + { result.clear(); + } else if (StringUtils::StartsWith(result, "thumb://Remote")) { - const int number = std::atoi(StringUtils::Mid(result, 14).c_str()); - result = remoteArt[number]; + const int index = std::atoi(StringUtils::Mid(result, 14).c_str()); + result = artHandler->UpdateRemoteArt(remoteArt, index); } + // flip selected image, if user wants it + if (!result.empty() && flip) + result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); + // write the selected artwork to the database artHandler->PersistArt(result); @@ -1998,135 +1928,6 @@ bool CGUIDialogVideoInfo::LinkMovieToTvShow(const std::shared_ptr& it return false; } -bool CGUIDialogVideoInfo::OnGetFanart(const std::shared_ptr& videoItem) -{ - if (videoItem == nullptr || !videoItem->HasVideoInfoTag()) - return false; - - CVideoDatabase videodb; - if (!videodb.Open()) - return false; - - CVideoInfoTag* videoTag = videoItem->GetVideoInfoTag(); - - // Ensure the fanart is unpacked - videoTag->m_fanart.Unpack(); - - CFileItemList items; - - if (videoItem->HasArt("fanart")) - { - const auto itemCurrent = std::make_shared("fanart://Current", false); - itemCurrent->SetArt("thumb", videoItem->GetArt("fanart")); - itemCurrent->SetArt("icon", "DefaultPicture.png"); - itemCurrent->SetLabel(g_localizeStrings.Get(20440)); - items.Add(itemCurrent); - } - - const std::string embeddedArt = GetEmbeddedArt(videoTag->m_strFileNameAndPath, "fanart"); - if (!embeddedArt.empty()) - { - const auto itemEmbedded = std::make_shared("fanart://Embedded", false); - itemEmbedded->SetArt("thumb", embeddedArt); - itemEmbedded->SetLabel(g_localizeStrings.Get(13520)); - items.Add(itemEmbedded); - } - - // Grab the thumbnails from the web - for (unsigned int i = 0; i < videoTag->m_fanart.GetNumFanarts(); ++i) - { - const std::string thumb = videoTag->m_fanart.GetPreviewURL(i); - if (URIUtils::IsProtocol(thumb, "image")) - continue; - - std::string itemPath = StringUtils::Format("fanart://Remote{}", i); - const auto itemRemote = std::make_shared(itemPath, false); - itemRemote->SetArt("thumb", CTextureUtils::GetWrappedThumbURL(thumb)); - itemRemote->SetArt("icon", "DefaultPicture.png"); - itemRemote->SetLabel(g_localizeStrings.Get(20441)); - - //! @todo Do we need to clear the cached image? - // CServiceBroker::GetTextureCache()->ClearCachedImage(thumb); - items.Add(itemRemote); - } - - CFileItem item(*videoTag); - const std::string localFanart = item.GetLocalFanart(); - if (!localFanart.empty()) - { - const auto itemLocal = std::make_shared("fanart://Local", false); - itemLocal->SetArt("thumb", localFanart); - itemLocal->SetArt("icon", "DefaultPicture.png"); - itemLocal->SetLabel(g_localizeStrings.Get(20438)); - - //! @todo Do we need to clear the cached image? - CServiceBroker::GetTextureCache()->ClearCachedImage(localFanart); - items.Add(itemLocal); - } - else - { - const auto itemNone = std::make_shared("fanart://None", false); - itemNone->SetArt("icon", "DefaultPicture.png"); - itemNone->SetLabel(g_localizeStrings.Get(20439)); - items.Add(itemNone); - } - - std::string result; - VECSOURCES sources(*CMediaSourceSettings::GetInstance().GetSources("video")); - CServiceBroker::GetMediaManager().GetLocalDrives(sources); - AddItemPathToFileBrowserSources(sources, item); - bool flip = false; - if (!CGUIDialogFileBrowser::ShowAndGetImage(items, sources, g_localizeStrings.Get(20437), result, &flip, 20445) || - StringUtils::EqualsNoCase(result, "fanart://Current")) - return false; - - if (StringUtils::EqualsNoCase(result, "fanart://Local")) - result = localFanart; - - if (StringUtils::EqualsNoCase(result, "fanart://Embedded")) - { - const int current = videoTag->m_fanart.GetNumFanarts(); - int found = -1; - for (int i = 0; i < current; ++i) - { - if (URIUtils::IsProtocol(videoTag->m_fanart.GetImageURL(i), "image")) - found = i; - } - - if (found != -1) - { - videoTag->m_fanart.AddFanart(embeddedArt, "", ""); - found = current; - } - - videoTag->m_fanart.SetPrimaryFanart(found); - videodb.UpdateFanart(*videoItem, videoItem->GetVideoContentType()); - result = embeddedArt; - } - - if (StringUtils::StartsWith(result, "fanart://Remote")) - { - const int fanartIndex = std::atoi(result.substr(15).c_str()); - // set new primary fanart, and update our database accordingly - videoItem->GetVideoInfoTag()->m_fanart.SetPrimaryFanart(fanartIndex); - videodb.UpdateFanart(*videoItem, videoItem->GetVideoContentType()); - result = videoTag->m_fanart.GetImageURL(); - } - else if (StringUtils::EqualsNoCase(result, "fanart://None") || !CFileUtils::Exists(result)) - result.clear(); - - // set the fanart image - if (!result.empty() && flip) - result = CTextureUtils::GetWrappedImageURL(result, "", "flipped"); - - videodb.SetArtForItem(videoTag->m_iDbId, videoTag->m_type, "fanart", result); - - CUtil::DeleteVideoDatabaseDirectoryCache(); // to get them new thumbs to show - videoItem->SetArt("fanart", result); - - return true; -} - void CGUIDialogVideoInfo::ShowFor(const CFileItem& item) { auto window = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_VIDEO_NAV); diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.h b/xbmc/video/dialogs/GUIDialogVideoInfo.h index 259ee8884bf04..06c703cc56078 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.h +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.h @@ -97,11 +97,6 @@ class CGUIDialogVideoInfo : bool bRemove, CVideoDatabase& database); - /*! \brief Pop up a fanart chooser. Does not utilise remote URLs. - \param videoItem the item to choose fanart for. - */ - static bool OnGetFanart(const std::shared_ptr& videoItem); - std::shared_ptr m_movieItem; CFileItemList *m_castList; bool m_bViewReview = false; From 4f532d3fb9fbff83f655634612e15297fbd1cbf6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:58:05 +1000 Subject: [PATCH 210/811] [cmake] FindHarfBuzz migrate to full TARGET usage --- cmake/modules/FindHarfBuzz.cmake | 49 ++++++++++++++------------------ 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/cmake/modules/FindHarfBuzz.cmake b/cmake/modules/FindHarfBuzz.cmake index 6691136bbbf3e..44b291c9b81b9 100644 --- a/cmake/modules/FindHarfBuzz.cmake +++ b/cmake/modules/FindHarfBuzz.cmake @@ -3,44 +3,37 @@ # ------------ # Finds the HarfBuzz library # -# This will define the following variables:: -# -# HARFBUZZ_FOUND - system has HarfBuzz -# HARFBUZZ_INCLUDE_DIRS - the HarfBuzz include directory -# HARFBUZZ_LIBRARIES - the HarfBuzz libraries -# -# and the following imported targets:: +# This will define the following target: # # HarfBuzz::HarfBuzz - The HarfBuzz library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_HARFBUZZ harfbuzz QUIET) -endif() - -find_path(HARFBUZZ_INCLUDE_DIR NAMES harfbuzz/hb-ft.h hb-ft.h - PATHS ${PC_HARFBUZZ_INCLUDEDIR} - ${PC_HARFBUZZ_INCLUDE_DIRS} - PATH_SUFFIXES harfbuzz) -find_library(HARFBUZZ_LIBRARY NAMES harfbuzz harfbuzz - PATHS ${PC_HARFBUZZ_LIBDIR}) +if(NOT TARGET HarfBuzz::HarfBuzz) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_HARFBUZZ harfbuzz QUIET) + endif() -set(HARFBUZZ_VERSION ${PC_HARFBUZZ_VERSION}) + find_path(HARFBUZZ_INCLUDE_DIR NAMES harfbuzz/hb-ft.h hb-ft.h + HINTS ${PC_HARFBUZZ_INCLUDEDIR} + ${PC_HARFBUZZ_INCLUDE_DIRS} + PATH_SUFFIXES harfbuzz + NO_CACHE) + find_library(HARFBUZZ_LIBRARY NAMES harfbuzz + HINTS ${PC_HARFBUZZ_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(HarfBuzz - REQUIRED_VARS HARFBUZZ_LIBRARY HARFBUZZ_INCLUDE_DIR - VERSION_VAR HARFBUZZ_VERSION) + set(HARFBUZZ_VERSION ${PC_HARFBUZZ_VERSION}) -if(HARFBUZZ_FOUND) - set(HARFBUZZ_LIBRARIES ${HARFBUZZ_LIBRARY}) - set(HARFBUZZ_INCLUDE_DIRS ${HARFBUZZ_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(HarfBuzz + REQUIRED_VARS HARFBUZZ_LIBRARY HARFBUZZ_INCLUDE_DIR + VERSION_VAR HARFBUZZ_VERSION) - if(NOT TARGET HarfBuzz::HarfBuzz) + if(HARFBUZZ_FOUND) add_library(HarfBuzz::HarfBuzz UNKNOWN IMPORTED) set_target_properties(HarfBuzz::HarfBuzz PROPERTIES IMPORTED_LOCATION "${HARFBUZZ_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${HARFBUZZ_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP HarfBuzz::HarfBuzz) endif() endif() - -mark_as_advanced(HARFBUZZ_INCLUDE_DIR HARFBUZZ_LIBRARY) From 94250a03ad5ba7365e9492fb3f12f79820d5af3d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 18:00:09 +1000 Subject: [PATCH 211/811] [cmake] FindLCMS2 migrate to full TARGET usage --- cmake/modules/FindLCMS2.cmake | 52 ++++++++++++++--------------------- 1 file changed, 21 insertions(+), 31 deletions(-) diff --git a/cmake/modules/FindLCMS2.cmake b/cmake/modules/FindLCMS2.cmake index d02515815d5da..73075d8997e07 100644 --- a/cmake/modules/FindLCMS2.cmake +++ b/cmake/modules/FindLCMS2.cmake @@ -3,46 +3,36 @@ # ----------- # Finds the LCMS Color Management library # -# This will define the following variables:: +# This will define the following target: # -# LCMS2_FOUND - system has LCMS Color Management -# LCMS2_INCLUDE_DIRS - the LCMS Color Management include directory -# LCMS2_LIBRARIES - the LCMS Color Management libraries -# LCMS2_DEFINITIONS - the LCMS Color Management definitions -# -# and the following imported targets:: -# -# LCMS2::LCMS2 - The LCMS Color Management library - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_LCMS2 lcms2>=2.10 QUIET) -endif() +# LCMS2::LCMS2 - The LCMS Color Management library -find_path(LCMS2_INCLUDE_DIR NAMES lcms2.h - PATHS ${PC_LCMS2_INCLUDEDIR}) -find_library(LCMS2_LIBRARY NAMES lcms2 liblcms2 - PATHS ${PC_LCMS2_LIBDIR}) +if(NOT TARGET LCMS2::LCMS2) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_LCMS2 lcms2>=2.10 QUIET) + endif() -set(LCMS2_VERSION ${PC_LCMS2_VERSION}) + find_path(LCMS2_INCLUDE_DIR NAMES lcms2.h + HINTS ${PC_LCMS2_INCLUDEDIR} + NO_CACHE) + find_library(LCMS2_LIBRARY NAMES lcms2 liblcms2 + HINTS ${PC_LCMS2_LIBDIR} + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(LCMS2 - REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR - VERSION_VAR LCMS2_VERSION) + set(LCMS2_VERSION ${PC_LCMS2_VERSION}) -if(LCMS2_FOUND) - set(LCMS2_LIBRARIES ${LCMS2_LIBRARY}) - set(LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR}) - set(LCMS2_DEFINITIONS -DHAVE_LCMS2=1 -DCMS_NO_REGISTER_KEYWORD=1) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(LCMS2 + REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR + VERSION_VAR LCMS2_VERSION) - if(NOT TARGET LCMS2::LCMS2) + if(LCMS2_FOUND) add_library(LCMS2::LCMS2 UNKNOWN IMPORTED) set_target_properties(LCMS2::LCMS2 PROPERTIES IMPORTED_LOCATION "${LCMS2_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${LCMS2_INCLUDE_DIR}" - INTERFACE_COMPILE_DEFINITIONS HAVE_LCMS2=1) + INTERFACE_COMPILE_DEFINITIONS "HAVE_LCMS2=1;CMS_NO_REGISTER_KEYWORD=1") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP LCMS2::LCMS2) endif() endif() - -mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY) - From b9202156b8d1ad6f484bdb4e60d164ecf5c740ef Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 13:29:08 +1000 Subject: [PATCH 212/811] [cmake] FindWaylandProtocolsWebOS simplify find module Remove manual pkgconfig file from dependency build. It hardcodes things, and isnt needed for our purposes as all. --- cmake/modules/FindWaylandProtocolsWebOS.cmake | 21 ++++++------------- .../target/webos-wayland-extensions/Makefile | 6 ------ 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/cmake/modules/FindWaylandProtocolsWebOS.cmake b/cmake/modules/FindWaylandProtocolsWebOS.cmake index 979c34d6602fa..041ea2bffa761 100644 --- a/cmake/modules/FindWaylandProtocolsWebOS.cmake +++ b/cmake/modules/FindWaylandProtocolsWebOS.cmake @@ -4,24 +4,15 @@ # # This will define the following variables:: # -# WAYLANDPROTOCOLSWEBOS_FOUND - systm has wayland-webos-client # WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR - directory containing the additional webOS Wayland protocols # from the webos-wayland-extensions package -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_WAYLAND_WEBOS_CLIENT wayland-webos-client>=1.0.0) -endif() -find_path(WAYLAND_PROTOCOLS_WEBOS_PROTOCOLDIR NAMES wayland-webos/webos-shell.xml - PATHS ${DEPENDS_PATH}/share) +find_path(WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR NAMES webos-shell.xml + PATH_SUFFIXES wayland-webos + PATHS ${DEPENDS_PATH}/share + REQUIRED) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(WaylandProtocolsWebOS - REQUIRED_VARS WAYLAND_PROTOCOLS_WEBOS_PROTOCOLDIR - VERSION_VAR WAYLAND_WEBOS_SERVER_VERSION) - -if(WAYLANDPROTOCOLSWEBOS_FOUND) - # Promote to cache variables so all code can access it - set(WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR "${WAYLAND_PROTOCOLS_WEBOS_PROTOCOLDIR}/wayland-webos" CACHE INTERNAL "") -endif() +include(FindPackageMessage) +find_package_message(WaylandProtocolsWebOS "Found WaylandProtocols-WebOS: ${WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR}" "[${WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR}]") mark_as_advanced(WAYLANDPROTOCOLSWEBOS_PROTOCOLSDIR) diff --git a/tools/depends/target/webos-wayland-extensions/Makefile b/tools/depends/target/webos-wayland-extensions/Makefile index 64d975bd789b9..90022df192f69 100644 --- a/tools/depends/target/webos-wayland-extensions/Makefile +++ b/tools/depends/target/webos-wayland-extensions/Makefile @@ -17,12 +17,6 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) .installed-$(PLATFORM): $(PLATFORM) mkdir -p $(PREFIX)/share/wayland-webos $(PREFIX)/lib/pkgconfig - rm -f ${PREFIX}/lib/pkgconfig/wayland-webos-client.pc - echo 'includedir=$(PREFIX)/include' >> ${PREFIX}/lib/pkgconfig/wayland-webos-client.pc - echo 'Name: wayland-webos-client' >> ${PREFIX}/lib/pkgconfig/wayland-webos-client.pc - echo 'Description: wayland-webos-client' >> ${PREFIX}/lib/pkgconfig/wayland-webos-client.pc - echo 'Version: 1.0.0' >> ${PREFIX}/lib/pkgconfig/wayland-webos-client.pc - cp $(PLATFORM)/protocol/webos-shell.xml $(PREFIX)/share/wayland-webos cp $(PLATFORM)/protocol/webos-foreign.xml $(PREFIX)/share/wayland-webos touch $@ From 78137d61168f0a8476392ce553ba61daa3a06d8c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 15:41:46 +1000 Subject: [PATCH 213/811] [cmake] FindRapidJSON update comment to match output --- cmake/modules/FindRapidJSON.cmake | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 5f6b290879f98..ace4356b0fbf7 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -3,10 +3,9 @@ # ----------- # Finds the RapidJSON library # -# This will define the following variables:: +# This will define the following target: # -# RapidJSON_FOUND - system has RapidJSON parser -# RapidJSON_INCLUDE_DIRS - the RapidJSON parser include directory +# RapidJSON::RapidJSON - The RapidJSON library # if(NOT TARGET RapidJSON::RapidJSON) From e2423c7daf032fc45e1aac5b6a8f6fddc8b87b91 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 19:19:05 +1000 Subject: [PATCH 214/811] [cmake] Add FindDetours for windows --- cmake/modules/FindDetours.cmake | 37 +++++++++++++++++++++ cmake/platform/windows/windows.cmake | 2 +- xbmc/windowing/windows/WinSystemWin32DX.cpp | 5 --- 3 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 cmake/modules/FindDetours.cmake diff --git a/cmake/modules/FindDetours.cmake b/cmake/modules/FindDetours.cmake new file mode 100644 index 0000000000000..81cc0e9016028 --- /dev/null +++ b/cmake/modules/FindDetours.cmake @@ -0,0 +1,37 @@ +#.rst: +# FindDetours +# -------- +# Finds the Detours library +# +# This will define the following target: +# +# windows::Detours - The Detours library + +if(NOT TARGET windows::Detours) + find_path(DETOURS_INCLUDE_DIR NAMES detours.h + NO_CACHE) + + find_library(DETOURS_LIBRARY_RELEASE NAMES detours + NO_CACHE) + find_library(DETOURS_LIBRARY_DEBUG NAMES detoursd + NO_CACHE) + + include(SelectLibraryConfigurations) + select_library_configurations(DETOURS) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Detours + REQUIRED_VARS DETOURS_LIBRARY DETOURS_INCLUDE_DIR) + + if(DETOURS_FOUND) + add_library(windows::Detours UNKNOWN IMPORTED) + set_target_properties(windows::Detours PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${DETOURS_INCLUDE_DIR}" + IMPORTED_LOCATION "${DETOURS_LIBRARY_RELEASE}") + if(DETOURS_LIBRARY_DEBUG) + set_target_properties(windows::Detours PROPERTIES + IMPORTED_LOCATION_DEBUG "${DETOURS_LIBRARY_DEBUG}") + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP windows::Detours) + endif() +endif() diff --git a/cmake/platform/windows/windows.cmake b/cmake/platform/windows/windows.cmake index b269fe86b5357..a5e13a9c5cb4d 100644 --- a/cmake/platform/windows/windows.cmake +++ b/cmake/platform/windows/windows.cmake @@ -1,3 +1,3 @@ -set(PLATFORM_REQUIRED_DEPS D3DX11Effects) +set(PLATFORM_REQUIRED_DEPS D3DX11Effects Detours) set(APP_RENDER_SYSTEM dx11) list(APPEND PLATFORM_DEFINES -DNTDDI_VERSION=NTDDI_WINBLUE -D_WIN32_WINNT=_WIN32_WINNT_WINBLUE) diff --git a/xbmc/windowing/windows/WinSystemWin32DX.cpp b/xbmc/windowing/windows/WinSystemWin32DX.cpp index 4dff6364645f6..52bea4ac99390 100644 --- a/xbmc/windowing/windows/WinSystemWin32DX.cpp +++ b/xbmc/windowing/windows/WinSystemWin32DX.cpp @@ -25,11 +25,6 @@ #ifndef _M_X64 #include "utils/SystemInfo.h" #endif -#if _DEBUG -#pragma comment(lib, "detoursd.lib") -#else -#pragma comment(lib, "detours.lib") -#endif #pragma comment(lib, "dxgi.lib") #include #include From e5133140dd5b65f7d1598c536433771b5d2761a9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 07:00:08 +1000 Subject: [PATCH 215/811] [cmake] FindDav1d migrate to full TARGET usage --- cmake/modules/FindDav1d.cmake | 107 ++++++++++++++++++--------------- cmake/modules/FindFFMPEG.cmake | 19 +++--- 2 files changed, 67 insertions(+), 59 deletions(-) diff --git a/cmake/modules/FindDav1d.cmake b/cmake/modules/FindDav1d.cmake index 9e91a61395440..160a53226bab9 100644 --- a/cmake/modules/FindDav1d.cmake +++ b/cmake/modules/FindDav1d.cmake @@ -3,60 +3,67 @@ # -------- # Finds the dav1d library # -# This will define the following variables:: +# This will define the following target: # -# DAV1D_FOUND - system has dav1d -# DAV1D_INCLUDE_DIRS - the dav1d include directories -# DAV1D_LIBRARIES - the dav1d libraries - -if(ENABLE_INTERNAL_DAV1D) - include(cmake/scripts/common/ModuleHelpers.cmake) - - set(MODULE_LC dav1d) - - SETUP_BUILD_VARS() - - set(DAV1D_VERSION ${${MODULE}_VER}) - - find_program(NINJA_EXECUTABLE ninja REQUIRED) - find_program(MESON_EXECUTABLE meson REQUIRED) - - set(CONFIGURE_COMMAND ${MESON_EXECUTABLE} - --buildtype=release - --default-library=static - --prefix=${DEPENDS_PATH} - --libdir=lib - -Denable_asm=true - -Denable_tools=false - -Denable_examples=false - -Denable_tests=false - ../dav1d) - set(BUILD_COMMAND ${NINJA_EXECUTABLE}) - set(INSTALL_COMMAND ${NINJA_EXECUTABLE} install) - - BUILD_DEP_TARGET() -else() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_DAV1D dav1d QUIET) - endif() +# dav1d::dav1d - The dav1d library - find_library(DAV1D_LIBRARY NAMES dav1d libdav1d - PATHS ${PC_DAV1D_LIBDIR}) +if(NOT TARGET dav1d::dav1d) + if(ENABLE_INTERNAL_DAV1D) + include(cmake/scripts/common/ModuleHelpers.cmake) - find_path(DAV1D_INCLUDE_DIR NAMES dav1d/dav1d.h - PATHS ${PC_DAV1D_INCLUDEDIR}) + set(MODULE_LC dav1d) - set(DAV1D_VERSION ${PC_DAV1D_VERSION}) -endif() + SETUP_BUILD_VARS() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Dav1d - REQUIRED_VARS DAV1D_LIBRARY DAV1D_INCLUDE_DIR - VERSION_VAR DAV1D_VERSION) + set(DAV1D_VERSION ${${MODULE}_VER}) -if(DAV1D_FOUND) - set(DAV1D_INCLUDE_DIRS ${DAV1D_INCLUDE_DIR}) - set(DAV1D_LIBRARIES ${DAV1D_LIBRARY}) -endif() + find_program(NINJA_EXECUTABLE ninja REQUIRED) + find_program(MESON_EXECUTABLE meson REQUIRED) + + set(CONFIGURE_COMMAND ${MESON_EXECUTABLE} + --buildtype=release + --default-library=static + --prefix=${DEPENDS_PATH} + --libdir=lib + -Denable_asm=true + -Denable_tools=false + -Denable_examples=false + -Denable_tests=false + ../dav1d) + set(BUILD_COMMAND ${NINJA_EXECUTABLE}) + set(INSTALL_COMMAND ${NINJA_EXECUTABLE} install) + + BUILD_DEP_TARGET() + else() + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_DAV1D dav1d QUIET) + endif() + + find_library(DAV1D_LIBRARY NAMES dav1d libdav1d + PATHS ${PC_DAV1D_LIBDIR} + NO_CACHE) + + find_path(DAV1D_INCLUDE_DIR NAMES dav1d/dav1d.h + PATHS ${PC_DAV1D_INCLUDEDIR} + NO_CACHE) -mark_as_advanced(DAV1D_INCLUDE_DIR DAV1D_LIBRARY) + set(DAV1D_VERSION ${PC_DAV1D_VERSION}) + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Dav1d + REQUIRED_VARS DAV1D_LIBRARY DAV1D_INCLUDE_DIR + VERSION_VAR DAV1D_VERSION) + + if(DAV1D_FOUND) + add_library(dav1d::dav1d UNKNOWN IMPORTED) + set_target_properties(dav1d::dav1d PROPERTIES + IMPORTED_LOCATION "${DAV1D_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${DAV1D_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP dav1d::dav1d) + + if(TARGET dav1d) + add_dependencies(dav1d::dav1d dav1d) + endif() + endif() +endif() diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 96d9533406ae4..6cacc4aa1aa0b 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -32,20 +32,21 @@ macro(buildFFMPEG) # Check for dependencies - Must be done before SETUP_BUILD_VARS get_libversion_data("dav1d" "target") find_package(Dav1d ${LIB_DAV1D_VER} MODULE) - if(NOT DAV1D_FOUND) + if(NOT TARGET dav1d::dav1d) message(STATUS "dav1d not found, internal ffmpeg build will be missing AV1 support!") + else() + set(FFMPEG_OPTIONS -DENABLE_DAV1D=ON) endif() set(MODULE_LC ffmpeg) SETUP_BUILD_VARS() - set(FFMPEG_OPTIONS -DENABLE_CCACHE=${ENABLE_CCACHE} - -DCCACHE_PROGRAM=${CCACHE_PROGRAM} - -DENABLE_VAAPI=${ENABLE_VAAPI} - -DENABLE_VDPAU=${ENABLE_VDPAU} - -DENABLE_DAV1D=${DAV1D_FOUND} - -DEXTRA_FLAGS=${FFMPEG_EXTRA_FLAGS}) + list(APPEND FFMPEG_OPTIONS -DENABLE_CCACHE=${ENABLE_CCACHE} + -DCCACHE_PROGRAM=${CCACHE_PROGRAM} + -DENABLE_VAAPI=${ENABLE_VAAPI} + -DENABLE_VDPAU=${ENABLE_VDPAU} + -DEXTRA_FLAGS=${FFMPEG_EXTRA_FLAGS}) if(KODI_DEPENDSBUILD) set(CROSS_ARGS -DDEPENDS_PATH=${DEPENDS_PATH} @@ -87,8 +88,8 @@ macro(buildFFMPEG) BUILD_DEP_TARGET() - if(TARGET dav1d) - add_dependencies(ffmpeg dav1d) + if(TARGET dav1d::dav1d) + add_dependencies(ffmpeg dav1d::dav1d) endif() find_program(BASH_COMMAND bash) From 3b5abdbea5fdd731b6a0f82da59b7ab58b1c4d39 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 15:15:19 +1000 Subject: [PATCH 216/811] [cmake] Fix linux Install.cmake after PR 22112 --- cmake/scripts/linux/Install.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 3f6c7ac6dac01..1564083b7cbe8 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -188,7 +188,7 @@ install(FILES ${CMAKE_SOURCE_DIR}/cmake/scripts/common/AddonHelpers.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/ArchSetup.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/CheckCommits.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/CheckTargetPlatform.cmake - ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateVersionedFiles.cmake + ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateCompileInfo.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GeneratorSetup.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/HandleDepends.cmake ${CMAKE_SOURCE_DIR}/cmake/scripts/common/Macros.cmake From 60761e12ba8390272ae120d8e18575b8e91577fe Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 31 Aug 2023 14:14:38 +0200 Subject: [PATCH 217/811] [PVR] Fix CPVRRecordingsPath path directory/params parsing. --- xbmc/pvr/recordings/PVRRecordingsPath.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/pvr/recordings/PVRRecordingsPath.cpp b/xbmc/pvr/recordings/PVRRecordingsPath.cpp index 85883ea8fbcb6..29bb7cff222f2 100644 --- a/xbmc/pvr/recordings/PVRRecordingsPath.cpp +++ b/xbmc/pvr/recordings/PVRRecordingsPath.cpp @@ -46,7 +46,7 @@ CPVRRecordingsPath::CPVRRecordingsPath(const std::string& strPath) strVarPath.append("/"); else { - size_t paramStart = m_path.find(", TV"); + size_t paramStart = strVarPath.find(", TV"); if (paramStart == std::string::npos) m_directoryPath = strVarPath.substr(GetDirectoryPathPosition()); else From 80b3624d24b6cd6905ec73d841e55f012858c279 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 07:04:39 +1000 Subject: [PATCH 218/811] [cmake] FindFFMPEG remove folder property from ffmpeg::ffmpeg INTERFACE TARGETs cmake < 3.19 only allows specific whitelisted properties on interface targets. Just remove folder property, as its only for visual display in IDE's like VS/Xcode --- cmake/modules/FindFFMPEG.cmake | 8 -------- cmake/modules/FindFlatBuffers.cmake | 3 +-- cmake/modules/FindRapidJSON.cmake | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake index 6cacc4aa1aa0b..d3291743f7c4e 100644 --- a/cmake/modules/FindFFMPEG.cmake +++ b/cmake/modules/FindFFMPEG.cmake @@ -127,37 +127,30 @@ fi") add_library(ffmpeg::libavcodec INTERFACE IMPORTED) set_target_properties(ffmpeg::libavcodec PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libavfilter INTERFACE IMPORTED) set_target_properties(ffmpeg::libavfilter PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libavformat INTERFACE IMPORTED) set_target_properties(ffmpeg::libavformat PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libavutil INTERFACE IMPORTED) set_target_properties(ffmpeg::libavutil PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libswscale INTERFACE IMPORTED) set_target_properties(ffmpeg::libswscale PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libswresample INTERFACE IMPORTED) set_target_properties(ffmpeg::libswresample PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") add_library(ffmpeg::libpostproc INTERFACE IMPORTED) set_target_properties(ffmpeg::libpostproc PROPERTIES - FOLDER "FFMPEG - External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIR}") endmacro() @@ -396,7 +389,6 @@ if(FFMPEG_FOUND) if(NOT TARGET ffmpeg::ffmpeg) add_library(ffmpeg::ffmpeg INTERFACE IMPORTED) set_target_properties(ffmpeg::ffmpeg PROPERTIES - FOLDER "External Projects" INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_INCLUDE_DIRS}" INTERFACE_COMPILE_DEFINITIONS "${_ffmpeg_definitions}") endif() diff --git a/cmake/modules/FindFlatBuffers.cmake b/cmake/modules/FindFlatBuffers.cmake index 0553c976ce5e7..924bcd28c17fa 100644 --- a/cmake/modules/FindFlatBuffers.cmake +++ b/cmake/modules/FindFlatBuffers.cmake @@ -44,8 +44,7 @@ if(NOT TARGET flatbuffers::flatbuffers) add_library(flatbuffers::flatbuffers INTERFACE IMPORTED) set_target_properties(flatbuffers::flatbuffers PROPERTIES - FOLDER "External Projects" - INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${FLATBUFFERS_INCLUDE_DIR}") add_dependencies(flatbuffers::flatbuffers flatbuffers::flatc) diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 5f6b290879f98..bfa8af00c4514 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -64,7 +64,6 @@ if(NOT TARGET RapidJSON::RapidJSON) if(RAPIDJSON_FOUND) add_library(RapidJSON::RapidJSON INTERFACE IMPORTED) set_target_properties(RapidJSON::RapidJSON PROPERTIES - FOLDER "External Projects" INTERFACE_INCLUDE_DIRECTORIES "${RAPIDJSON_INCLUDE_DIRS}") if(TARGET rapidjson) add_dependencies(RapidJSON::RapidJSON rapidjson) From e641616f2f78d20a28885ebc7475a29ee0c14b39 Mon Sep 17 00:00:00 2001 From: Garrett Brown Date: Mon, 11 Sep 2023 13:57:41 -0700 Subject: [PATCH 219/811] tools/depends: Fix rustup by delegating runtime to shebang Rely on the shell script to know which runtimes it's compatible with. Fixes build error: make: bash: Permission denied make: *** [Makefile:29: x86_64-linux-native/bin/rustup] Error 127 --- tools/depends/native/rustup/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/native/rustup/Makefile b/tools/depends/native/rustup/Makefile index 13d145cccda07..0c699b952dce1 100644 --- a/tools/depends/native/rustup/Makefile +++ b/tools/depends/native/rustup/Makefile @@ -26,7 +26,7 @@ $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) $(APP): $(PLATFORM) - bash $(PLATFORM)/rustup-init.sh -y --no-modify-path \ + ./$(PLATFORM)/rustup-init.sh -y --no-modify-path \ --profile minimal \ --default-toolchain=$(RUST_TOOLCHAIN_VERSION) From 7fbdd3cb4ced0e64d9d9af98ff2dbd82167c67a6 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 12 Sep 2023 15:38:18 +0100 Subject: [PATCH 220/811] [macos] Fix include name --- xbmc/platform/darwin/osx/network/NetworkMacOS.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm index 649007bc1e8ee..ac63317cf908c 100644 --- a/xbmc/platform/darwin/osx/network/NetworkMacOS.mm +++ b/xbmc/platform/darwin/osx/network/NetworkMacOS.mm @@ -6,7 +6,7 @@ * See LICENSES/README.md for more information. */ -#include "NetworkmacOS.h" +#include "NetworkMacOS.h" #include "utils/StringUtils.h" #include "utils/log.h" From 5fe88ed25197923592ea3973b1ccab271112527f Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 12 Sep 2023 15:39:08 +0100 Subject: [PATCH 221/811] [tests] Fixup unused variable --- xbmc/utils/test/TestGPUInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/utils/test/TestGPUInfo.cpp b/xbmc/utils/test/TestGPUInfo.cpp index ce3d4a6d79f61..331a62c3bd907 100644 --- a/xbmc/utils/test/TestGPUInfo.cpp +++ b/xbmc/utils/test/TestGPUInfo.cpp @@ -32,6 +32,7 @@ TEST_F(TestGPUInfo, GetTemperature) EXPECT_NE(gpuInfo, nullptr); CTemperature t; bool success = gpuInfo->GetTemperature(t); + EXPECT_TRUE(success); EXPECT_TRUE(t.IsValid()); EXPECT_EQ(t.ToCelsius(), 50); } From 41e6ed5342627e0b46955487a3b7ed9192e4061d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 13 Sep 2023 09:08:07 +1000 Subject: [PATCH 222/811] [cmake] Remove XBT_FILES as a dependency of generate-packaging TARGET Ninja doesnt handle the dependency without a clear indication of where the files are from. As they are generated as part of gen_skin_pack, we already have an order dependency in place so we do not need XBT_FILES --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4df3257a55e43..8e137c447cb05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -438,7 +438,7 @@ add_custom_target(gen_skin_pack # Packaging target. This generates system addon, xbt creation, copy files to build tree add_custom_target(generate-packaging ALL - DEPENDS TexturePacker::TexturePacker::Executable export-files gen_skin_pack gen_system_addons ${XBT_FILES}) + DEPENDS TexturePacker::TexturePacker::Executable export-files gen_skin_pack gen_system_addons) # Make sure we build any libs before we look to export-files. # We may need to export some shared libs/data (eg Python) add_dependencies(export-files ${GLOBAL_TARGET_DEPS}) From 5422ac5f4217b9b3690396a8e2c31bb68d90078a Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Thu, 17 Feb 2022 20:02:06 +0000 Subject: [PATCH 223/811] [JSON] Add new song video fields to json-rpc --- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 2 ++ xbmc/interfaces/json-rpc/schema/methods.json | 3 ++- xbmc/interfaces/json-rpc/schema/types.json | 13 ++++++++----- xbmc/interfaces/json-rpc/schema/version.txt | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index 3521a3a8aefab..813c4c85c4659 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -991,6 +991,8 @@ JSONRPC_STATUS CAudioLibrary::SetSongDetails(const std::string &method, ITranspo song.strOrigReleaseDate = parameterObject["originaldate"].asString(); if (ParameterNotNull(parameterObject, "albumreleasedate")) song.strReleaseDate = parameterObject["albumreleasedate"].asString(); + if (ParameterNotNull(parameterObject, "songvideourl")) + song.songVideoURL = parameterObject["songvideourl"].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. diff --git a/xbmc/interfaces/json-rpc/schema/methods.json b/xbmc/interfaces/json-rpc/schema/methods.json index 4d396cf6bacfa..c33e23ea31997 100644 --- a/xbmc/interfaces/json-rpc/schema/methods.json +++ b/xbmc/interfaces/json-rpc/schema/methods.json @@ -1220,7 +1220,8 @@ { "name": "disctitle", "$ref": "Optional.String" }, { "name": "releasedate", "$ref": "Optional.String" }, { "name": "originaldate", "$ref": "Optional.String" }, - { "name": "bpm", "$ref": "Optional.Integer" } + { "name": "bpm", "$ref": "Optional.Integer" }, + { "name": "songvideourl", "$ref": "Optional.String" } ], "returns": "string" }, diff --git a/xbmc/interfaces/json-rpc/schema/types.json b/xbmc/interfaces/json-rpc/schema/types.json index 86e8b22961be8..7310ddf55be01 100644 --- a/xbmc/interfaces/json-rpc/schema/types.json +++ b/xbmc/interfaces/json-rpc/schema/types.json @@ -545,7 +545,8 @@ "votes", "userrating", "mood", "contributors", "displaycomposer", "displayconductor", "displayorchestra", "displaylyricist", "sortartist", "art", "sourceid", "disctitle", "releasedate", "originaldate", - "bpm", "samplerate", "bitrate", "channels", "datemodified", "datenew" ] + "bpm", "samplerate", "bitrate", "channels", "datemodified", "datenew", + "songvideourl" ] } }, "Audio.Album.ReleaseType": { @@ -692,7 +693,8 @@ "bpm": { "type": "Integer" }, "samplerate": { "type": "Integer" }, "bitrate": { "type": "Integer"}, - "channels": { "type": "Integer"} + "channels": { "type": "Integer"}, + "songvideourl": { "type": "string" } } }, "Audio.Property.Name": { @@ -1572,7 +1574,8 @@ "samplerate": { "type": "integer" }, "channels": { "type": "integer"}, "albumstatus": { "type": "string" }, - "customproperties": { "$ref": "Item.CustomProperties" } + "customproperties": { "$ref": "Item.CustomProperties" }, + "songvideourl": { "type": "string" } } }, "List.Fields.All": { @@ -1597,7 +1600,7 @@ "musicbrainzreleasegroupid", "mediapath", "dynpath", "isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm", "bitrate", "samplerate", "channels", "albumstatus", "datemodified", "datenew", "customproperties", - "albumduration"] + "albumduration", "songvideourl"] } }, "List.Item.All": { @@ -1631,7 +1634,7 @@ "specialsortseason", "specialsortepisode", "sortartist", "musicbrainzreleasegroupid", "isboxset", "totaldiscs", "disctitle", "releasedate", "originaldate", "bpm", "bitrate", "samplerate", "channels", "datemodified", "datenew", "customproperties", - "albumduration", "userrating"] + "albumduration", "userrating", "songvideourl" ] } }, "List.Item.File": { diff --git a/xbmc/interfaces/json-rpc/schema/version.txt b/xbmc/interfaces/json-rpc/schema/version.txt index 3f74ab3e4c5c9..998f113d61775 100644 --- a/xbmc/interfaces/json-rpc/schema/version.txt +++ b/xbmc/interfaces/json-rpc/schema/version.txt @@ -1 +1 @@ -JSONRPC_VERSION 13.2.1 +JSONRPC_VERSION 13.3.0 From 78c00ad9a64bbec208803548939ff089cc183094 Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Thu, 17 Feb 2022 19:59:08 +0000 Subject: [PATCH 224/811] [MUSIC] Add, store and process scraped video links for artist songs --- xbmc/GUIInfoManager.cpp | 8 ++ xbmc/addons/Scraper.cpp | 17 +++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 + xbmc/guilib/guiinfo/MusicGUIInfo.cpp | 3 + xbmc/music/Artist.cpp | 29 ++++++ xbmc/music/Artist.h | 10 ++ xbmc/music/MusicDatabase.cpp | 150 +++++++++++++++++++++++++-- xbmc/music/MusicDatabase.h | 9 +- xbmc/music/Song.cpp | 3 + xbmc/music/Song.h | 1 + xbmc/music/tags/MusicInfoTag.cpp | 15 +++ xbmc/music/tags/MusicInfoTag.h | 3 + 12 files changed, 238 insertions(+), 11 deletions(-) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index ac4e0f7d413c3..130204dfc877c 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -6857,6 +6857,13 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, ///


/// @skinning_v20 **[New Infolabel]** \link ListItem_HdrType `ListItem.HdrType`\endlink /// } +/// \table_row3{ `ListItem.SongVideoURL`, +/// \anchor ListItem_SongVideoURL +/// _string_, +/// @return Link to a video of a song +///


+/// @skinning_v21 **[New Infolabel]** \link ListItem_SongVideoURL `ListItem.SongVideoURL`\endlink +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -7069,6 +7076,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "albumstatus", LISTITEM_ALBUMSTATUS }, { "isautoupdateable", LISTITEM_ISAUTOUPDATEABLE }, { "hdrtype", LISTITEM_VIDEO_HDR_TYPE }, + { "songvideourl", LISTITEM_SONG_VIDEO_URL }, }; /// \page modules__infolabels_boolean_conditions diff --git a/xbmc/addons/Scraper.cpp b/xbmc/addons/Scraper.cpp index 30c22473afcbf..ad7b25105251c 100644 --- a/xbmc/addons/Scraper.cpp +++ b/xbmc/addons/Scraper.cpp @@ -816,6 +816,23 @@ void DetailsFromFileItem(const CFileItem &item, CArtist &artist) artist.discography.emplace_back(discoAlbum); } + const int numvideolinks = item.GetProperty("artist.videolinks").asInteger32(); + if (numvideolinks > 0) + { + artist.videolinks.reserve(numvideolinks); + for (int i = 1; i <= numvideolinks; ++i) + { + std::stringstream prefix; + prefix << "artist.videolink" << i; + ArtistVideoLinks videoLink; + videoLink.title = FromString(item, prefix.str() + ".title"); + videoLink.mbTrackID = FromString(item, prefix.str() + ".mbtrackid"); + videoLink.videoURL = FromString(item, prefix.str() + ".url"); + videoLink.thumbURL = FromString(item, prefix.str() + ".thumb"); + artist.videolinks.emplace_back(std::move(videoLink)); + } + } + int nThumbs = item.GetProperty("artist.thumbs").asInteger32(); ParseThumbs(artist.thumbURL, item, nThumbs, "artist.thumb"); diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index 5d186bc2efff7..dfb5b7f4223b1 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -962,6 +962,7 @@ #define LISTITEM_ALBUMSTATUS (LISTITEM_START + 206) #define LISTITEM_ISAUTOUPDATEABLE (LISTITEM_START + 207) #define LISTITEM_VIDEO_HDR_TYPE (LISTITEM_START + 208) +#define LISTITEM_SONG_VIDEO_URL (LISTITEM_START + 209) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp index f58a9ae3c4b0f..ca79c2c8dea05 100644 --- a/xbmc/guilib/guiinfo/MusicGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/MusicGUIInfo.cpp @@ -403,6 +403,9 @@ bool CMusicGUIInfo::GetLabel(std::string& value, const CFileItem *item, int cont return true; } break; + case LISTITEM_SONG_VIDEO_URL: + value = tag->GetSongVideoURL(); + return true; } } diff --git a/xbmc/music/Artist.cpp b/xbmc/music/Artist.cpp index 786f7a737b2a8..c5626c58ebd0d 100644 --- a/xbmc/music/Artist.cpp +++ b/xbmc/music/Artist.cpp @@ -62,6 +62,7 @@ void CArtist::MergeScrapedArtist(const CArtist& source, bool override /* = true art = source.art; discography = source.discography; + videolinks = source.videolinks; } @@ -133,6 +134,24 @@ bool CArtist::Load(const TiXmlElement *artist, bool append, bool prioritise) node = node->NextSiblingElement("album"); } + //song video links + const TiXmlElement* songurls = artist->FirstChildElement("videourl"); + if (songurls) + videolinks.clear(); + while (songurls) + { + if (songurls->FirstChild()) + { + ArtistVideoLinks videoLink; + XMLUtils::GetString(songurls, "title", videoLink.title); + XMLUtils::GetString(songurls, "musicbrainztrackid", videoLink.mbTrackID); + XMLUtils::GetString(songurls, "url", videoLink.videoURL); + XMLUtils::GetString(songurls, "thumburl", videoLink.thumbURL); + videolinks.emplace_back(std::move(videoLink)); + } + songurls = songurls->NextSiblingElement("videourl"); + } + // Support old style for backwards compatibility of old nfo files and scrapers const TiXmlElement *fanart2 = artist->FirstChildElement("fanart"); if (fanart2) @@ -218,6 +237,16 @@ bool CArtist::Save(TiXmlNode *node, const std::string &tag, const std::string& s XMLUtils::SetString(node, "year", it.strYear); XMLUtils::SetString(node, "musicbrainzreleasegroupid", it.strReleaseGroupMBID); } + // song video links + for (const auto& it : videolinks) + { + TiXmlElement videolinkElement("videourl"); + TiXmlNode* node = artist->InsertEndChild(videolinkElement); + XMLUtils::SetString(node, "title", it.title); + XMLUtils::SetString(node, "musicbrainztrackid", it.mbTrackID); + XMLUtils::SetString(node, "url", it.videoURL); + XMLUtils::SetString(node, "thumburl", it.thumbURL); + } return true; } diff --git a/xbmc/music/Artist.h b/xbmc/music/Artist.h index 2ec77a82549d7..8791288f263a6 100644 --- a/xbmc/music/Artist.h +++ b/xbmc/music/Artist.h @@ -29,6 +29,14 @@ class CDiscoAlbum std::string strReleaseGroupMBID; }; +struct ArtistVideoLinks +{ + std::string title; + std::string mbTrackID; + std::string videoURL; + std::string thumbURL; +}; + class CArtist { public: @@ -76,6 +84,7 @@ class CArtist dateNew.Reset(); bScrapedMBID = false; strLastScraped.clear(); + videolinks.clear(); } /*! \brief Load artist information from an XML file. @@ -117,6 +126,7 @@ class CArtist CDateTime dateNew; // Time db record created bool bScrapedMBID = false; std::string strLastScraped; + std::vector videolinks; }; class CArtistCredit diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index 69819a98e48ef..dd54a6379abda 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -203,6 +203,7 @@ void CMusicDatabase::CreateTables() " comment text, mood text, iBPM INTEGER NOT NULL DEFAULT 0, " " iBitRate INTEGER NOT NULL DEFAULT 0, " " iSampleRate INTEGER NOT NULL DEFAULT 0, iChannels INTEGER NOT NULL DEFAULT 0, " + " strVideoURL TEXT, " " strReplayGain text, " " dateAdded TEXT, dateNew TEXT, dateModified TEXT)"); CLog::Log(LOGINFO, "create song_artist table"); @@ -452,6 +453,7 @@ void CMusicDatabase::CreateViews() " iBitRate, " " iSampleRate, " " iChannels, " + " song.strVideoURL as strVideoURL, " " album.iAlbumDuration AS iAlbumDuration, " " album.iDiscTotal as iDiscTotal, " " song.dateAdded as dateAdded, " @@ -769,6 +771,7 @@ bool CMusicDatabase::AddAlbum(CAlbum& album, int idSource) song->userrating, // song->votes, // song->iBPM, song->iBitRate, song->iSampleRate, song->iChannels, // + song->songVideoURL, // song->replayGain); // Song must have at least one artist so set artist to [Missing] @@ -1019,6 +1022,7 @@ int CMusicDatabase::AddSong(const int idSong, int iBitRate, int iSampleRate, int iChannels, + const std::string& songVideoURL, const ReplayGain& replayGain) { int idNew = -1; @@ -1146,7 +1150,7 @@ int CMusicDatabase::AddSong(const int idSong, dtLastPlayed, // rating, userrating, votes, // replayGain, // - iBPM, iBitRate, iSampleRate, iChannels); + iBPM, iBitRate, iSampleRate, iChannels, songVideoURL); } if (!strThumb.empty()) SetArtForItem(idNew, MediaTypeSong, "thumb", strThumb); @@ -1238,7 +1242,8 @@ bool CMusicDatabase::UpdateSong(CSong& song, bool bArtists /*= true*/, bool bArt song.lastPlayed, // song.rating, song.userrating, song.votes, // song.replayGain, // - song.iBPM, song.iBitRate, song.iSampleRate, song.iChannels); + song.iBPM, song.iBitRate, song.iSampleRate, song.iChannels, // + song.songVideoURL); if (result < 0) return false; @@ -1297,7 +1302,8 @@ int CMusicDatabase::UpdateSong(int idSong, int iBPM, int iBitRate, int iSampleRate, - int iChannels) + int iChannels, + const std::string& songVideoURL) { if (idSong < 0) return -1; @@ -1319,7 +1325,7 @@ int CMusicDatabase::UpdateSong(int idSong, " strTitle = '%s', iTrack = %i, iDuration = %i, " "strReleaseDate = '%s', strOrigReleaseDate = '%s', strDiscSubtitle = '%s', " "strFileName = '%s', iBPM = %i, iBitrate = %i, iSampleRate = %i, iChannels = %i, " - "dateAdded = '%s'", + "dateAdded = '%s', strVideoURL = '%s'", idPath, artistDisp.c_str(), StringUtils::Join( genres, @@ -1327,7 +1333,7 @@ int CMusicDatabase::UpdateSong(int idSong, .c_str(), strTitle.c_str(), iTrack, iDuration, strRelease.c_str(), strOriginal.c_str(), strDiscSubtitle.c_str(), strFileName.c_str(), iBPM, iBitRate, iSampleRate, iChannels, - strDateMedia.c_str()); + strDateMedia.c_str(), songVideoURL.c_str()); if (strMusicBrainzTrackID.empty()) strSQL += PrepareSQL(", strMusicBrainzTrackID = NULL"); else @@ -1756,6 +1762,11 @@ bool CMusicDatabase::UpdateArtist(const CArtist& artist) AddArtistDiscography(artist.idArtist, disc); } + if (!DeleteArtistVideoLinks(artist.idArtist)) + CLog::Log(LOGERROR, "MusicDatabase: Error deleting ArtistVideoLinks"); + + AddArtistVideoLinks(artist); + // Set current artwork (held in art table) if (!artist.art.empty()) SetArtForItem(artist.idArtist, MediaTypeArtist, artist.art); @@ -2037,6 +2048,8 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist& artist, bool fetchAll /* = return false; if (nullptr == m_pDS) return false; + if (nullptr == m_pDS2) + return false; if (idArtist == -1) return false; // not in the database @@ -2057,7 +2070,7 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist& artist, bool fetchAll /* = m_pDS->close(); return false; } - + std::string debugSQL = strSQL + " - "; int discographyOffset = artist_enumCount; artist.discography.clear(); @@ -2077,12 +2090,36 @@ bool CMusicDatabase::GetArtist(int idArtist, CArtist& artist, bool fetchAll /* = } m_pDS->close(); // cleanup recordset data + artist.videolinks.clear(); + if (fetchAll) + { + strSQL = PrepareSQL("SELECT idSong, strTitle, strMusicBrainzTrackID, strVideoURL, url " + "FROM song JOIN album_artist ON song.idAlbum = album_artist.idAlbum " + "LEFT JOIN art ON art.media_id = song.idSong AND art.type = 'videothumb' " + "WHERE album_artist.idArtist = %i AND " + "song.strVideoURL is not NULL GROUP by song.strVideoURL ORDER BY idSong", + idArtist); + debugSQL += strSQL; + m_pDS->query(strSQL); + while (!m_pDS->eof()) + { + const dbiplus::sql_record* const record = m_pDS->get_sql_record(); + ArtistVideoLinks videoLink; + videoLink.title = record->at(1).get_asString(); + videoLink.mbTrackID = record->at(2).get_asString(); + videoLink.videoURL = record->at(3).get_asString(); + videoLink.thumbURL = record->at(4).get_asString(); + + artist.videolinks.emplace_back(std::move(videoLink)); + m_pDS->next(); + } + m_pDS->close(); + } + auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); - CLog::Log(LOGDEBUG, LOGDATABASE, "{0}({1}) - took {2} ms", __FUNCTION__, strSQL, - duration.count()); - + CLog::LogF(LOGDEBUG, "{} - took {} ms", debugSQL, duration.count()); return true; } catch (...) @@ -2180,6 +2217,92 @@ bool CMusicDatabase::ClearArtistLastScrapedTime(int idArtist) return ExecuteQuery(strSQL); } +bool CMusicDatabase::AddArtistVideoLinks(const CArtist& artist) +{ + auto start = std::chrono::steady_clock::now(); + std::string dbSong; + + try + { + if (nullptr == m_pDB || nullptr == m_pDS || nullptr == m_pDS2) + return false; + + for (const auto& videoURL : artist.videolinks) + { + dbSong = videoURL.title; + std::string strSQL = PrepareSQL( + "SELECT idSong, strTitle FROM song WHERE strMusicBrainzTrackID = '%s' OR (EXISTS " + "(SELECT 1 FROM album_artist WHERE album_artist.idAlbum = song.idAlbum AND " + "album_artist.idArtist = '%i' AND song.strTitle LIKE '%%%s%%'))", + videoURL.mbTrackID.c_str(), artist.idArtist, videoURL.title.c_str()); + + if (!m_pDS->query(strSQL)) + return false; + if (m_pDS->num_rows() == 0) + continue; + + while (!m_pDS->eof()) + { + const int songId = m_pDS->fv(0).get_asInt(); + std::string strSQL2 = PrepareSQL("UPDATE song SET strVideoURL='%s' WHERE idSong = %i", + videoURL.videoURL.c_str(), songId); + CLog::Log(LOGDEBUG, "Adding videolink for song {} with id {}", dbSong.c_str(), songId); + m_pDS2->exec(strSQL2); + + if (!videoURL.thumbURL.empty()) + { // already have a videothumb for this song ? + strSQL2 = PrepareSQL("SELECT art_id FROM art " + "WHERE media_id=%i AND media_type='%s' AND type='videothumb'", + songId, MediaTypeSong); + m_pDS2->query(strSQL2); + if (!m_pDS2->eof()) + { // update existing thumb + const int artId = m_pDS2->fv(0).get_asInt(); + m_pDS2->close(); + strSQL2 = PrepareSQL("UPDATE art SET url='%s' where art_id=%d", + videoURL.thumbURL.c_str(), artId); + m_pDS2->exec(strSQL2); + } + else + { // insert new thumb + m_pDS2->close(); + strSQL2 = PrepareSQL("INSERT INTO art(media_id, media_type, type, url) " + "VALUES (%d, '%s', '%s', '%s')", + songId, MediaTypeSong, "videothumb", videoURL.thumbURL.c_str()); + m_pDS2->exec(strSQL2); + } + m_pDS2->close(); + } + m_pDS->next(); + } + m_pDS->close(); + } + auto end = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(end - start); + CLog::LogF(LOGDEBUG, "Time to store videolinks {}ms ", duration.count()); + return true; + } + catch (...) + { + CLog::Log(LOGERROR, "MusicDatabase: Unable to add videolink for song ({})", dbSong.c_str()); + return false; + } +} + +bool CMusicDatabase::DeleteArtistVideoLinks(const int idArtist) +{ + std::string strSQL = PrepareSQL("UPDATE song SET strVideoURL = NULL WHERE idAlbum IN " + "(SELECT idAlbum FROM album_artist WHERE idArtist = %i)", + idArtist); + if (!ExecuteQuery(strSQL)) + return false; + strSQL = PrepareSQL( + "DELETE FROM art WHERE art.type = 'videothumb' AND art.media_id IN (SELECT idSong FROM song " + "JOIN album_artist ON song.idAlbum = album_artist.idAlbum WHERE album_artist.idArtist = %i)", + idArtist); + return ExecuteQuery(strSQL); +} + int CMusicDatabase::AddArtistDiscography(int idArtist, const CDiscoAlbum& discoAlbum) { std::string strSQL = PrepareSQL("INSERT INTO discography " @@ -2994,6 +3117,7 @@ CSong CMusicDatabase::GetSongFromDataset(const dbiplus::sql_record* const record song.iBitRate = record->at(offset + song_iBitRate).get_asInt(); song.iSampleRate = record->at(offset + song_iSampleRate).get_asInt(); song.iChannels = record->at(offset + song_iChannels).get_asInt(); + song.songVideoURL = record->at(offset + song_songVideoURL).get_asString(); return song; } @@ -3056,6 +3180,7 @@ void CMusicDatabase::GetFileItemFromDataset(const dbiplus::sql_record* const rec replaygain.Set(record->at(song_strReplayGain).get_asString()); item->GetMusicInfoTag()->SetReplayGain(replaygain); item->GetMusicInfoTag()->SetTotalDiscs(record->at(song_iDiscTotal).get_asInt()); + item->GetMusicInfoTag()->SetSongVideoURL(record->at(song_songVideoURL).get_asString()); item->GetMusicInfoTag()->SetLoaded(true); // Get filename with full path @@ -7578,6 +7703,7 @@ static const translateJSONField JSONtoDBSong[] = { { "bitrate", "integer", true, "iBitRate", "" }, { "samplerate", "integer", true, "iSampleRate", "" }, { "channels", "integer", true, "iChannels", "" }, + { "songvideourl", "string", true, "strVideoURL", "" }, // JOIN fields (multivalue), same order as _JoinToSongFields { "albumartistid", "array", false, "idAlbumArtist", "album_artist.idArtist AS idAlbumArtist" }, @@ -9288,6 +9414,10 @@ void CMusicDatabase::UpdateTables(int version) m_pDS->exec("DROP TABLE artist"); m_pDS->exec("ALTER TABLE artist_new RENAME TO artist"); } + + if (version < 83) + m_pDS->exec("ALTER TABLE song ADD strVideoURL TEXT"); + // Set the version of tag scanning required. // Not every schema change requires the tags to be rescanned, set to the highest schema version // that needs this. Forced rescanning (of music files that have not changed since they were @@ -9308,7 +9438,7 @@ void CMusicDatabase::UpdateTables(int version) int CMusicDatabase::GetSchemaVersion() const { - return 82; + return 83; } int CMusicDatabase::GetMusicNeedsTagScan() diff --git a/xbmc/music/MusicDatabase.h b/xbmc/music/MusicDatabase.h index 8544eb297d64a..791ff3d9cda2b 100644 --- a/xbmc/music/MusicDatabase.h +++ b/xbmc/music/MusicDatabase.h @@ -140,6 +140,7 @@ class CMusicDatabase : public CDatabase \param rating [in] a rating for the song \param userrating [in] a userrating (my rating) for the song \param votes [in] a vote counter for the song rating + \param songVideoURL [in] url to video of the song \param replayGain [in] album and track replaygain and peak values \return the id of the song */ @@ -171,6 +172,7 @@ class CMusicDatabase : public CDatabase int iBitRate, int iSampleRate, int iChannels, + const std::string& songVideoURL, const ReplayGain& replayGain); bool GetSong(int idSong, CSong& song); @@ -210,6 +212,7 @@ class CMusicDatabase : public CDatabase \param iBitRate [in] the bitrate of the song file \param iSampleRate [in] the sample rate of the song file \param iChannels [in] the number of audio channels in the song file + \param songVideoURL [in] url link to a video of the song \return the id of the song */ int UpdateSong(int idSong, @@ -238,7 +241,8 @@ class CMusicDatabase : public CDatabase int iBPM, int iBitRate, int iSampleRate, - int iChannels); + int iChannels, + const std::string& songVideoURL); //// Misc Song bool GetSongByFileName(const std::string& strFileName, CSong& song, int64_t startOffset = 0); @@ -405,6 +409,8 @@ class CMusicDatabase : public CDatabase int AddArtistDiscography(int idArtist, const CDiscoAlbum& discoAlbum); bool DeleteArtistDiscography(int idArtist); bool GetArtistDiscography(int idArtist, CFileItemList& items); + bool AddArtistVideoLinks(const CArtist& artist); + bool DeleteArtistVideoLinks(const int idArtist); std::string GetArtistById(int id); int GetArtistByName(const std::string& strArtist); @@ -1017,6 +1023,7 @@ class CMusicDatabase : public CDatabase song_iBitRate, song_iSampleRate, song_iChannels, + song_songVideoURL, song_iAlbumDuration, song_iDiscTotal, song_dateAdded, diff --git a/xbmc/music/Song.cpp b/xbmc/music/Song.cpp index 165889ca92444..93b2156d7b3bb 100644 --- a/xbmc/music/Song.cpp +++ b/xbmc/music/Song.cpp @@ -75,6 +75,7 @@ CSong::CSong(CFileItem& item) iSampleRate = tag.GetSampleRate(); iBitRate = tag.GetBitRate(); iChannels = tag.GetNoOfChannels(); + songVideoURL = tag.GetSongVideoURL(); } CSong::CSong() @@ -239,6 +240,7 @@ void CSong::Serialize(CVariant& value) const value["bitrate"] = iBitRate; value["samplerate"] = iSampleRate; value["channels"] = iChannels; + value["songvideourl"] = songVideoURL; } void CSong::Clear() @@ -279,6 +281,7 @@ void CSong::Clear() iBitRate = 0; iSampleRate = 0; iChannels = 0; + songVideoURL.clear(); replayGain = ReplayGain(); } diff --git a/xbmc/music/Song.h b/xbmc/music/Song.h index 3fc127a60f427..737ba46eed7c8 100644 --- a/xbmc/music/Song.h +++ b/xbmc/music/Song.h @@ -194,6 +194,7 @@ class CSong final : public ISerializable int iChannels; std::string strRecordLabel; // Record label from tag for album processing by CMusicInfoScanner::FileItemsToAlbums std::string strAlbumType; // (Musicbrainz release type) album type from tag for album processing by CMusicInfoScanner::FileItemsToAlbums + std::string songVideoURL; // url to song video ReplayGain replayGain; private: diff --git a/xbmc/music/tags/MusicInfoTag.cpp b/xbmc/music/tags/MusicInfoTag.cpp index 62548bf5d558c..50c7ae773b957 100644 --- a/xbmc/music/tags/MusicInfoTag.cpp +++ b/xbmc/music/tags/MusicInfoTag.cpp @@ -323,6 +323,11 @@ const std::string& CMusicInfoTag::GetStationArt() const return m_stationArt; } +const std::string& CMusicInfoTag::GetSongVideoURL() const +{ + return m_songVideoURL; +} + void CMusicInfoTag::SetURL(const std::string& strURL) { m_strURL = strURL; @@ -774,6 +779,11 @@ void CMusicInfoTag::SetStationArt(const std::string& strStationArt) m_stationArt = strStationArt; } +void CMusicInfoTag::SetSongVideoURL(const std::string& songVideoURL) +{ + m_songVideoURL = songVideoURL; +} + void CMusicInfoTag::SetArtist(const CArtist& artist) { SetArtist(artist.strArtist); @@ -882,6 +892,7 @@ void CMusicInfoTag::SetSong(const CSong& song) SetBitRate(song.iBitRate); SetSampleRate(song.iSampleRate); SetNoOfChannels(song.iChannels); + SetSongVideoURL(song.songVideoURL); if (song.replayGain.Get(ReplayGain::TRACK).Valid()) m_replayGain.Set(ReplayGain::TRACK, song.replayGain.Get(ReplayGain::TRACK)); @@ -970,6 +981,7 @@ void CMusicInfoTag::Serialize(CVariant& value) const value["bitrate"] = m_bitrate; value["samplerate"] = m_samplerate; value["channels"] = m_channels; + value["songvideourl"] = m_songVideoURL; } void CMusicInfoTag::ToSortable(SortItem& sortable, Field field) const @@ -1071,6 +1083,7 @@ void CMusicInfoTag::Archive(CArchive& ar) ar << m_samplerate; ar << m_bitrate; ar << m_channels; + ar << m_songVideoURL; } else { @@ -1138,6 +1151,7 @@ void CMusicInfoTag::Archive(CArchive& ar) ar >> m_samplerate; ar >> m_bitrate; ar >> m_channels; + ar >> m_songVideoURL; } } @@ -1193,6 +1207,7 @@ void CMusicInfoTag::Clear() m_channels = 0; m_stationName.clear(); m_stationArt.clear(); + m_songVideoURL.clear(); } void CMusicInfoTag::AppendArtist(const std::string &artist) diff --git a/xbmc/music/tags/MusicInfoTag.h b/xbmc/music/tags/MusicInfoTag.h index 3c1b994425313..0b264a93010b1 100644 --- a/xbmc/music/tags/MusicInfoTag.h +++ b/xbmc/music/tags/MusicInfoTag.h @@ -85,6 +85,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo const std::string& GetAlbumReleaseStatus() const; const std::string& GetStationName() const; const std::string& GetStationArt() const; + const std::string& GetSongVideoURL() const; const EmbeddedArtInfo &GetCoverArtInfo() const; const ReplayGain& GetReplayGain() const; CAlbum::ReleaseType GetAlbumReleaseType() const; @@ -157,6 +158,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo void SetAlbumReleaseStatus(const std::string& strReleaseStatus); void SetStationName(const std::string& strStationName); // name of online radio station void SetStationArt(const std::string& strStationArt); + void SetSongVideoURL(const std::string& songVideoURL); // link to video of song /*! \brief Append a unique artist to the artist list Checks if we have this artist already added, and if not adds it to the songs artist list. @@ -255,6 +257,7 @@ class CMusicInfoTag final : public IArchivable, public ISerializable, public ISo int m_bitrate; std::string m_stationName; std::string m_stationArt; // Used to fetch thumb URL for Shoutcasts + std::string m_songVideoURL; // link to a video for a song EmbeddedArtInfo m_coverArt; ///< art information From 5dc4cccc20348f4fd993a303416feeddcc154f3a Mon Sep 17 00:00:00 2001 From: the-black-eagle Date: Sun, 20 Aug 2023 09:51:52 +0100 Subject: [PATCH 225/811] [COSMETIC] Add clang-format off/on tags --- xbmc/GUIInfoManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 130204dfc877c..32666e4ebce35 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -6867,6 +6867,7 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// \table_end /// /// ----------------------------------------------------------------------------- +// clang-format off const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "icon", LISTITEM_ICON }, { "actualicon", LISTITEM_ACTUAL_ICON }, @@ -7078,6 +7079,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "hdrtype", LISTITEM_VIDEO_HDR_TYPE }, { "songvideourl", LISTITEM_SONG_VIDEO_URL }, }; +// clang-format on /// \page modules__infolabels_boolean_conditions /// \subsection modules__infolabels_boolean_conditions_Visualisation Visualisation From 1bebf11c701e5fb39002f8daf364c9a454b6735d Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 19 Aug 2023 15:19:05 -0600 Subject: [PATCH 226/811] add thumb loader for chapters --- xbmc/TextureCacheJob.cpp | 6 +++ xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 8 ++- xbmc/cores/VideoPlayer/DVDFileInfo.h | 3 +- xbmc/imagefiles/SpecialImageLoaderFactory.cpp | 2 + xbmc/imagefiles/SpecialImageLoaderFactory.h | 2 +- xbmc/video/CMakeLists.txt | 6 ++- xbmc/video/VideoChapterImageFileLoader.cpp | 54 +++++++++++++++++++ xbmc/video/VideoChapterImageFileLoader.h | 31 +++++++++++ 8 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 xbmc/video/VideoChapterImageFileLoader.cpp create mode 100644 xbmc/video/VideoChapterImageFileLoader.h diff --git a/xbmc/TextureCacheJob.cpp b/xbmc/TextureCacheJob.cpp index 0139993f3ac66..66edb392023e3 100644 --- a/xbmc/TextureCacheJob.cpp +++ b/xbmc/TextureCacheJob.cpp @@ -212,6 +212,12 @@ std::string CTextureCacheJob::DecodeImageURL(const std::string &url, unsigned in scalingAlgorithm = CPictureScalingAlgorithm::FromString(thumbURL.GetOption("scaling_algorithm")); } + if (StringUtils::StartsWith(url, "chapter://")) + { + // workaround for chapter thumbnail paths, which don't yet conform to the image:// path. + additional_info = "videochapter"; + } + // Handle special case about audiodecoder addon music files, e.g. SACD if (StringUtils::EndsWith(URIUtils::GetExtension(image), KODI_ADDON_AUDIODECODER_TRACK_EXT)) { diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index 73e5729e72e75..cdd573697029b 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -270,7 +270,8 @@ bool CDVDFileInfo::ExtractThumb(const CFileItem& fileItem, CTextureDetails& deta return bOk; } -std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& fileItem) +std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& fileItem, + int chapterNumber) { if (!CanExtract(fileItem)) return {}; @@ -349,7 +350,10 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f if (pVideoCodec) { int nTotalLen = pDemuxer->GetStreamLength(); - int64_t nSeekTo = nTotalLen / 3; + + bool seekToChapter = chapterNumber > 0 && pDemuxer->GetChapterCount() > 0; + int64_t nSeekTo = + seekToChapter ? pDemuxer->GetChapterPos(chapterNumber) * 1000 : nTotalLen / 3; CLog::LogF(LOGDEBUG, "seeking to pos {}ms (total: {}ms) in {}", nSeekTo, nTotalLen, redactPath); diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.h b/xbmc/cores/VideoPlayer/DVDFileInfo.h index bbb9776326f50..0c306e50e85d6 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.h +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.h @@ -26,7 +26,8 @@ class CDVDFileInfo // Extract a thumbnail image from the media referenced by fileItem static bool ExtractThumb(const CFileItem& fileItem, CTextureDetails& details, int64_t pos); - static std::unique_ptr ExtractThumbToTexture(const CFileItem& fileItem); + static std::unique_ptr ExtractThumbToTexture(const CFileItem& fileItem, + int chapterNumber = 0); /*! * @brief Can a thumbnail image and file stream details be extracted from this file item? diff --git a/xbmc/imagefiles/SpecialImageLoaderFactory.cpp b/xbmc/imagefiles/SpecialImageLoaderFactory.cpp index 0fae9618847ad..c143ad1ef414a 100644 --- a/xbmc/imagefiles/SpecialImageLoaderFactory.cpp +++ b/xbmc/imagefiles/SpecialImageLoaderFactory.cpp @@ -11,6 +11,7 @@ #include "guilib/Texture.h" #include "music/MusicEmbeddedImageFileLoader.h" #include "pictures/PictureFolderImageFileLoader.h" +#include "video/VideoChapterImageFileLoader.h" #include "video/VideoEmbeddedImageFileLoader.h" #include "video/VideoGeneratedImageFileLoader.h" @@ -22,6 +23,7 @@ CSpecialImageLoaderFactory::CSpecialImageLoaderFactory() m_specialImageLoaders[1] = std::make_unique(); m_specialImageLoaders[2] = std::make_unique(); m_specialImageLoaders[3] = std::make_unique(); + m_specialImageLoaders[4] = std::make_unique(); } std::unique_ptr CSpecialImageLoaderFactory::Load(const std::string& specialType, diff --git a/xbmc/imagefiles/SpecialImageLoaderFactory.h b/xbmc/imagefiles/SpecialImageLoaderFactory.h index d633c617f165f..e5bb7b6259c2d 100644 --- a/xbmc/imagefiles/SpecialImageLoaderFactory.h +++ b/xbmc/imagefiles/SpecialImageLoaderFactory.h @@ -29,6 +29,6 @@ class CSpecialImageLoaderFactory unsigned int preferredHeight) const; private: - std::array, 4> m_specialImageLoaders{}; + std::array, 5> m_specialImageLoaders{}; }; } // namespace IMAGE_FILES diff --git a/xbmc/video/CMakeLists.txt b/xbmc/video/CMakeLists.txt index b5068599b738c..06e559c938294 100644 --- a/xbmc/video/CMakeLists.txt +++ b/xbmc/video/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES Bookmark.cpp GUIViewStateVideo.cpp PlayerController.cpp Teletext.cpp - VideoItemArtworkHandler.cpp + VideoChapterImageFileLoader.cpp VideoDatabase.cpp VideoDbUrl.cpp VideoEmbeddedImageFileLoader.cpp @@ -11,6 +11,7 @@ set(SOURCES Bookmark.cpp VideoInfoDownloader.cpp VideoInfoScanner.cpp VideoInfoTag.cpp + VideoItemArtworkHandler.cpp VideoLibraryQueue.cpp VideoThumbLoader.cpp VideoUtils.cpp @@ -23,7 +24,7 @@ set(HEADERS Bookmark.h PlayerController.h Teletext.h TeletextDefines.h - VideoItemArtworkHandler.h + VideoChapterImageFileLoader.h VideoDatabase.h VideoDbUrl.h VideoEmbeddedImageFileLoader.h @@ -31,6 +32,7 @@ set(HEADERS Bookmark.h VideoInfoDownloader.h VideoInfoScanner.h VideoInfoTag.h + VideoItemArtworkHandler.h VideoLibraryQueue.h VideoThumbLoader.h VideoUtils.h diff --git a/xbmc/video/VideoChapterImageFileLoader.cpp b/xbmc/video/VideoChapterImageFileLoader.cpp new file mode 100644 index 0000000000000..5cb0704ff47e4 --- /dev/null +++ b/xbmc/video/VideoChapterImageFileLoader.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "VideoChapterImageFileLoader.h" + +#include "DVDFileInfo.h" +#include "FileItem.h" +#include "ServiceBroker.h" +#include "guilib/Texture.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/log.h" + +bool VIDEO::CVideoChapterImageFileLoader::CanLoad(const std::string& specialType) const +{ + return specialType == "videochapter"; +} + +std::unique_ptr VIDEO::CVideoChapterImageFileLoader::Load( + const std::string& specialType, + const std::string& goofyChapterPath, + unsigned int, + unsigned int) const +{ + // "goofy" chapter path because these paths don't yet conform to 'image://' path standard + + // 10 = length of "chapter://" string prefix from GUIDialogVideoBookmarks + size_t lastSlashPos = goofyChapterPath.rfind("/"); + std::string cleanname = goofyChapterPath.substr(10, lastSlashPos - 10); + + int chapterNum = 0; + try + { + chapterNum = std::stoi(goofyChapterPath.substr(lastSlashPos + 1)); + } + catch (...) + { + // invalid_argument because these paths can come from anywhere + // out_of_range mostly for the same reason - 32k+ seems high for a chapter count + return {}; + } + if (chapterNum < 1) + { + return {}; + } + + CFileItem item{cleanname, false}; + return CDVDFileInfo::ExtractThumbToTexture(item, chapterNum); +} diff --git a/xbmc/video/VideoChapterImageFileLoader.h b/xbmc/video/VideoChapterImageFileLoader.h new file mode 100644 index 0000000000000..9cd2eab4966ec --- /dev/null +++ b/xbmc/video/VideoChapterImageFileLoader.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "imagefiles/SpecialImageFileLoader.h" + +namespace VIDEO +{ +/*! + * @brief Generates a texture for a thumbnail of a video chapter. +*/ +class CVideoChapterImageFileLoader : public IMAGE_FILES::ISpecialImageFileLoader +{ +public: + CVideoChapterImageFileLoader() = default; + ~CVideoChapterImageFileLoader() override = default; + + bool CanLoad(const std::string& specialType) const override; + std::unique_ptr Load(const std::string& specialType, + const std::string& goofyChapterPath, + unsigned int preferredWidth, + unsigned int preferredHeight) const override; +}; + +} // namespace VIDEO From e6ba91067dde60d5564fbd5cecd29841e958e2c7 Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 19 Aug 2023 16:00:43 -0600 Subject: [PATCH 227/811] just set chapter thumb path in chapter dialog --- xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp index c4428817aea54..86f545fa12c51 100644 --- a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp @@ -285,18 +285,11 @@ void CGUIDialogVideoBookmarks::OnRefreshList() CFileItemPtr item(new CFileItem(chapterName)); item->SetLabel2(time); - std::string chapterPath = StringUtils::Format("chapter://{}/{}", m_filePath, i); - std::string cachefile = CServiceBroker::GetTextureCache()->GetCachedPath( - CServiceBroker::GetTextureCache()->GetCacheFile(chapterPath) + ".jpg"); - if (CFileUtils::Exists(cachefile)) - item->SetArt("thumb", cachefile); - else if (i > m_jobsStarted && CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS)) + if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( + CSettings::SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS)) { - CFileItem item(m_filePath, false); - CJob* job = new CChapterThumbExtractor(item, m_filePath, chapterPath, pos * 1000); - AddJob(job); - m_mapJobsChapter[job] = i; - m_jobsStarted++; + std::string chapterPath = StringUtils::Format("chapter://{}/{}", m_filePath, i); + item->SetArt("thumb", chapterPath); } item->SetProperty("chapter", i); From 0586131fb17c44346504b45f7e3176d7cf83510a Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 19 Aug 2023 16:35:32 -0600 Subject: [PATCH 228/811] remove old chapter thumb generator --- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 183 ------------------ xbmc/cores/VideoPlayer/DVDFileInfo.h | 3 - xbmc/video/VideoThumbLoader.cpp | 50 ----- xbmc/video/VideoThumbLoader.h | 36 ---- .../video/dialogs/GUIDialogVideoBookmarks.cpp | 54 +----- xbmc/video/dialogs/GUIDialogVideoBookmarks.h | 11 +- 6 files changed, 2 insertions(+), 335 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index cdd573697029b..450d2cd4825c4 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -87,189 +87,6 @@ int DegreeToOrientation(int degrees) } } -bool CDVDFileInfo::ExtractThumb(const CFileItem& fileItem, CTextureDetails& details, int64_t pos) -{ - const std::string redactPath = CURL::GetRedacted(fileItem.GetPath()); - auto start = std::chrono::steady_clock::now(); - - CFileItem item(fileItem); - item.SetMimeTypeForInternetFile(); - auto pInputStream = CDVDFactoryInputStream::CreateInputStream(NULL, item); - if (!pInputStream) - { - CLog::Log(LOGERROR, "InputStream: Error creating stream for {}", redactPath); - return false; - } - - if (!pInputStream->Open()) - { - CLog::Log(LOGERROR, "InputStream: Error opening, {}", redactPath); - return false; - } - - CDVDDemux *pDemuxer = NULL; - - try - { - pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream, true); - if(!pDemuxer) - { - CLog::Log(LOGERROR, "{} - Error creating demuxer", __FUNCTION__); - return false; - } - } - catch(...) - { - CLog::Log(LOGERROR, "{} - Exception thrown when opening demuxer", __FUNCTION__); - if (pDemuxer) - delete pDemuxer; - - return false; - } - - int nVideoStream = -1; - int64_t demuxerId = -1; - for (CDemuxStream* pStream : pDemuxer->GetStreams()) - { - if (pStream) - { - // ignore if it's a picture attachment (e.g. jpeg artwork) - if (pStream->type == STREAM_VIDEO && !(pStream->flags & AV_DISPOSITION_ATTACHED_PIC)) - { - nVideoStream = pStream->uniqueId; - demuxerId = pStream->demuxerId; - } - else - pDemuxer->EnableStream(pStream->demuxerId, pStream->uniqueId, false); - } - } - - bool bOk = false; - int packetsTried = 0; - - if (nVideoStream != -1) - { - std::unique_ptr pProcessInfo(CProcessInfo::CreateInstance()); - std::vector pixFmts; - pixFmts.push_back(AV_PIX_FMT_YUV420P); - pProcessInfo->SetPixFormats(pixFmts); - - CDVDStreamInfo hint(*pDemuxer->GetStream(demuxerId, nVideoStream), true); - hint.codecOptions = CODEC_FORCE_SOFTWARE; - - std::unique_ptr pVideoCodec = - CDVDFactoryCodec::CreateVideoCodec(hint, *pProcessInfo); - - if (pVideoCodec) - { - int nTotalLen = pDemuxer->GetStreamLength(); - int64_t nSeekTo = (pos == -1) ? nTotalLen / 3 : pos; - - CLog::Log(LOGDEBUG, "{} - seeking to pos {}ms (total: {}ms) in {}", __FUNCTION__, nSeekTo, - nTotalLen, redactPath); - - if (pDemuxer->SeekTime(static_cast(nSeekTo), true)) - { - CDVDVideoCodec::VCReturn iDecoderState = CDVDVideoCodec::VC_NONE; - VideoPicture picture = {}; - - // num streams * 160 frames, should get a valid frame, if not abort. - int abort_index = pDemuxer->GetNrOfStreams() * 160; - do - { - DemuxPacket* pPacket = pDemuxer->Read(); - packetsTried++; - - if (!pPacket) - break; - - if (pPacket->iStreamId != nVideoStream) - { - CDVDDemuxUtils::FreeDemuxPacket(pPacket); - continue; - } - - pVideoCodec->AddData(*pPacket); - CDVDDemuxUtils::FreeDemuxPacket(pPacket); - - iDecoderState = CDVDVideoCodec::VC_NONE; - while (iDecoderState == CDVDVideoCodec::VC_NONE) - { - iDecoderState = pVideoCodec->GetPicture(&picture); - } - - if (iDecoderState == CDVDVideoCodec::VC_PICTURE) - { - if(!(picture.iFlags & DVP_FLAG_DROPPED)) - break; - } - - } while (abort_index--); - - if (iDecoderState == CDVDVideoCodec::VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED)) - { - { - unsigned int nWidth = std::min(picture.iDisplayWidth, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes); - double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight; - if(hint.forced_aspect && hint.aspect != 0) - aspect = hint.aspect; - unsigned int nHeight = (unsigned int)((double)nWidth / aspect); - - // We pass the buffers to sws_scale uses 16 aligned widths when using intrinsics - int sizeNeeded = FFALIGN(nWidth, 16) * nHeight * 4; - uint8_t *pOutBuf = static_cast(av_malloc(sizeNeeded)); - struct SwsContext *context = sws_getContext(picture.iWidth, picture.iHeight, - AV_PIX_FMT_YUV420P, nWidth, nHeight, AV_PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL); - - if (context) - { - uint8_t *planes[YuvImage::MAX_PLANES]; - int stride[YuvImage::MAX_PLANES]; - picture.videoBuffer->GetPlanes(planes); - picture.videoBuffer->GetStrides(stride); - uint8_t *src[4]= { planes[0], planes[1], planes[2], 0 }; - int srcStride[] = { stride[0], stride[1], stride[2], 0 }; - uint8_t *dst[] = { pOutBuf, 0, 0, 0 }; - int dstStride[] = { (int)nWidth*4, 0, 0, 0 }; - int orientation = DegreeToOrientation(hint.orientation); - sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride); - sws_freeContext(context); - - details.width = nWidth; - details.height = nHeight; - CPicture::CacheTexture(pOutBuf, nWidth, nHeight, nWidth * 4, orientation, nWidth, nHeight, CTextureCache::GetCachedPath(details.file)); - bOk = true; - } - av_free(pOutBuf); - } - } - else - { - CLog::Log(LOGDEBUG, "{} - decode failed in {} after {} packets.", __FUNCTION__, - redactPath, packetsTried); - } - } - } - } - - if (pDemuxer) - delete pDemuxer; - - if(!bOk) - { - XFILE::CFile file; - if(file.OpenForWrite(CTextureCache::GetCachedPath(details.file))) - file.Close(); - } - - auto end = std::chrono::steady_clock::now(); - auto duration = std::chrono::duration_cast(end - start); - CLog::Log(LOGDEBUG, "{} - measured {} ms to extract thumb from file <{}> in {} packets. ", - __FUNCTION__, duration.count(), redactPath, packetsTried); - - return bOk; -} - std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& fileItem, int chapterNumber) { diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.h b/xbmc/cores/VideoPlayer/DVDFileInfo.h index 0c306e50e85d6..0129d6ac4c7d3 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.h +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.h @@ -23,9 +23,6 @@ class CTextureDetails; class CDVDFileInfo { public: - // Extract a thumbnail image from the media referenced by fileItem - static bool ExtractThumb(const CFileItem& fileItem, CTextureDetails& details, int64_t pos); - static std::unique_ptr ExtractThumbToTexture(const CFileItem& fileItem, int chapterNumber = 0); diff --git a/xbmc/video/VideoThumbLoader.cpp b/xbmc/video/VideoThumbLoader.cpp index 7bce9f300eefe..91d1e7a43644b 100644 --- a/xbmc/video/VideoThumbLoader.cpp +++ b/xbmc/video/VideoThumbLoader.cpp @@ -37,56 +37,6 @@ using namespace XFILE; using namespace VIDEO; -CChapterThumbExtractor::CChapterThumbExtractor(const CFileItem& item, - const std::string& listpath, - const std::string& target, - int64_t pos) - : m_target(target), m_listpath(listpath), m_item(item) -{ - m_pos = pos; - - if (item.IsVideoDb() && item.HasVideoInfoTag()) - m_item.SetPath(item.GetVideoInfoTag()->m_strFileNameAndPath); - - if (m_item.IsStack()) - m_item.SetPath(CStackDirectory::GetFirstStackedFile(m_item.GetPath())); -} - -CChapterThumbExtractor::~CChapterThumbExtractor() = default; - -bool CChapterThumbExtractor::operator==(const CJob* job) const -{ - if (strcmp(job->GetType(),GetType()) == 0) - { - const CChapterThumbExtractor* jobExtract = dynamic_cast(job); - if (jobExtract && jobExtract->m_listpath == m_listpath - && jobExtract->m_target == m_target) - return true; - } - return false; -} - -bool CChapterThumbExtractor::DoWork() -{ - if (!CDVDFileInfo::CanExtract(m_item)) - return false; - - bool result=false; - CLog::LogF(LOGDEBUG, "trying to extract thumb from video file {}", - CURL::GetRedacted(m_item.GetPath())); - // construct the thumb cache file - CTextureDetails details; - details.file = CTextureCache::GetCacheFile(m_target) + ".jpg"; - result = CDVDFileInfo::ExtractThumb(m_item, details, m_pos); - if (!result) - return false; - - CServiceBroker::GetTextureCache()->AddCachedTexture(m_target, details); - m_item.SetArt("thumb", m_target); - - return true; -} - CVideoThumbLoader::CVideoThumbLoader() : CThumbLoader() { m_videoDatabase = new CVideoDatabase(); diff --git a/xbmc/video/VideoThumbLoader.h b/xbmc/video/VideoThumbLoader.h index 9cf0a9482f970..f5505fe8609ef 100644 --- a/xbmc/video/VideoThumbLoader.h +++ b/xbmc/video/VideoThumbLoader.h @@ -10,7 +10,6 @@ #include "FileItem.h" #include "ThumbLoader.h" -#include "utils/JobManager.h" #include #include @@ -22,41 +21,6 @@ class EmbeddedArt; using ArtMap = std::map; using ArtCache = std::map, ArtMap>; -/*! - \ingroup thumbs,jobs - \brief Thumb extractor job class - - Used by the "chapter browser" GUI window to generate chapter thumbs. - - \sa CVideoThumbLoader and CJob - */ -class CChapterThumbExtractor : public CJob -{ -public: - CChapterThumbExtractor(const CFileItem& item, - const std::string& listpath, - const std::string& strTarget = "", - int64_t pos = -1); - ~CChapterThumbExtractor() override; - - /*! - \brief Work function that extracts thumb. - */ - bool DoWork() override; - - const char* GetType() const override - { - return kJobTypeMediaFlags; - } - - bool operator==(const CJob* job) const override; - - std::string m_target; ///< thumbpath - std::string m_listpath; ///< path used in fileitem list - CFileItem m_item; - int64_t m_pos; ///< position to extract thumb from -}; - class CVideoThumbLoader : public CThumbLoader { public: diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp index 86f545fa12c51..425344219ea5b 100644 --- a/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.cpp @@ -35,7 +35,6 @@ #include "utils/Variant.h" #include "utils/log.h" #include "video/VideoDatabase.h" -#include "video/VideoThumbLoader.h" #include "view/ViewState.h" #include @@ -51,12 +50,10 @@ #define CONTROL_THUMBS 11 CGUIDialogVideoBookmarks::CGUIDialogVideoBookmarks() - : CGUIDialog(WINDOW_DIALOG_VIDEO_BOOKMARKS, "VideoOSDBookmarks.xml"), - CJobQueue(false, 1, CJob::PRIORITY_NORMAL) + : CGUIDialog(WINDOW_DIALOG_VIDEO_BOOKMARKS, "VideoOSDBookmarks.xml") { m_vecItems = new CFileItemList; m_loadType = LOAD_EVERY_TIME; - m_jobsStarted = 0; } CGUIDialogVideoBookmarks::~CGUIDialogVideoBookmarks() @@ -137,9 +134,6 @@ bool CGUIDialogVideoBookmarks::OnMessage(CGUIMessage& message) case 0: OnRefreshList(); break; - case 1: - UpdateItem(message.GetParam2()); - break; default: break; } @@ -202,30 +196,6 @@ void CGUIDialogVideoBookmarks::Delete(int item) Update(); } -void CGUIDialogVideoBookmarks::UpdateItem(unsigned int chapterIdx) -{ - std::unique_lock lock(m_refreshSection); - - int itemPos = 0; - for (const auto& item : *m_vecItems) - { - if (chapterIdx == item->GetProperty("chapter").asInteger()) - break; - itemPos++; - } - - if (itemPos < m_vecItems->Size()) - { - std::string time = StringUtils::Format("chapter://{}/{}", m_filePath, chapterIdx); - std::string cachefile = CServiceBroker::GetTextureCache()->GetCachedPath( - CServiceBroker::GetTextureCache()->GetCacheFile(time) + ".jpg"); - if (CFileUtils::Exists(cachefile)) - { - (*m_vecItems)[itemPos]->SetArt("thumb", cachefile); - } - } -} - void CGUIDialogVideoBookmarks::OnRefreshList() { m_bookmarks.clear(); @@ -471,16 +441,11 @@ void CGUIDialogVideoBookmarks::OnWindowLoaded() m_viewControl.Reset(); m_viewControl.SetParentWindow(GetID()); m_viewControl.AddView(GetControl(CONTROL_THUMBS)); - m_jobsStarted = 0; - m_mapJobsChapter.clear(); m_vecItems->Clear(); } void CGUIDialogVideoBookmarks::OnWindowUnload() { - //stop running thumb extraction jobs - CancelJobs(); - m_mapJobsChapter.clear(); m_vecItems->Clear(); CGUIDialog::OnWindowUnload(); m_viewControl.Reset(); @@ -564,20 +529,3 @@ bool CGUIDialogVideoBookmarks::OnAddEpisodeBookmark() } return bReturn; } - -void CGUIDialogVideoBookmarks::OnJobComplete(unsigned int jobID, - bool success, CJob* job) -{ - if (success && IsActive()) - { - MAPJOBSCHAPS::iterator iter = m_mapJobsChapter.find(job); - if (iter != m_mapJobsChapter.end()) - { - unsigned int chapterIdx = (*iter).second; - CGUIMessage m(GUI_MSG_REFRESH_LIST, GetID(), 0, 1, chapterIdx); - CServiceBroker::GetAppMessenger()->SendGUIMessage(m); - m_mapJobsChapter.erase(iter); - } - } - CJobQueue::OnJobComplete(jobID, success, job); -} diff --git a/xbmc/video/dialogs/GUIDialogVideoBookmarks.h b/xbmc/video/dialogs/GUIDialogVideoBookmarks.h index 801afcdd37367..09fa5e3e985c6 100644 --- a/xbmc/video/dialogs/GUIDialogVideoBookmarks.h +++ b/xbmc/video/dialogs/GUIDialogVideoBookmarks.h @@ -9,16 +9,13 @@ #pragma once #include "guilib/GUIDialog.h" -#include "utils/JobManager.h" #include "video/VideoDatabase.h" #include "view/GUIViewControl.h" class CFileItemList; -class CGUIDialogVideoBookmarks : public CGUIDialog, public CJobQueue +class CGUIDialogVideoBookmarks : public CGUIDialog { - typedef std::map MAPJOBSCHAPS; - public: CGUIDialogVideoBookmarks(void); ~CGUIDialogVideoBookmarks(void) override; @@ -60,17 +57,11 @@ class CGUIDialogVideoBookmarks : public CGUIDialog, public CJobQueue void OnPopupMenu(int item); CGUIControl *GetFirstFocusableControl(int id) override; - void OnJobComplete(unsigned int jobID, bool success, CJob* job) override; - CFileItemList* m_vecItems; CGUIViewControl m_viewControl; VECBOOKMARKS m_bookmarks; private: - void UpdateItem(unsigned int chapterIdx); - - int m_jobsStarted; std::string m_filePath; CCriticalSection m_refreshSection; - MAPJOBSCHAPS m_mapJobsChapter; }; From c7115e0ae6b857aa2a61b3ad80eeae52d89935e5 Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 19 Aug 2023 16:43:28 -0600 Subject: [PATCH 229/811] fix save new no-hash images to DB --- xbmc/TextureCache.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/TextureCache.cpp b/xbmc/TextureCache.cpp index e0a5e2d906627..ab5fff51e7215 100644 --- a/xbmc/TextureCache.cpp +++ b/xbmc/TextureCache.cpp @@ -296,7 +296,7 @@ void CTextureCache::OnCachingComplete(bool success, CTextureCacheJob *job) { if (success) { - if (job->m_oldHash == job->m_details.hash) + if (job->m_details.id != -1 && job->m_oldHash == job->m_details.hash) SetCachedTextureValid(job->m_url, job->m_details.updateable); else AddCachedTexture(job->m_url, job->m_details); From 0bfa18e486e8db6a28435393fdf2ec84d2050e1c Mon Sep 17 00:00:00 2001 From: Ryan Rector Date: Sat, 19 Aug 2023 17:29:02 -0600 Subject: [PATCH 230/811] style fixup CDVDFileInfo::ExtractThumbToTexture --- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 94 +++++++++++--------------- 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index 450d2cd4825c4..eb62cf24ea5dd 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -111,29 +111,16 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f return {}; } - CDVDDemux* pDemuxer = NULL; - - try - { - pDemuxer = CDVDFactoryDemuxer::CreateDemuxer(pInputStream, true); - if (!pDemuxer) - { - CLog::LogF(LOGERROR, "Error creating demuxer"); - return {}; - } - } - catch (...) + std::unique_ptr demuxer{CDVDFactoryDemuxer::CreateDemuxer(pInputStream, true)}; + if (!demuxer) { - CLog::LogF(LOGERROR, "Exception thrown when opening demuxer"); - if (pDemuxer) - delete pDemuxer; - + CLog::LogF(LOGERROR, "Error creating demuxer"); return {}; } int nVideoStream = -1; int64_t demuxerId = -1; - for (CDemuxStream* pStream : pDemuxer->GetStreams()) + for (CDemuxStream* pStream : demuxer->GetStreams()) { if (pStream) { @@ -144,7 +131,7 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f demuxerId = pStream->demuxerId; } else - pDemuxer->EnableStream(pStream->demuxerId, pStream->uniqueId, false); + demuxer->EnableStream(pStream->demuxerId, pStream->uniqueId, false); } } @@ -158,7 +145,7 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f pixFmts.push_back(AV_PIX_FMT_YUV420P); pProcessInfo->SetPixFormats(pixFmts); - CDVDStreamInfo hint(*pDemuxer->GetStream(demuxerId, nVideoStream), true); + CDVDStreamInfo hint(*demuxer->GetStream(demuxerId, nVideoStream), true); hint.codecOptions = CODEC_FORCE_SOFTWARE; std::unique_ptr pVideoCodec = @@ -166,25 +153,25 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f if (pVideoCodec) { - int nTotalLen = pDemuxer->GetStreamLength(); + int nTotalLen = demuxer->GetStreamLength(); - bool seekToChapter = chapterNumber > 0 && pDemuxer->GetChapterCount() > 0; + bool seekToChapter = chapterNumber > 0 && demuxer->GetChapterCount() > 0; int64_t nSeekTo = - seekToChapter ? pDemuxer->GetChapterPos(chapterNumber) * 1000 : nTotalLen / 3; + seekToChapter ? demuxer->GetChapterPos(chapterNumber) * 1000 : nTotalLen / 3; CLog::LogF(LOGDEBUG, "seeking to pos {}ms (total: {}ms) in {}", nSeekTo, nTotalLen, redactPath); - if (pDemuxer->SeekTime(static_cast(nSeekTo), true)) + if (demuxer->SeekTime(static_cast(nSeekTo), true)) { CDVDVideoCodec::VCReturn iDecoderState = CDVDVideoCodec::VC_NONE; VideoPicture picture = {}; // num streams * 160 frames, should get a valid frame, if not abort. - int abort_index = pDemuxer->GetNrOfStreams() * 160; + int abort_index = demuxer->GetNrOfStreams() * 160; do { - DemuxPacket* pPacket = pDemuxer->Read(); + DemuxPacket* pPacket = demuxer->Read(); packetsTried++; if (!pPacket) @@ -215,35 +202,33 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f if (iDecoderState == CDVDVideoCodec::VC_PICTURE && !(picture.iFlags & DVP_FLAG_DROPPED)) { + unsigned int nWidth = + std::min(picture.iDisplayWidth, + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes); + double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight; + if (hint.forced_aspect && hint.aspect != 0) + aspect = hint.aspect; + unsigned int nHeight = (unsigned int)((double)nWidth / aspect); + + result = CTexture::CreateTexture(nWidth, nHeight); + result->SetAlpha(false); + struct SwsContext* context = + sws_getContext(picture.iWidth, picture.iHeight, AV_PIX_FMT_YUV420P, nWidth, nHeight, + AV_PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL); + + if (context) { - unsigned int nWidth = - std::min(picture.iDisplayWidth, - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_imageRes); - double aspect = (double)picture.iDisplayWidth / (double)picture.iDisplayHeight; - if (hint.forced_aspect && hint.aspect != 0) - aspect = hint.aspect; - unsigned int nHeight = (unsigned int)((double)nWidth / aspect); - - result = CTexture::CreateTexture(nWidth, nHeight); - result->SetAlpha(false); - struct SwsContext* context = - sws_getContext(picture.iWidth, picture.iHeight, AV_PIX_FMT_YUV420P, nWidth, nHeight, - AV_PIX_FMT_BGRA, SWS_FAST_BILINEAR, NULL, NULL, NULL); - - if (context) - { - uint8_t* planes[YuvImage::MAX_PLANES]; - int stride[YuvImage::MAX_PLANES]; - picture.videoBuffer->GetPlanes(planes); - picture.videoBuffer->GetStrides(stride); - uint8_t* src[4] = {planes[0], planes[1], planes[2], 0}; - int srcStride[] = {stride[0], stride[1], stride[2], 0}; - uint8_t* dst[] = {result->GetPixels(), 0, 0, 0}; - int dstStride[] = {static_cast(result->GetPitch()), 0, 0, 0}; - result->SetOrientation(DegreeToOrientation(hint.orientation)); - sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride); - sws_freeContext(context); - } + uint8_t* planes[YuvImage::MAX_PLANES]; + int stride[YuvImage::MAX_PLANES]; + picture.videoBuffer->GetPlanes(planes); + picture.videoBuffer->GetStrides(stride); + uint8_t* src[4] = {planes[0], planes[1], planes[2], 0}; + int srcStride[] = {stride[0], stride[1], stride[2], 0}; + uint8_t* dst[] = {result->GetPixels(), 0, 0, 0}; + int dstStride[] = {static_cast(result->GetPitch()), 0, 0, 0}; + result->SetOrientation(DegreeToOrientation(hint.orientation)); + sws_scale(context, src, srcStride, 0, picture.iHeight, dst, dstStride); + sws_freeContext(context); } } else @@ -254,9 +239,6 @@ std::unique_ptr CDVDFileInfo::ExtractThumbToTexture(const CFileItem& f } } - if (pDemuxer) - delete pDemuxer; - auto end = std::chrono::steady_clock::now(); auto duration = std::chrono::duration_cast(end - start); CLog::LogF(LOGDEBUG, "measured {} ms to extract thumb from file <{}> in {} packets. ", From 87ebcbebabd1441e86a5cbe5b80686f822d79da7 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Thu, 14 Sep 2023 17:23:51 +0200 Subject: [PATCH 231/811] [Windows] Fix 24Hz refresh rate when HDR is ON on AMD systems --- xbmc/rendering/dx/DeviceResources.cpp | 20 -------------------- xbmc/rendering/dx/DeviceResources.h | 1 - 2 files changed, 21 deletions(-) diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp index 8cfe5cf62c10d..103809d591b0d 100644 --- a/xbmc/rendering/dx/DeviceResources.cpp +++ b/xbmc/rendering/dx/DeviceResources.cpp @@ -729,7 +729,6 @@ void DX::DeviceResources::ResizeBuffers() ComPtr dxgiDevice; hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR(); dxgiDevice->SetMaximumFrameLatency(1); - m_usedSwapChain = false; if (m_IsHDROutput) SetHdrColorSpace(DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); @@ -960,7 +959,6 @@ void DX::DeviceResources::HandleDeviceLost(bool removed) bool DX::DeviceResources::Begin() { HRESULT hr = m_swapChain->Present(0, DXGI_PRESENT_TEST); - m_usedSwapChain = true; // If the device was removed either by a disconnection or a driver upgrade, we // must recreate all device resources. @@ -992,7 +990,6 @@ void DX::DeviceResources::Present() // frames that will never be displayed to the screen. DXGI_PRESENT_PARAMETERS parameters = {}; HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); - m_usedSwapChain = true; // If the device was removed either by a disconnection or a driver upgrade, we // must recreate all device resources. @@ -1311,23 +1308,6 @@ void DX::DeviceResources::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpac if (SUCCEEDED(m_swapChain.As(&swapChain3))) { - // Set the color space on a new swap chain - not mandated by MS documentation but needed - // at least for some AMD on Windows 10, at least up to driver 31.0.21001.45002 - // Applying to AMD only because it breaks refresh rate switching in Windows 11 for Intel and - // nVidia and they don't need the workaround. - if (m_usedSwapChain && - m_IsTransferPQ != (colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020)) - { - if (GetAdapterDesc().VendorId == PCIV_AMD) - { - // Temporary release, can't hold references during swap chain re-creation - swapChain3 = nullptr; - DestroySwapChain(); - CreateWindowSizeDependentResources(); - m_swapChain.As(&swapChain3); - } - } - if (SUCCEEDED(swapChain3->SetColorSpace1(colorSpace))) { m_IsTransferPQ = (colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); diff --git a/xbmc/rendering/dx/DeviceResources.h b/xbmc/rendering/dx/DeviceResources.h index 01fa76a659851..8d325ee6dc200 100644 --- a/xbmc/rendering/dx/DeviceResources.h +++ b/xbmc/rendering/dx/DeviceResources.h @@ -188,7 +188,6 @@ namespace DX bool m_NV12SharedTexturesSupport{false}; bool m_DXVA2SharedDecoderSurfaces{false}; bool m_DXVASuperResolutionSupport{false}; - bool m_usedSwapChain{false}; bool m_DXVA2UseFence{false}; }; } From 127ea962e06a56aec187649ba573b8dd5a0787ec Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 15 Sep 2023 00:30:45 +0200 Subject: [PATCH 232/811] [Video] Fix crash when scan to library on a directory containing new movies --- xbmc/video/tags/VideoTagExtractionHelper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xbmc/video/tags/VideoTagExtractionHelper.cpp b/xbmc/video/tags/VideoTagExtractionHelper.cpp index 5ce567aed92fd..fbab01c805ce5 100644 --- a/xbmc/video/tags/VideoTagExtractionHelper.cpp +++ b/xbmc/video/tags/VideoTagExtractionHelper.cpp @@ -21,10 +21,9 @@ using namespace VIDEO::TAGS; bool CVideoTagExtractionHelper::IsExtractionSupportedFor(const CFileItem& item) { - const std::string fileNameAndPath = item.GetVideoInfoTag()->m_strFileNameAndPath; return CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool( CSettings::SETTING_MYVIDEOS_USETAGS) && - URIUtils::HasExtension(fileNameAndPath, ".mkv|.mp4|.avi|.m4v"); + URIUtils::HasExtension(item.GetDynPath(), ".mkv|.mp4|.avi|.m4v"); } std::string CVideoTagExtractionHelper::ExtractEmbeddedArtFor(const CFileItem& item, From 0f9db4fdd7dbf55d6db3fe693d0d61cc977a4441 Mon Sep 17 00:00:00 2001 From: quietvoid <39477805+quietvoid@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:45:30 -0400 Subject: [PATCH 233/811] [webOS] Signal Dolby Vision with AV1 codec --- .../DVDCodecs/Video/DVDVideoCodecStarfish.cpp | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp index 2069228e7e4db..c393fd6b6d1ab 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp @@ -172,8 +172,25 @@ bool CDVDVideoCodecStarfish::OpenInternal(CDVDStreamInfo& hints, CDVDCodecOption m_bitstream.reset(); } } + + break; + } + case AV_CODEC_ID_AV1: + { + if (m_hints.hdrType == StreamHdrType::HDR_TYPE_DOLBYVISION && m_hints.dovi.dv_profile == 10) + { + m_formatname = "starfish-dav1"; + + payloadArg["option"]["externalStreamingInfo"]["contents"]["DolbyHdrInfo"] + ["encryptionType"] = "clear"; //"clear", "bl", "el", "all" + payloadArg["option"]["externalStreamingInfo"]["contents"]["DolbyHdrInfo"]["profileId"] = + m_hints.dovi.dv_profile; // profile 10 + payloadArg["option"]["externalStreamingInfo"]["contents"]["DolbyHdrInfo"]["trackType"] = + "single"; // "single" / "dual" + } + + break; } - break; default: break; } From 29755df826b063e2d51343dd9d0a1a62afa14394 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 16 Sep 2023 13:12:00 +1000 Subject: [PATCH 234/811] [cmake] implement ${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG This enables a platform to set specific search config criteria for use with find_* cmake calls. For platforms like apple/android, we dont ever want system libs, we only want to search our provided depends path. --- cmake/platform/android/android.cmake | 2 ++ cmake/platform/darwin_embedded/ios.cmake | 2 ++ cmake/platform/darwin_embedded/tvos.cmake | 1 + cmake/platform/osx/osx.cmake | 2 ++ 4 files changed, 7 insertions(+) diff --git a/cmake/platform/android/android.cmake b/cmake/platform/android/android.cmake index ca3fad9dce819..07fa72c746715 100644 --- a/cmake/platform/android/android.cmake +++ b/cmake/platform/android/android.cmake @@ -6,3 +6,5 @@ list(APPEND PLATFORM_OPTIONAL_DEPS LibDovi) set(TARGET_SDK 33) # Minimum supported SDK version set(TARGET_MINSDK 21) + +set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") diff --git a/cmake/platform/darwin_embedded/ios.cmake b/cmake/platform/darwin_embedded/ios.cmake index 55d7c4a0086c0..aff02ecfc681a 100644 --- a/cmake/platform/darwin_embedded/ios.cmake +++ b/cmake/platform/darwin_embedded/ios.cmake @@ -1 +1,3 @@ list(APPEND ARCH_DEFINES -DTARGET_DARWIN_IOS) +set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") + diff --git a/cmake/platform/darwin_embedded/tvos.cmake b/cmake/platform/darwin_embedded/tvos.cmake index 8d2538cdff053..62e16f5c06b80 100644 --- a/cmake/platform/darwin_embedded/tvos.cmake +++ b/cmake/platform/darwin_embedded/tvos.cmake @@ -1,2 +1,3 @@ list(APPEND ARCH_DEFINES -DTARGET_DARWIN_TVOS) set(ENABLE_AIRTUNES OFF CACHE BOOL "" FORCE) +set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") diff --git a/cmake/platform/osx/osx.cmake b/cmake/platform/osx/osx.cmake index cd3a996ded842..c46bfbb12efbe 100644 --- a/cmake/platform/osx/osx.cmake +++ b/cmake/platform/osx/osx.cmake @@ -18,3 +18,5 @@ elseif(APP_WINDOW_SYSTEM STREQUAL native) else() message(SEND_ERROR "Only SDL or native windowing options are supported.") endif() + +set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") From 2481f3fb58d8ed4342bc7779b5eba7984f04c273 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sat, 16 Sep 2023 13:14:21 +1000 Subject: [PATCH 235/811] [cmake] add support for ${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG in several find modules Update HINTS paths as well to accommodate restricted search paths with the use of ${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG --- cmake/modules/FindCrossGUID.cmake | 6 +++--- cmake/modules/FindDav1d.cmake | 4 ++-- cmake/modules/FindFlatBuffers.cmake | 5 ++++- cmake/modules/FindFmt.cmake | 16 ++++++++++++---- cmake/modules/FindLibZip.cmake | 12 +++++++++--- cmake/modules/FindNFS.cmake | 12 +++++++++--- cmake/modules/FindPCRE.cmake | 28 +++++++++++++++++++++------- cmake/modules/FindRapidJSON.cmake | 8 ++++++-- cmake/modules/FindSpdlog.cmake | 19 +++++++++---------- cmake/modules/FindTagLib.cmake | 13 ++++++++++--- cmake/modules/FindTinyXML2.cmake | 25 +++++++++++++------------ 11 files changed, 98 insertions(+), 50 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 59d0cebc6a6e5..f7ae5aff1e044 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -41,11 +41,11 @@ if(NOT TARGET CrossGUID::CrossGUID) endif() find_path(CROSSGUID_INCLUDE_DIR NAMES crossguid/guid.hpp guid.h - PATHS ${PC_CROSSGUID_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_CROSSGUID_INCLUDEDIR}) find_library(CROSSGUID_LIBRARY_RELEASE NAMES crossguid - HINTS ${PC_CROSSGUID_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR}) find_library(CROSSGUID_LIBRARY_DEBUG NAMES crossguidd crossguid-dgb - HINTS ${PC_CROSSGUID_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR}) # NEW_CROSSGUID >= 0.2.0 release if(EXISTS "${CROSSGUID_INCLUDE_DIR}/crossguid/guid.hpp") diff --git a/cmake/modules/FindDav1d.cmake b/cmake/modules/FindDav1d.cmake index 160a53226bab9..f4312d27af839 100644 --- a/cmake/modules/FindDav1d.cmake +++ b/cmake/modules/FindDav1d.cmake @@ -40,11 +40,11 @@ if(NOT TARGET dav1d::dav1d) endif() find_library(DAV1D_LIBRARY NAMES dav1d libdav1d - PATHS ${PC_DAV1D_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_DAV1D_LIBDIR} NO_CACHE) find_path(DAV1D_INCLUDE_DIR NAMES dav1d/dav1d.h - PATHS ${PC_DAV1D_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include ${PC_DAV1D_INCLUDEDIR} NO_CACHE) set(DAV1D_VERSION ${PC_DAV1D_VERSION}) diff --git a/cmake/modules/FindFlatBuffers.cmake b/cmake/modules/FindFlatBuffers.cmake index 924bcd28c17fa..6ddfa389c1a37 100644 --- a/cmake/modules/FindFlatBuffers.cmake +++ b/cmake/modules/FindFlatBuffers.cmake @@ -34,7 +34,10 @@ if(NOT TARGET flatbuffers::flatbuffers) BUILD_DEP_TARGET() else() - find_path(FLATBUFFERS_INCLUDE_DIR NAMES flatbuffers/flatbuffers.h) + find_path(FLATBUFFERS_INCLUDE_DIR NAMES flatbuffers/flatbuffers.h + HINTS ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) endif() include(FindPackageHandleStandardArgs) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index 223445c595b70..a403a03ce540d 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -24,7 +24,9 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) SETUP_BUILD_VARS() # Check for existing FMT. If version >= FMT-VERSION file version, dont build - find_package(FMT CONFIG QUIET) + find_package(FMT CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Build if ENABLE_INTERNAL_FMT, or if required version in find_package call is greater # than already found FMT_VERSION from a previous find_package call @@ -83,12 +85,18 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) endif() find_path(FMT_INCLUDE_DIR NAMES fmt/format.h - PATHS ${PC_FMT_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_FMT_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(FMT_LIBRARY_RELEASE NAMES fmt - PATHS ${PC_FMT_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_FMT_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(FMT_LIBRARY_DEBUG NAMES fmtd - PATHS ${PC_FMT_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_FMT_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) endif() add_library(fmt::fmt UNKNOWN IMPORTED) diff --git a/cmake/modules/FindLibZip.cmake b/cmake/modules/FindLibZip.cmake index 6423c29be118e..ccbc4e38a6353 100644 --- a/cmake/modules/FindLibZip.cmake +++ b/cmake/modules/FindLibZip.cmake @@ -19,7 +19,9 @@ set(MODULE_LC libzip) SETUP_BUILD_VARS() # Check for existing lib -find_package(LIBZIP CONFIG QUIET) +find_package(LIBZIP CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) if(NOT LIBZIP_FOUND OR LIBZIP_VERSION VERSION_LESS ${${MODULE}_VER}) # Check for dependencies @@ -39,9 +41,13 @@ if(NOT LIBZIP_FOUND OR LIBZIP_VERSION VERSION_LESS ${${MODULE}_VER}) BUILD_DEP_TARGET() else() - find_path(LIBZIP_INCLUDE_DIR NAMES zip.h) + find_path(LIBZIP_INCLUDE_DIR NAMES zip.h + HINTS ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) - find_library(LIBZIP_LIBRARY NAMES zip) + find_library(LIBZIP_LIBRARY NAMES zip + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() include(FindPackageHandleStandardArgs) diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index 8608571d62e51..c81f2e02ae95f 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -16,7 +16,9 @@ if(NOT TARGET libnfs::nfs) SETUP_BUILD_VARS() # Search for cmake config. Suitable for all platforms including windows - find_package(libnfs CONFIG QUIET) + find_package(libnfs CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Check for existing LIBNFS. If version >= LIBNFS-VERSION file version, dont build # A corner case, but if a linux/freebsd user WANTS to build internal libnfs, build anyway @@ -42,12 +44,16 @@ if(NOT TARGET libnfs::nfs) find_library(LIBNFS_LIBRARY_RELEASE NAMES nfs libnfs HINTS ${DEPENDS_PATH}/lib - ${PC_LIBNFS_LIBDIR}) + ${PC_LIBNFS_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) set(LIBNFS_VERSION ${PC_LIBNFS_VERSION}) endif() find_path(LIBNFS_INCLUDE_DIR nfsc/libnfs.h HINTS ${PC_LIBNFS_INCLUDEDIR} - ${DEPENDS_PATH}/include) + ${DEPENDS_PATH}/include + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) endif() if(TARGET libnfs::nfs) diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index f4c54aea8a300..0b7e0c3203754 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -17,7 +17,9 @@ if(NOT PCRE::pcre) SETUP_BUILD_VARS() # Check for existing PCRE. If version >= PCRE-VERSION file version, dont build - find_package(PCRE CONFIG QUIET) + find_package(PCRE CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) if((PCRE_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_PCRE)) @@ -76,15 +78,25 @@ if(NOT PCRE::pcre) endif() find_path(PCRE_INCLUDE_DIR pcrecpp.h - PATHS ${PC_PCRE_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_PCRE_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(PCRECPP_LIBRARY_RELEASE NAMES pcrecpp - PATHS ${PC_PCRE_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(PCRE_LIBRARY_RELEASE NAMES pcre - PATHS ${PC_PCRE_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(PCRECPP_LIBRARY_DEBUG NAMES pcrecppd - PATHS ${PC_PCRE_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(PCRE_LIBRARY_DEBUG NAMES pcred - PATHS ${PC_PCRE_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_PCRE_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) set(PCRE_VERSION ${PC_PCRE_VERSION}) else() @@ -113,7 +125,9 @@ if(NOT PCRE::pcre) # ToDo: patch PCRE cmake to include includedir in config file find_path(PCRE_INCLUDE_DIR pcrecpp.h - PATHS ${PC_PCRE_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_PCRE_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) set_target_properties(PCRE::pcre PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${PCRE_INCLUDE_DIR}") diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 44a25b6193932..19405867bdde0 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -46,12 +46,16 @@ if(NOT TARGET RapidJSON::RapidJSON) if(PC_RapidJSON_VERSION) set(RapidJSON_VERSION ${PC_RapidJSON_VERSION}) else() - find_package(RapidJSON 1.1.0 CONFIG REQUIRED QUIET) + find_package(RapidJSON 1.1.0 CONFIG REQUIRED + QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() endif() find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h - PATHS ${PC_RapidJSON_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include ${PC_RapidJSON_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} NO_CACHE) endif() diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index b1beabea3b72c..f8e8e998b91b6 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -24,14 +24,10 @@ if(NOT TARGET spdlog::spdlog) set(MODULE_LC spdlog) SETUP_BUILD_VARS() - # Darwin systems we want to avoid system packages. We are entirely self sufficient - # Avoids homebrew populating rubbish we cant control - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(_spdlog_find_option NO_SYSTEM_ENVIRONMENT_PATH) - endif() - # Check for existing SPDLOG. If version >= SPDLOG-VERSION file version, dont build - find_package(SPDLOG ${_spdlog_find_option} CONFIG QUIET) + find_package(SPDLOG CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) if((SPDLOG_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_SPDLOG) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_SPDLOG) OR @@ -83,14 +79,17 @@ if(NOT TARGET spdlog::spdlog) endif() find_path(SPDLOG_INCLUDE_DIR NAMES spdlog/spdlog.h - PATHS ${PC_SPDLOG_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include ${PC_SPDLOG_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(SPDLOG_LIBRARY_RELEASE NAMES spdlog - PATHS ${PC_SPDLOG_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_SPDLOG_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(SPDLOG_LIBRARY_DEBUG NAMES spdlogd - PATHS ${PC_SPDLOG_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_SPDLOG_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) # Only add -D definitions. Skip -I include as we do a find_path for the header anyway diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index e1b8745e4a749..e455a58cb5315 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -61,11 +61,18 @@ if(NOT TARGET TagLib::TagLib) pkg_check_modules(PC_TAGLIB taglib>=1.9.0 QUIET) endif() - find_path(TAGLIB_INCLUDE_DIR taglib/tag.h PATHS ${PC_TAGLIB_INCLUDEDIR}) + find_path(TAGLIB_INCLUDE_DIR NAMES taglib/tag.h + HINTS ${DEPENDS_PATH}/include ${PC_TAGLIB_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(TAGLIB_LIBRARY_RELEASE NAMES tag - PATHS ${PC_TAGLIB_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_TAGLIB_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(TAGLIB_LIBRARY_DEBUG NAMES tagd - PATHS ${PC_TAGLIB_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_TAGLIB_LIBDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) set(TAGLIB_VERSION ${PC_TAGLIB_VERSION}) set(TAGLIB_LINK_LIBS ${PC_TAGLIB_LIBRARIES}) diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index d95a7343b6a67..ba11a47a3f604 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -14,14 +14,9 @@ if(NOT TARGET tinyxml2::tinyxml2) SETUP_BUILD_VARS() - # Darwin systems we want to avoid system packages. We are entirely self sufficient - # Avoids homebrew populating rubbish we cant control - # Do we want to set this for all except LINUX/FREEBSD possibly? - if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") - set(_tinyxml2_find_option NO_SYSTEM_ENVIRONMENT_PATH) - endif() - - find_package(TINYXML2 ${_tinyxml2_find_option} CONFIG QUIET) + find_package(TINYXML2 CONFIG QUIET + HINTS ${DEPENDS_PATH}/lib + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway @@ -68,12 +63,18 @@ if(NOT TARGET tinyxml2::tinyxml2) pkg_check_modules(PC_TINYXML2 tinyxml2 QUIET) endif() - find_path(TINYXML2_INCLUDE_DIR tinyxml2.h - PATHS ${PC_TINYXML2_INCLUDEDIR}) + find_path(TINYXML2_INCLUDE_DIR NAMES tinyxml2.h + HINTS ${DEPENDS_PATH}/include ${PC_TINYXML2_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(TINYXML2_LIBRARY_RELEASE NAMES tinyxml2 - PATHS ${PC_TINYXML2_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_TINYXML2_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(TINYXML2_LIBRARY_DEBUG NAMES tinyxml2d - PATHS ${PC_TINYXML2_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_TINYXML2_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) set(TINYXML2_VERSION ${PC_TINYXML2_VERSION}) endif() From 85381a90145cad627e3a0254690ed35c8fa77d57 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 15 Sep 2023 08:38:19 +0200 Subject: [PATCH 236/811] [video] CVideoTagExtractionHelper::ExtractEmbeddedArtFor: Use item's dyn path, so the code even works if item has no videotag (should not happen, but just in case for the future). --- xbmc/video/tags/VideoTagExtractionHelper.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xbmc/video/tags/VideoTagExtractionHelper.cpp b/xbmc/video/tags/VideoTagExtractionHelper.cpp index fbab01c805ce5..b331305fc4860 100644 --- a/xbmc/video/tags/VideoTagExtractionHelper.cpp +++ b/xbmc/video/tags/VideoTagExtractionHelper.cpp @@ -35,10 +35,7 @@ std::string CVideoTagExtractionHelper::ExtractEmbeddedArtFor(const CFileItem& it for (const auto& it : tag.m_coverArt) { if (it.m_type == artType) - { - return CTextureUtils::GetWrappedImageURL(item.GetVideoInfoTag()->m_strFileNameAndPath, - "video_" + artType); - } + return CTextureUtils::GetWrappedImageURL(item.GetDynPath(), "video_" + artType); } return {}; } From 510bb3d60451ea9b708d695ff21f6a0e5135fae3 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 13 Sep 2023 09:55:41 +0200 Subject: [PATCH 237/811] [video] Choose art: Select last selected art type on re-open of type selection dialog. Modernize the surrounding code a bit. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 128 ++++++++++++++-------- xbmc/video/dialogs/GUIDialogVideoInfo.h | 1 - 2 files changed, 80 insertions(+), 49 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 2ded28218318b..5c804c7238c90 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -55,8 +55,10 @@ #include "video/VideoUtils.h" #include "video/windows/GUIWindowVideoNav.h" +#include #include #include +#include using namespace XFILE::VIDEODATABASEDIRECTORY; using namespace XFILE; @@ -766,7 +768,7 @@ void AddHardCodedAndExtendedArtTypes(std::vector& artTypes, const C { for (const auto& artType : CVideoThumbLoader::GetArtTypes(tag.m_type)) { - if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + if (std::find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) artTypes.emplace_back(artType); } } @@ -779,8 +781,9 @@ void AddCurrentArtTypes(std::vector& artTypes, const CVideoInfoTag& db.GetArtForItem(tag.m_iDbId, tag.m_type, currentArt); for (const auto& art : currentArt) { - if (!art.second.empty() && find(artTypes.cbegin(), artTypes.cend(), art.first) == artTypes.cend()) - artTypes.push_back(art.first); + if (!art.second.empty() && + std::find(artTypes.cbegin(), artTypes.cend(), art.first) == artTypes.cend()) + artTypes.emplace_back(art.first); } } @@ -792,8 +795,8 @@ void AddMediaTypeArtTypes(std::vector& artTypes, const CVideoInfoTa db.GetArtTypes(tag.m_type, dbArtTypes); for (const auto& artType : dbArtTypes) { - if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) - artTypes.push_back(artType); + if (std::find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + artTypes.emplace_back(artType); } } @@ -803,8 +806,8 @@ void AddAvailableArtTypes(std::vector& artTypes, const CVideoInfoTa { for (const auto& artType : db.GetAvailableArtTypesForItem(tag.m_iDbId, tag.m_type)) { - if (find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) - artTypes.push_back(artType); + if (std::find(artTypes.cbegin(), artTypes.cend(), artType) == artTypes.cend()) + artTypes.emplace_back(artType); } } @@ -823,58 +826,86 @@ std::vector GetArtTypesList(const CVideoInfoTag& tag) db.Close(); return artTypes; } -} -std::string CGUIDialogVideoInfo::ChooseArtType(const CFileItem &videoItem) +class CArtTypeChooser { - // prompt for choice - CGUIDialogSelect *dialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); - if (!dialog || !videoItem.HasVideoInfoTag()) - return ""; +public: + CArtTypeChooser() = delete; + explicit CArtTypeChooser(const std::shared_ptr& item) : m_item(item) {} + + bool ChooseArtType(); + const std::string& GetArtType() const { return m_artType; } + +private: + std::shared_ptr m_item; + CFileItemList m_items; + int m_selectedItem{0}; + std::string m_artType; +}; + +bool CArtTypeChooser::ChooseArtType() +{ + CGUIDialogSelect* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_SELECT); + if (!dialog || !m_item->HasVideoInfoTag()) + return false; - CFileItemList items; dialog->SetHeading(CVariant{13511}); dialog->Reset(); dialog->SetUseDetails(true); - dialog->EnableButton(true, 13516); - - std::vector artTypes = GetArtTypesList(*videoItem.GetVideoInfoTag()); - - for (std::vector::const_iterator i = artTypes.begin(); i != artTypes.end(); ++i) - { - const std::string& type = *i; - CFileItemPtr item(new CFileItem(type, false)); - if (type == "banner") - item->SetLabel(g_localizeStrings.Get(20020)); - else if (type == "fanart") - item->SetLabel(g_localizeStrings.Get(20445)); - else if (type == "poster") - item->SetLabel(g_localizeStrings.Get(20021)); - else if (type == "thumb") - item->SetLabel(g_localizeStrings.Get(21371)); - else - item->SetLabel(type); - item->SetProperty("type", type); - if (videoItem.HasArt(type)) - item->SetArt("thumb", videoItem.GetArt(type)); - items.Add(item); + dialog->EnableButton(true, 13516); // Enable "Add art type" button + + if (m_items.IsEmpty()) + { + const std::vector availableArtTypes = GetArtTypesList(*m_item->GetVideoInfoTag()); + + // maps art types to resource ids + static const std::unordered_map name2idMap = { + {"banner", 20020}, + {"fanart", 20445}, + {"poster", 20021}, + {"thumb", 21371}, + }; + + for (const auto& type : availableArtTypes) + { + const auto item = std::make_shared(type, false); + item->SetProperty("type", type); + if (m_item->HasArt(type)) + item->SetArt("thumb", m_item->GetArt(type)); + + const auto it = name2idMap.find(type); + item->SetLabel(it == name2idMap.cend() ? type : g_localizeStrings.Get((*it).second)); + + m_items.Add(item); + } } - dialog->SetItems(items); + dialog->SetItems(m_items); + dialog->SetSelected(m_selectedItem); dialog->Open(); + m_selectedItem = dialog->GetSelectedItem(); if (dialog->IsButtonPressed()) { - // Get the new artwork name - std::string strArtworkName; - if (!CGUIKeyboardFactory::ShowAndGetInput(strArtworkName, CVariant{g_localizeStrings.Get(13516)}, false)) - return ""; + // "Add art type" button pressed. Get the new artwork name. + std::string artworkName; + if (!CGUIKeyboardFactory::ShowAndGetInput(artworkName, CVariant{g_localizeStrings.Get(13516)}, + false)) + return false; - return strArtworkName; + m_artType = artworkName; + } + else + { + // A type was selected from the list of available art types. + m_artType = dialog->GetSelectedFileItem()->GetProperty("type").asString(); } - return dialog->GetSelectedFileItem()->GetProperty("type").asString(); + return !m_artType.empty(); } +} // unnamed namespace void CGUIDialogVideoInfo::OnGetArt() { @@ -1682,14 +1713,15 @@ bool CGUIDialogVideoInfo::ChooseAndManageVideoItemArtwork(const std::shared_ptr< { bool result = false; - std::string artType; + CArtTypeChooser chooser{item}; do { - artType = ChooseArtType(*item); - if (!artType.empty()) - result = ManageVideoItemArtwork(item, item->GetVideoInfoTag()->m_type, artType); + if (!chooser.ChooseArtType()) + break; + + result = ManageVideoItemArtwork(item, item->GetVideoInfoTag()->m_type, chooser.GetArtType()); - } while (!artType.empty()); + } while (true); return result; } diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.h b/xbmc/video/dialogs/GUIDialogVideoInfo.h index 06c703cc56078..c726003beac2e 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.h +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.h @@ -107,7 +107,6 @@ class CGUIDialogVideoInfo : int m_startUserrating = -1; private: - static std::string ChooseArtType(const CFileItem& item); static bool ManageVideoItemArtwork(const std::shared_ptr& item, const MediaType& mediaType, const std::string& artType); From 7192b68f747ee8809a48699b0f7e0978f88fe4a7 Mon Sep 17 00:00:00 2001 From: honest-mule Date: Sun, 17 Sep 2023 17:41:36 +0530 Subject: [PATCH 238/811] [VideoPlayer] Fixed: Infinite video halt when cache is full & valid --- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index 72419823d8aa9..c4590feca78a1 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -1871,7 +1871,10 @@ void CVideoPlayer::HandlePlaySpeed() CGUIDialogKaiToast::QueueNotification(g_localizeStrings.Get(21454), g_localizeStrings.Get(21455)); SetCaching(CACHESTATE_INIT); } - if (cache.level >= 1.0) + // Note: Previously used cache.level >= 1 would keep video stalled + // event after cache was full + // Talk link: https://github.com/xbmc/xbmc/pull/23760 + if (cache.time > 8.0) SetCaching(CACHESTATE_INIT); } else From df8cf140ab7662f7d9940aa44d6941a78f07e8c1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 13 Sep 2023 22:37:40 +0200 Subject: [PATCH 239/811] [video] Choose art: Offload gathering art to separate thread, so GUI does not block. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 54 +++++++++++++++++++++-- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 5c804c7238c90..14d718f79359e 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -15,6 +15,7 @@ #include "ServiceBroker.h" #include "TextureCache.h" #include "Util.h" +#include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogFileBrowser.h" #include "dialogs/GUIDialogProgress.h" #include "dialogs/GUIDialogSelect.h" @@ -41,6 +42,7 @@ #include "settings/SettingsComponent.h" #include "settings/lib/Setting.h" #include "storage/MediaManager.h" +#include "threads/IRunnable.h" #include "utils/FileUtils.h" #include "utils/SortUtils.h" #include "utils/StringUtils.h" @@ -1733,6 +1735,48 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& GetRemoteArt() const { return m_remoteArt; } + const std::string& GetLocalArt() const { return m_localArt; } + +private: + // IRunnable implementation + void Run() override + { + m_currentArt = m_handler.GetCurrentArt(); + m_embeddedArt = m_handler.GetEmbeddedArt(); + m_remoteArt = m_handler.GetRemoteArt(); + m_localArt = m_handler.GetLocalArt(); + } + + const VIDEO::IVideoItemArtworkHandler& m_handler; + + // Note: No mutex needed to protect the strings, as correct usage sequence of this class is: + // T1 calls FetchAllArt + // T1 blocks in CGUIDialogBusy::Wait until strings are filled by worker thread T2 in Run() + // T1: calls GetFooArt, which accesses the then completely filled strings + std::string m_currentArt; + std::string m_embeddedArt; + std::vector m_remoteArt; + std::string m_localArt; +}; +} // unnamed namespace + bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr& item, const MediaType& mediaType, const std::string& artType) @@ -1742,10 +1786,12 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr artHandler = VIDEO::IVideoItemArtworkHandlerFactory::Create(item, mediaType, artType); + CAsyncGetArt asyncArtHandler{*artHandler}; + asyncArtHandler.FetchAllArt(); CFileItemList items; - const std::string currentArt = artHandler->GetCurrentArt(); + const std::string currentArt = asyncArtHandler.GetCurrentArt(); if (!currentArt.empty()) { const auto itemCurrent = std::make_shared("thumb://Current", false); @@ -1754,7 +1800,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrGetEmbeddedArt(); + const std::string embeddedArt = asyncArtHandler.GetEmbeddedArt(); if (!embeddedArt.empty()) { const auto itemEmbedded = std::make_shared("thumb://Embedded", false); @@ -1763,7 +1809,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptr remoteArt = artHandler->GetRemoteArt(); + const std::vector remoteArt = asyncArtHandler.GetRemoteArt(); for (size_t i = 0; i < remoteArt.size(); ++i) { const auto itemRemote = @@ -1777,7 +1823,7 @@ bool CGUIDialogVideoInfo::ManageVideoItemArtwork(const std::shared_ptrClearCachedImage(remoteArt[i]); } - const std::string localArt = artHandler->GetLocalArt(); + const std::string localArt = asyncArtHandler.GetLocalArt(); if (!localArt.empty()) { const auto itemLocal = std::make_shared("thumb://Local", false); From b57a63242ef1cac33efd8d68793b6285ef7dc8f6 Mon Sep 17 00:00:00 2001 From: CastagnaIT Date: Mon, 18 Sep 2023 08:55:40 +0200 Subject: [PATCH 240/811] [LanguageCodes] Narrow mode for finding language code --- xbmc/utils/LangCodeExpander.cpp | 3 +-- xbmc/utils/LangCodeExpander.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/xbmc/utils/LangCodeExpander.cpp b/xbmc/utils/LangCodeExpander.cpp index f683905049dda..aa629c2b08fc0 100644 --- a/xbmc/utils/LangCodeExpander.cpp +++ b/xbmc/utils/LangCodeExpander.cpp @@ -592,8 +592,7 @@ std::string CLangCodeExpander::ConvertToISO6392T(const std::string& lang) std::string CLangCodeExpander::FindLanguageCodeWithSubtag(const std::string& str) { CRegExp regLangCode; - if (regLangCode.RegComp( - "(?:^|\\s|\\()(([A-Za-z]{2,3})-([A-Za-z]{2}|[0-9]{3}|[A-Za-z]{4}))(?:$|\\s|\\))") && + if (regLangCode.RegComp("\\{(([A-Za-z]{2,3})-([A-Za-z]{2}|[0-9]{3}|[A-Za-z]{4}))\\}") && regLangCode.RegFind(str) >= 0) { return regLangCode.GetMatch(1); diff --git a/xbmc/utils/LangCodeExpander.h b/xbmc/utils/LangCodeExpander.h index dc9e5dc48a9b0..80f02d7649c30 100644 --- a/xbmc/utils/LangCodeExpander.h +++ b/xbmc/utils/LangCodeExpander.h @@ -120,7 +120,7 @@ class CLangCodeExpander * \brief Find a language code with subtag (e.g. zh-tw, zh-Hans) in to a string. * This function find a limited set of IETF BCP47 specs, so: * language tag + region subtag, or, language tag + script subtag. - * The language code can be found also if wrapped with round brackets. + * The language code can be found if wrapped by curly brackets e.g. {pt-br}. * \param str The string where find the language code. * \return The language code found in the string, otherwise empty string */ From be8f6e92e88c7b94508b3bfd21b44e5c8d85d8d6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 18 Sep 2023 19:16:03 +1000 Subject: [PATCH 241/811] [android] Allow packaging other themes for bundled skins --- tools/android/packaging/Makefile.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/android/packaging/Makefile.in b/tools/android/packaging/Makefile.in index 2b82c8d780f8b..514b655efb6f0 100644 --- a/tools/android/packaging/Makefile.in +++ b/tools/android/packaging/Makefile.in @@ -47,7 +47,7 @@ shared: cp -rfp $(DEPENDS_PATH)/share/kodi/* ./assets || true find `pwd`/assets/ -depth -name ".git" -exec rm -rf {} \; find `pwd`/assets/ -name "*.so" -exec rm {} \; - find `pwd`/assets/addons/skin.*/media/* -depth -not -iname "Textures.xbt" -exec rm -rf {} \; + find `pwd`/assets/addons/skin.*/media/* -depth -not -iname "*.xbt" -exec rm -rf {} \; cd `pwd`/assets/addons; rm -rf $(EXCLUDED_ADDONS) cp $(CMAKE_SOURCE_DIR)/privacy-policy.txt assets From 15b24746d78e5de13b48f7b7f20a3312fbdbc382 Mon Sep 17 00:00:00 2001 From: DeltaMikeCharlie <127641886+DeltaMikeCharlie@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:13:51 +1000 Subject: [PATCH 242/811] Add ParentalRatingCode availability for Skins --- addons/skin.estuary/xml/DialogPVRInfo.xml | 2 +- addons/skin.estuary/xml/Includes_PVR.xml | 2 +- xbmc/GUIInfoManager.cpp | 9 +++++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 1 + xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp | 3 +++ 5 files changed, 15 insertions(+), 2 deletions(-) diff --git a/addons/skin.estuary/xml/DialogPVRInfo.xml b/addons/skin.estuary/xml/DialogPVRInfo.xml index 6a287f56ab7b3..124915745afee 100644 --- a/addons/skin.estuary/xml/DialogPVRInfo.xml +++ b/addons/skin.estuary/xml/DialogPVRInfo.xml @@ -49,7 +49,7 @@ 1050 425 justify - + Skin.HasSetting(AutoScroll)
diff --git a/addons/skin.estuary/xml/Includes_PVR.xml b/addons/skin.estuary/xml/Includes_PVR.xml index 993aa0de1bff2..c5da145a01595 100644 --- a/addons/skin.estuary/xml/Includes_PVR.xml +++ b/addons/skin.estuary/xml/Includes_PVR.xml @@ -440,7 +440,7 @@ 465 830 list_bottom_offset - + Skin.HasSetting(AutoScroll) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index 32666e4ebce35..c74c5d12ec6e8 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -6713,6 +6713,14 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, /// @return The parental rating of the list item (PVR). ///

/// } +/// \table_row3{ `ListItem.ParentalRatingCode`, +/// \anchor ListItem_ParentalRatingCode +/// _string_, +/// @return The parental rating code (eg: 'PG'\, etc) of the list item (PVR). +///


+/// @skinning_v21 **[New Infolabel]** \link ListItem_ParentalRatingCode `ListItem.ParentalRatingCode`\endlink +///

+/// } /// \table_row3{ `ListItem.CurrentItem`, /// \anchor ListItem_CurrentItem /// _string_, @@ -7059,6 +7067,7 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "art", LISTITEM_ART }, { "property", LISTITEM_PROPERTY }, { "parentalrating", LISTITEM_PARENTAL_RATING }, + { "parentalratingcode", LISTITEM_PARENTAL_RATING_CODE }, { "currentitem", LISTITEM_CURRENTITEM }, { "isnew", LISTITEM_IS_NEW }, { "isboxset", LISTITEM_IS_BOXSET }, diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index dfb5b7f4223b1..ea5b326ccabd8 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -963,6 +963,7 @@ #define LISTITEM_ISAUTOUPDATEABLE (LISTITEM_START + 207) #define LISTITEM_VIDEO_HDR_TYPE (LISTITEM_START + 208) #define LISTITEM_SONG_VIDEO_URL (LISTITEM_START + 209) +#define LISTITEM_PARENTAL_RATING_CODE (LISTITEM_START + 210) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp index ae66f12093ae9..185e55165e23e 100644 --- a/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp +++ b/xbmc/pvr/guilib/guiinfo/PVRGUIInfo.cpp @@ -752,6 +752,9 @@ bool CPVRGUIInfo::GetListItemAndPlayerLabel(const CFileItem* item, return true; } return false; + case LISTITEM_PARENTAL_RATING_CODE: + strValue = epgTag->ParentalRatingCode(); + return true; case VIDEOPLAYER_PREMIERED: case LISTITEM_PREMIERED: if (epgTag->FirstAired().IsValid()) From d6cc1131ce9075a5dafea3169beb2ca292ad8a8e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 18 Sep 2023 15:13:01 +1000 Subject: [PATCH 243/811] [cmake] gen_skin_pack move to add_custom_command Move gen_skin_pack to add_custom_command and generate a "timestamp" file for dependency tracking. This allows the custom command to only be run once, rather than every build. clean in VS also removes timestamp file, allowing for a full clean and full rebuild including running the custom command. --- CMakeLists.txt | 20 +++++++++++++++----- cmake/scripts/common/ProjectMacros.cmake | 3 +++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8e137c447cb05..defd8e77692a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -412,6 +412,7 @@ else() endif() # These are skins that are copied into place from the source tree +set(XBT_SOURCE_FILELIST "") foreach(skin ${SKINS}) list(GET skin 0 dir) list(GET skin 1 relative) @@ -430,11 +431,20 @@ add_custom_target(gen_system_addons WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) # Pack skins and copy to correct build dir (MultiConfig Generator aware) -add_custom_target(gen_skin_pack - COMMAND ${CMAKE_COMMAND} -DBUNDLEDIR=${_bundle_dir} - -DTEXTUREPACKER_EXECUTABLE=$ - -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +add_custom_command( + OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp + COMMAND ${CMAKE_COMMAND} -DBUNDLEDIR=${_bundle_dir} + -DTEXTUREPACKER_EXECUTABLE=$ + -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$ + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp + DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake + ${XBT_SOURCE_FILELIST} + BYPRODUCTS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + COMMENT "Generating skin xbt" +) +add_custom_target(gen_skin_pack DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp) # Packaging target. This generates system addon, xbt creation, copy files to build tree add_custom_target(generate-packaging ALL diff --git a/cmake/scripts/common/ProjectMacros.cmake b/cmake/scripts/common/ProjectMacros.cmake index 015cc3db94a76..e3d9a58a6d03e 100644 --- a/cmake/scripts/common/ProjectMacros.cmake +++ b/cmake/scripts/common/ProjectMacros.cmake @@ -8,6 +8,8 @@ # xbt is added to ${XBT_FILES} function(pack_xbt input output) file(GLOB_RECURSE MEDIA_FILES ${input}/*) + list(APPEND XBT_SOURCE_FILELIST ${MEDIA_FILES}) + set(XBT_SOURCE_FILELIST ${XBT_SOURCE_FILELIST} PARENT_SCOPE) get_filename_component(dir ${output} DIRECTORY) if(${CORE_SYSTEM_NAME} MATCHES "windows") @@ -45,6 +47,7 @@ function(copy_skin_to_buildtree skin) endforeach() set(XBT_FILES ${XBT_FILES} PARENT_SCOPE) + set(XBT_SOURCE_FILELIST ${XBT_SOURCE_FILELIST} PARENT_SCOPE) set(install_data ${install_data} PARENT_SCOPE) endfunction() From e67efb3ccbb8072922584a319e6e3a8516ab9906 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 20 Sep 2023 12:42:18 +1000 Subject: [PATCH 244/811] [depends][native] Meson 1.2.1 bump --- tools/depends/native/meson/MESON-VERSION | 4 ++++ tools/depends/native/meson/Makefile | 12 ++---------- 2 files changed, 6 insertions(+), 10 deletions(-) create mode 100644 tools/depends/native/meson/MESON-VERSION diff --git a/tools/depends/native/meson/MESON-VERSION b/tools/depends/native/meson/MESON-VERSION new file mode 100644 index 0000000000000..cabee64aa20b9 --- /dev/null +++ b/tools/depends/native/meson/MESON-VERSION @@ -0,0 +1,4 @@ +LIBNAME=meson +VERSION=1.2.1 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=6221a14a6046aaba2c6eb601a9a5b928308bbd9da813ccec16b8f7578296b27d741e30e9343723770c3c7825c86b53193b41b9672dd17468d06d3b8d743bf52e diff --git a/tools/depends/native/meson/Makefile b/tools/depends/native/meson/Makefile index d8c684ff5c317..6d7754b29c1a7 100644 --- a/tools/depends/native/meson/Makefile +++ b/tools/depends/native/meson/Makefile @@ -1,14 +1,6 @@ -include ../../Makefile.include +include ../../Makefile.include MESON-VERSION ../../download-files.include PLATFORM=$(NATIVEPLATFORM) -DEPS =../../Makefile.include Makefile ../../download-files.include - -# lib name, version -LIBNAME=meson -VERSION=1.0.0 -SOURCE=$(LIBNAME)-$(VERSION) -ARCHIVE=$(SOURCE).tar.gz -SHA512=9b1195cfe856c1aa51bc79f6eb4d0f94925bb02d0a9fbd68a6a6ced6e5c252b09b22d9aac812640687e49b8d64a313ce48d0a69a3bf83ea8ffb8c9dab559fc23 -include ../../download-files.include +DEPS =../../Makefile.include Makefile MESON-VERSION ../../download-files.include all: .installed-$(PLATFORM) From 6256f7999711799c23c39888730d353326261d3a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:33:22 +1000 Subject: [PATCH 245/811] [cmake] Global variable to indicate Multi Config Generator --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index defd8e77692a3..2d5febb16d5c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,9 @@ if(DEPENDS_DIR) list(APPEND CMAKE_PREFIX_PATH ${DEPENDS_DIR}) endif() +# Variable to indicate if the project is targeting a Multi Config Generator (VS/Xcode primarily) +get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + # Set CORE_BUILD_DIR set(CORE_BUILD_DIR build) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) From 6a4f7fcd6839ca5b0b01feb2e5e654a5b1308c3c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:34:15 +1000 Subject: [PATCH 246/811] [cmake] FindCrossGUID Add internal build target always for multiconfig gen --- cmake/modules/FindCrossGUID.cmake | 51 ++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 18 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index f7ae5aff1e044..9f98b8a2c564d 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -6,34 +6,37 @@ # # CrossGUID::CrossGUID - The CrossGUID library -if(NOT TARGET CrossGUID::CrossGUID) - if(ENABLE_INTERNAL_CROSSGUID) - include(cmake/scripts/common/ModuleHelpers.cmake) +macro(buildCrossGUID) + include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC crossguid) + set(MODULE_LC crossguid) - SETUP_BUILD_VARS() + SETUP_BUILD_VARS() - set(CROSSGUID_VERSION ${${MODULE}_VER}) - set(CROSSGUID_DEBUG_POSTFIX "-dgb") + set(CROSSGUID_VERSION ${${MODULE}_VER}) + set(CROSSGUID_DEBUG_POSTFIX "-dgb") - set(_crossguid_definitions HAVE_NEW_CROSSGUID) + set(_crossguid_definitions HAVE_NEW_CROSSGUID) - if(ANDROID) - list(APPEND _crossguid_definitions GUID_ANDROID) - endif() + if(ANDROID) + list(APPEND _crossguid_definitions GUID_ANDROID) + endif() - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/001-fix-unused-function.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/002-disable-Wall-error.patch" - "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/003-add-cstdint-include.patch") + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/001-fix-unused-function.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/002-disable-Wall-error.patch" + "${CMAKE_SOURCE_DIR}/tools/depends/target/crossguid/003-add-cstdint-include.patch") - generate_patchcommand("${patches}") + generate_patchcommand("${patches}") - set(CMAKE_ARGS -DCROSSGUID_TESTS=OFF - -DDISABLE_WALL=ON) + set(CMAKE_ARGS -DCROSSGUID_TESTS=OFF + -DDISABLE_WALL=ON) - BUILD_DEP_TARGET() + BUILD_DEP_TARGET() +endmacro() +if(NOT TARGET CrossGUID::CrossGUID) + if(ENABLE_INTERNAL_CROSSGUID) + buildCrossGUID() else() if(PKG_CONFIG_FOUND) pkg_check_modules(PC_CROSSGUID crossguid QUIET) @@ -91,6 +94,18 @@ if(NOT TARGET CrossGUID::CrossGUID) if(TARGET crossguid) add_dependencies(CrossGUID::CrossGUID crossguid) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildCrossGUID() + endif() endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CrossGUID::CrossGUID) From 191bb2161d6e856ac188337489b9a9de9e9824ef Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:35:13 +1000 Subject: [PATCH 247/811] [cmake] FindFMT Add internal build target always for multiconfig gen --- cmake/modules/FindFmt.cmake | 57 ++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index a403a03ce540d..a6b844532c6e6 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -6,6 +6,30 @@ # # fmt::fmt - The Fmt library +macro(buildFmt) + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + set(FMT_VERSION ${${MODULE}_VER}) + # fmt debug uses postfix d for all platforms + set(FMT_DEBUG_POSTFIX d) + + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") + generate_patchcommand("${patches}") + endif() + + set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DFMT_DOC=OFF + -DFMT_TEST=OFF + -DFMT_INSTALL=ON + "${EXTRA_ARGS}") + + BUILD_DEP_TARGET() +endmacro() + define_property(TARGET PROPERTY LIB_BUILD BRIEF_DOCS "This target will be compiling the library" FULL_DOCS "This target will be compiling the library") @@ -45,27 +69,8 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) # Set FORCE_BUILD to enable fmt::fmt property that build will occur set(FORCE_BUILD ON) - if(APPLE) - set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") - endif() - - set(FMT_VERSION ${${MODULE}_VER}) - # fmt debug uses postfix d for all platforms - set(FMT_DEBUG_POSTFIX d) - - if(WIN32 OR WINDOWS_STORE) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") - generate_patchcommand("${patches}") - endif() + buildFmt() - set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} - -DFMT_DOC=OFF - -DFMT_TEST=OFF - -DFMT_INSTALL=ON - "${EXTRA_ARGS}") - - BUILD_DEP_TARGET() else() if(NOT TARGET fmt::fmt) set(FMT_PKGCONFIG_CHECK ON) @@ -115,6 +120,18 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) if(TARGET fmt) add_dependencies(fmt::fmt fmt) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildFmt() + endif() endif() endif() From ee3272d06221dbe8d346b98e846dc97e122a73ed Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:35:38 +1000 Subject: [PATCH 248/811] [cmake] FindPCRE Add internal build target always for multiconfig gen --- cmake/modules/FindPCRE.cmake | 109 ++++++++++++++++++++--------------- 1 file changed, 63 insertions(+), 46 deletions(-) diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index 0b7e0c3203754..e650802184c5e 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -8,6 +8,55 @@ # PCRE::PCRECPP - The PCRECPP library # PCRE::PCRE - The PCRE library +macro(buildPCRE) + set(PCRE_VERSION ${${MODULE}_VER}) + set(PCRE_DEBUG_POSTFIX d) + + set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-cmakeconfig.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-enable_docs_pc.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-postfix.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-win-pdb.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/jit_aarch64.patch") + + if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) + list(APPEND patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/tvos-bitcode-fix.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/ios-clear_cache.patch") + endif() + + generate_patchcommand("${patches}") + + set(CMAKE_ARGS -DPCRE_NEWLINE=ANYCRLF + -DPCRE_NO_RECURSE=ON + -DPCRE_MATCH_LIMIT_RECURSION=1500 + -DPCRE_SUPPORT_JIT=ON + -DPCRE_SUPPORT_PCREGREP_JIT=ON + -DPCRE_SUPPORT_UTF=ON + -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON + -DPCRE_SUPPORT_LIBZ=OFF + -DPCRE_SUPPORT_LIBBZ2=OFF + -DPCRE_BUILD_PCREGREP=OFF + -DPCRE_BUILD_TESTS=OFF) + + if(WIN32 OR WINDOWS_STORE) + list(APPEND CMAKE_ARGS -DINSTALL_MSVC_PDB=ON) + elseif(CORE_SYSTEM_NAME STREQUAL android) + # CMake CheckFunctionExists incorrectly detects strtoq for android + list(APPEND CMAKE_ARGS -DHAVE_STRTOQ=0) + endif() + + # populate PCRECPP lib without a separate module + if(NOT CORE_SYSTEM_NAME MATCHES windows) + # Non windows platforms have a lib prefix for the lib artifact + set(_libprefix "lib") + endif() + # regex used to get platform extension (eg lib for windows, .a for unix) + string(REGEX REPLACE "^.*\\." "" _LIBEXT ${${MODULE}_BYPRODUCT}) + set(PCRECPP_LIBRARY_DEBUG ${DEP_LOCATION}/lib/${_libprefix}pcrecpp${${MODULE}_DEBUG_POSTFIX}.${_LIBEXT}) + set(PCRECPP_LIBRARY_RELEASE ${DEP_LOCATION}/lib/${_libprefix}pcrecpp.${_LIBEXT}) + + BUILD_DEP_TARGET() +endmacro() + if(NOT PCRE::pcre) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -24,52 +73,7 @@ if(NOT PCRE::pcre) if((PCRE_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_PCRE)) - set(PCRE_VERSION ${${MODULE}_VER}) - set(PCRE_DEBUG_POSTFIX d) - - set(patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-all-cmakeconfig.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/002-all-enable_docs_pc.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/003-all-postfix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/004-win-pdb.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/jit_aarch64.patch") - - if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - list(APPEND patches "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/tvos-bitcode-fix.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/ios-clear_cache.patch") - endif() - - generate_patchcommand("${patches}") - - set(CMAKE_ARGS -DPCRE_NEWLINE=ANYCRLF - -DPCRE_NO_RECURSE=ON - -DPCRE_MATCH_LIMIT_RECURSION=1500 - -DPCRE_SUPPORT_JIT=ON - -DPCRE_SUPPORT_PCREGREP_JIT=ON - -DPCRE_SUPPORT_UTF=ON - -DPCRE_SUPPORT_UNICODE_PROPERTIES=ON - -DPCRE_SUPPORT_LIBZ=OFF - -DPCRE_SUPPORT_LIBBZ2=OFF - -DPCRE_BUILD_PCREGREP=OFF - -DPCRE_BUILD_TESTS=OFF) - - if(WIN32 OR WINDOWS_STORE) - list(APPEND CMAKE_ARGS -DINSTALL_MSVC_PDB=ON) - elseif(CORE_SYSTEM_NAME STREQUAL android) - # CMake CheckFunctionExists incorrectly detects strtoq for android - list(APPEND CMAKE_ARGS -DHAVE_STRTOQ=0) - endif() - - # populate PCRECPP lib without a separate module - if(NOT CORE_SYSTEM_NAME MATCHES windows) - # Non windows platforms have a lib prefix for the lib artifact - set(_libprefix "lib") - endif() - # regex used to get platform extension (eg lib for windows, .a for unix) - string(REGEX REPLACE "^.*\\." "" _LIBEXT ${${MODULE}_BYPRODUCT}) - set(PCRECPP_LIBRARY_DEBUG ${DEP_LOCATION}/lib/${_libprefix}pcrecpp${${MODULE}_DEBUG_POSTFIX}.${_LIBEXT}) - set(PCRECPP_LIBRARY_RELEASE ${DEP_LOCATION}/lib/${_libprefix}pcrecpp.${_LIBEXT}) - - BUILD_DEP_TARGET() + buildPCRE() else() if(NOT TARGET PCRE::pcre) @@ -192,9 +196,22 @@ if(NOT PCRE::pcre) if(TARGET pcre) add_dependencies(PCRE::pcre pcre) add_dependencies(PCRE::pcrecpp pcre) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildPCRE() + endif() endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcre) set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcrecpp) + endif() endif() From f3b825c171ea7dbc8c731b456db2efbed6af82ac Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:36:03 +1000 Subject: [PATCH 249/811] [cmake] FindSpdlog Add internal build target always for multiconfig gen --- cmake/modules/FindSpdlog.cmake | 88 ++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index f8e8e998b91b6..9ef855cbbe80b 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -6,6 +6,45 @@ # # spdlog::spdlog - The Spdlog library +macro(buildSpdlog) + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() + + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") + generate_patchcommand("${patches}") + + set(EXTRA_ARGS -DSPDLOG_WCHAR_SUPPORT=ON + -DSPDLOG_WCHAR_FILENAMES=ON) + + set(EXTRA_DEFINITIONS SPDLOG_WCHAR_FILENAMES + SPDLOG_WCHAR_TO_UTF8_SUPPORT) + endif() + + set(SPDLOG_VERSION ${${MODULE}_VER}) + # spdlog debug uses postfix d for all platforms + set(SPDLOG_DEBUG_POSTFIX d) + + set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DSPDLOG_BUILD_EXAMPLE=OFF + -DSPDLOG_BUILD_TESTS=OFF + -DSPDLOG_BUILD_BENCH=OFF + -DSPDLOG_FMT_EXTERNAL=ON + ${EXTRA_ARGS}) + + # Set definitions that will be set in the built cmake config file + # We dont import the config file if we build internal (chicken/egg scenario) + set(_spdlog_definitions SPDLOG_COMPILED_LIB + SPDLOG_FMT_EXTERNAL + ${EXTRA_DEFINITIONS}) + + BUILD_DEP_TARGET() + + add_dependencies(${MODULE_LC} fmt::fmt) +endmacro() + if(NOT TARGET spdlog::spdlog) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -33,42 +72,7 @@ if(NOT TARGET spdlog::spdlog) ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_SPDLOG) OR LIB_FORCE_REBUILD) - if(APPLE) - set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") - endif() - - if(WIN32 OR WINDOWS_STORE) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") - generate_patchcommand("${patches}") - - set(EXTRA_ARGS -DSPDLOG_WCHAR_SUPPORT=ON - -DSPDLOG_WCHAR_FILENAMES=ON) - - set(EXTRA_DEFINITIONS SPDLOG_WCHAR_FILENAMES - SPDLOG_WCHAR_TO_UTF8_SUPPORT) - endif() - - set(SPDLOG_VERSION ${${MODULE}_VER}) - # spdlog debug uses postfix d for all platforms - set(SPDLOG_DEBUG_POSTFIX d) - - set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} - -DSPDLOG_BUILD_EXAMPLE=OFF - -DSPDLOG_BUILD_TESTS=OFF - -DSPDLOG_BUILD_BENCH=OFF - -DSPDLOG_FMT_EXTERNAL=ON - ${EXTRA_ARGS}) - - # Set definitions that will be set in the built cmake config file - # We dont import the config file if we build internal (chicken/egg scenario) - set(_spdlog_definitions SPDLOG_COMPILED_LIB - SPDLOG_FMT_EXTERNAL - ${EXTRA_DEFINITIONS}) - - BUILD_DEP_TARGET() - - add_dependencies(${MODULE_LC} fmt::fmt) + buildSpdlog() else() if(NOT TARGET spdlog::spdlog) find_package(PkgConfig) @@ -160,6 +164,18 @@ if(NOT TARGET spdlog::spdlog) if(TARGET spdlog) add_dependencies(spdlog::spdlog spdlog) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildSpdlog() + endif() endif() endif() endif() From c70bd996a68050ce1ae645cdc19a67df3335c241 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:36:21 +1000 Subject: [PATCH 250/811] [cmake] FindTagLib Add internal build target always for multiconfig gen --- cmake/modules/FindTagLib.cmake | 84 ++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index e455a58cb5315..311d9e7ef8d58 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -8,53 +8,55 @@ # TagLib::TagLib - The TagLib library # -if(NOT TARGET TagLib::TagLib) - if(ENABLE_INTERNAL_TAGLIB) - - # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html - set(FPHSA_NAME_MISMATCHED 1) - - # Darwin systems use a system tbd that isnt found as a static lib - # Other platforms when using ENABLE_INTERNAL_TAGLIB, we want the static lib - if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") - # Requires cmake 3.24 for ZLIB_USE_STATIC_LIBS to actually do something - set(ZLIB_USE_STATIC_LIBS ON) - endif() - find_package(ZLIB REQUIRED) - unset(FPHSA_NAME_MISMATCHED) +macro(buildTagLib) + # Suppress mismatch warning, see https://cmake.org/cmake/help/latest/module/FindPackageHandleStandardArgs.html + set(FPHSA_NAME_MISMATCHED 1) + + # Darwin systems use a system tbd that isnt found as a static lib + # Other platforms when using ENABLE_INTERNAL_TAGLIB, we want the static lib + if(NOT CMAKE_SYSTEM_NAME STREQUAL "Darwin") + # Requires cmake 3.24 for ZLIB_USE_STATIC_LIBS to actually do something + set(ZLIB_USE_STATIC_LIBS ON) + endif() + find_package(ZLIB REQUIRED) + unset(FPHSA_NAME_MISMATCHED) - include(cmake/scripts/common/ModuleHelpers.cmake) + include(cmake/scripts/common/ModuleHelpers.cmake) - set(MODULE_LC taglib) + set(MODULE_LC taglib) - SETUP_BUILD_VARS() + SETUP_BUILD_VARS() - set(TAGLIB_VERSION ${${MODULE}_VER}) + set(TAGLIB_VERSION ${${MODULE}_VER}) - if(WIN32 OR WINDOWS_STORE) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-cmake-pdb-debug.patch") - generate_patchcommand("${patches}") + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-cmake-pdb-debug.patch") + generate_patchcommand("${patches}") - if(WINDOWS_STORE) - set(EXTRA_ARGS -DPLATFORM_WINRT=ON) - endif() + if(WINDOWS_STORE) + set(EXTRA_ARGS -DPLATFORM_WINRT=ON) endif() + endif() - # Debug postfix only used for windows - if(WIN32 OR WINDOWS_STORE) - set(TAGLIB_DEBUG_POSTFIX "d") - endif() + # Debug postfix only used for windows + if(WIN32 OR WINDOWS_STORE) + set(TAGLIB_DEBUG_POSTFIX "d") + endif() - set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - -DBUILD_EXAMPLES=OFF - -DBUILD_TESTING=OFF - -DBUILD_BINDINGS=OFF - ${EXTRA_ARGS}) + set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + -DBUILD_EXAMPLES=OFF + -DBUILD_TESTING=OFF + -DBUILD_BINDINGS=OFF + ${EXTRA_ARGS}) - BUILD_DEP_TARGET() + BUILD_DEP_TARGET() - add_dependencies(${MODULE_LC} ZLIB::ZLIB) + add_dependencies(${MODULE_LC} ZLIB::ZLIB) +endmacro() +if(NOT TARGET TagLib::TagLib) + if(ENABLE_INTERNAL_TAGLIB) + buildTagLib() else() if(PKG_CONFIG_FOUND) @@ -110,6 +112,18 @@ if(NOT TARGET TagLib::TagLib) if(TARGET taglib) add_dependencies(TagLib::TagLib taglib) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildTagLib() + endif() endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP TagLib::TagLib) From 1e232b48990dd50cab7133c057b6c64898fab80d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 11 Sep 2023 13:36:37 +1000 Subject: [PATCH 251/811] [cmake] FindTinyXML2 Add internal build target always for multiconfig gen --- cmake/modules/FindTinyXML2.cmake | 78 +++++++++++++++++++------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index ba11a47a3f604..709eeefabdf16 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -7,6 +7,40 @@ # # tinyxml2::tinyxml2 - The TinyXML2 library +macro(buildTinyXML2) + set(TINYXML2_VERSION ${${MODULE}_VER}) + set(TINYXML2_DEBUG_POSTFIX d) + + find_package(Patch MODULE REQUIRED) + + if(UNIX) + # ancient patch (Apple/freebsd) fails to patch tinyxml2 CMakeLists.txt file due to it being crlf encoded + # Strip crlf before applying patches. + # Freebsd fails even harder and requires both .patch and CMakeLists.txt to be crlf stripped + # possibly add requirement for freebsd on gpatch? Wouldnt need to copy/strip the patch file then + set(PATCH_COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/CMakeLists.txt + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch + COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch + COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch) + else() + set(PATCH_COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch) + endif() + + if(CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR STREQUAL Xcode) + # Multiconfig generators fail due to file(GENERATE tinyxml.pc) command. + # This patch makes it generate a distinct named pc file for each build type and rename + # pc file on install + list(APPEND PATCH_COMMAND COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/002-multiconfig-gen-pkgconfig.patch) + endif() + + set(CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} + -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -Dtinyxml2_BUILD_TESTING=OFF) + + BUILD_DEP_TARGET() +endmacro() + if(NOT TARGET tinyxml2::tinyxml2) include(cmake/scripts/common/ModuleHelpers.cmake) @@ -23,37 +57,7 @@ if(NOT TARGET tinyxml2::tinyxml2) if((TINYXML2_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_TINYXML2) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_TINYXML2)) - set(TINYXML2_VERSION ${${MODULE}_VER}) - set(TINYXML2_DEBUG_POSTFIX d) - - find_package(Patch MODULE REQUIRED) - - if(UNIX) - # ancient patch (Apple/freebsd) fails to patch tinyxml2 CMakeLists.txt file due to it being crlf encoded - # Strip crlf before applying patches. - # Freebsd fails even harder and requires both .patch and CMakeLists.txt to be crlf stripped - # possibly add requirement for freebsd on gpatch? Wouldnt need to copy/strip the patch file then - set(PATCH_COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/CMakeLists.txt - COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch - COMMAND sed -ie s|\\r\$|| ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch - COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${MODULE_LC}/src/${MODULE_LC}/001-debug-pdb.patch) - else() - set(PATCH_COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/001-debug-pdb.patch) - endif() - - if(CMAKE_GENERATOR MATCHES "Visual Studio" OR CMAKE_GENERATOR STREQUAL Xcode) - # Multiconfig generators fail due to file(GENERATE tinyxml.pc) command. - # This patch makes it generate a distinct named pc file for each build type and rename - # pc file on install - list(APPEND PATCH_COMMAND COMMAND ${PATCH_EXECUTABLE} -p1 -i ${CMAKE_SOURCE_DIR}/tools/depends/target/tinyxml2/002-multiconfig-gen-pkgconfig.patch) - endif() - - set(CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR} - -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} - -Dtinyxml2_BUILD_TESTING=OFF) - - BUILD_DEP_TARGET() + buildTinyXML2() else() # This is the fallback case where linux distro's dont ship cmake config files # use the old find_library way. Only do this if we didnt find a cmake config @@ -130,6 +134,18 @@ if(NOT TARGET tinyxml2::tinyxml2) if(TARGET tinyxml2) add_dependencies(tinyxml2::tinyxml2 tinyxml2) + else() + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + buildTinyXML2() + endif() endif() endif() From c4fde9deb8b262cd84a6fa50a37519b772c09971 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 20 Sep 2023 19:13:25 +1000 Subject: [PATCH 252/811] [darwin] Fix header inclusion for Xcode 15 Fix the following failure that occurs with Xcode 15 /xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm:300:22: error: property 'rightShoulder' cannot be found in forward class object 'GCExtendedGamepad' else if (gamepad.rightShoulder == element) ~~~~~~~ ^ /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.0.sdk/System/Library/Frameworks/GameController.framework/Headers/GCController.h:26:8: note: forward declaration of class here @class GCExtendedGamepad; --- xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm b/xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm index 3ec69d545e2c9..a4ba4be4a4752 100644 --- a/xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm +++ b/xbmc/platform/darwin/peripherals/Input_Gamecontroller.mm @@ -18,7 +18,7 @@ #include #import -#import +#import struct PlayerControllerState { From 4851719eea70b63f78c32560b90b996c5c3733a2 Mon Sep 17 00:00:00 2001 From: "Paul J. Ghosh" Date: Tue, 19 Sep 2023 20:09:09 -0500 Subject: [PATCH 253/811] * fix python XBMCAddon::xbmcgui::Window::onAction() action.getButtonCode() * changes to populate buttonCode for CAction such that it is available for python addons with custom controllers --- xbmc/input/InputManager.cpp | 2 +- xbmc/input/actions/Action.cpp | 5 +++-- xbmc/input/actions/Action.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/xbmc/input/InputManager.cpp b/xbmc/input/InputManager.cpp index 571b2599abf42..f02e46f003b69 100644 --- a/xbmc/input/InputManager.cpp +++ b/xbmc/input/InputManager.cpp @@ -230,7 +230,7 @@ bool CInputManager::ProcessEventServer(int windowId, float frameTime) CLog::Log(LOGDEBUG, "EventServer: key {} translated to action {}", wKeyID, actionName); - return ExecuteInputAction(CAction(actionID, fAmount, 0.0f, actionName)); + return ExecuteInputAction(CAction(actionID, fAmount, 0.0f, actionName, 0, wKeyID)); } else { diff --git a/xbmc/input/actions/Action.cpp b/xbmc/input/actions/Action.cpp index 8449e42029619..bc85cb26864b3 100644 --- a/xbmc/input/actions/Action.cpp +++ b/xbmc/input/actions/Action.cpp @@ -20,14 +20,15 @@ CAction::CAction(int actionID, float amount1 /* = 1.0f */, float amount2 /* = 0.0f */, const std::string& name /* = "" */, - unsigned int holdTime /*= 0*/) + unsigned int holdTime /*= 0*/, + unsigned int buttonCode /*= 0*/) : m_name(name) { m_id = actionID; m_amount[0] = amount1; m_amount[1] = amount2; m_repeat = 0; - m_buttonCode = 0; + m_buttonCode = buttonCode; m_unicode = 0; m_holdTime = holdTime; } diff --git a/xbmc/input/actions/Action.h b/xbmc/input/actions/Action.h index f1d417370a8be..dad49fa5b0196 100644 --- a/xbmc/input/actions/Action.h +++ b/xbmc/input/actions/Action.h @@ -27,7 +27,8 @@ class CAction float amount1 = 1.0f, float amount2 = 0.0f, const std::string& name = "", - unsigned int holdTime = 0); + unsigned int holdTime = 0, + unsigned int buttonCode = 0); CAction(int actionID, wchar_t unicode); CAction(int actionID, unsigned int state, From 23be39736e1bea71aa1d871679e1e4010e60a915 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 09:28:30 +1000 Subject: [PATCH 254/811] [cmake] gen_skin_pack remove BYPRODUCT to fix ninja build --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d5febb16d5c8..3aa86e4cbb4bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -443,7 +443,6 @@ add_custom_command( COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake ${XBT_SOURCE_FILELIST} - BYPRODUCTS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Generating skin xbt" ) From d84883c9eef1ef9130357d53f2290726e66bc9aa Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Thu, 21 Sep 2023 00:08:03 +0200 Subject: [PATCH 255/811] [depends] Bump zlib to 1.3 --- tools/depends/target/zlib/ZLIB-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/zlib/ZLIB-VERSION b/tools/depends/target/zlib/ZLIB-VERSION index e988128a41c06..393bbf332b2b2 100644 --- a/tools/depends/target/zlib/ZLIB-VERSION +++ b/tools/depends/target/zlib/ZLIB-VERSION @@ -1,5 +1,5 @@ LIBNAME=zlib -VERSION=1.2.13 +VERSION=1.3 ARCHIVE=$(LIBNAME)-$(VERSION).tar.xz -SHA512=9e7ac71a1824855ae526506883e439456b74ac0b811d54e94f6908249ba8719bec4c8d7672903c5280658b26cb6b5e93ecaaafe5cdc2980c760fa196773f0725 +SHA512=3868ac4da5842dd36c9dad794930675b9082ce15cbd099ddb79c0f6bd20a24aa8f33a123f378f26fe0ae02d91f31f2994dccaac565cedeaffed7b315e6ded2a2 BYPRODUCT=libz.a From 409f11341f3c681d83ee3cb2de2fa16ea8cce601 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 23 Sep 2023 10:43:45 +0200 Subject: [PATCH 256/811] [FileCache] Reduce CServiceBroker calls to obtain advancedsettings --- xbmc/filesystem/FileCache.cpp | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/xbmc/filesystem/FileCache.cpp b/xbmc/filesystem/FileCache.cpp index 26b5c978c21e8..3263f2294d25f 100644 --- a/xbmc/filesystem/FileCache.cpp +++ b/xbmc/filesystem/FileCache.cpp @@ -111,6 +111,12 @@ bool CFileCache::Open(const CURL& url) return false; } + const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if (!advancedSettings) + return false; + + const unsigned int cacheMemSize = advancedSettings->m_cacheMemSize; + m_source.IoControl(IOCTRL_SET_CACHE, this); bool retry = false; @@ -120,9 +126,8 @@ bool CFileCache::Open(const CURL& url) m_seekPossible = m_source.IoControl(IOCTRL_SEEK_POSSIBLE, NULL); // Determine the best chunk size we can use - m_chunkSize = CFile::DetermineChunkSize( - m_source.GetChunkSize(), - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheChunkSize); + m_chunkSize = + CFile::DetermineChunkSize(m_source.GetChunkSize(), advancedSettings->m_cacheChunkSize); CLog::Log(LOGDEBUG, "CFileCache::{} - <{}> source chunk size is {}, setting cache chunk size to {}", __FUNCTION__, m_sourcePath, m_source.GetChunkSize(), m_chunkSize); @@ -131,7 +136,7 @@ bool CFileCache::Open(const CURL& url) if (!m_pCache) { - if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize == 0) + if (cacheMemSize == 0) { // Use cache on disk m_pCache = std::unique_ptr(new CSimpleFileCache()); // C++14 - Replace with std::make_unique @@ -140,7 +145,7 @@ bool CFileCache::Open(const CURL& url) else { size_t cacheSize; - if (m_fileSize > 0 && m_fileSize < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize && !(m_flags & READ_AUDIO_VIDEO)) + if (m_fileSize > 0 && m_fileSize < cacheMemSize && !(m_flags & READ_AUDIO_VIDEO)) { // Cap cache size by filesize, but not for audio/video files as those may grow. // We don't need to take into account READ_MULTI_STREAM here as that's only used for audio/video @@ -152,7 +157,7 @@ bool CFileCache::Open(const CURL& url) } else { - cacheSize = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheMemSize; + cacheSize = cacheMemSize; // NOTE: READ_MULTI_STREAM is only used with READ_AUDIO_VIDEO if (m_flags & READ_MULTI_STREAM) @@ -227,6 +232,12 @@ void CFileCache::Process() return; } + const auto advancedSettings = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings(); + if (!advancedSettings) + return; + + const float readFactor = advancedSettings->m_cacheReadFactor; + CWriteRate limiter; CWriteRate average; @@ -279,13 +290,13 @@ void CFileCache::Process() while (m_writeRate) { - if (m_writePos - m_readPos < m_writeRate * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor) + if (m_writePos - m_readPos < m_writeRate * readFactor) { limiter.Reset(m_writePos); break; } - if (limiter.Rate(m_writePos) < m_writeRate * CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cacheReadFactor) + if (limiter.Rate(m_writePos) < m_writeRate * readFactor) break; if (m_seekEvent.Wait(100ms)) From 5f37f5b9df8b711650c18268cb97657c64294ec7 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 23 Sep 2023 11:05:28 +0200 Subject: [PATCH 257/811] [FileCache] Replace new with std::make_unique --- xbmc/filesystem/FileCache.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/xbmc/filesystem/FileCache.cpp b/xbmc/filesystem/FileCache.cpp index 3263f2294d25f..5d9598931aa1c 100644 --- a/xbmc/filesystem/FileCache.cpp +++ b/xbmc/filesystem/FileCache.cpp @@ -139,7 +139,7 @@ bool CFileCache::Open(const CURL& url) if (cacheMemSize == 0) { // Use cache on disk - m_pCache = std::unique_ptr(new CSimpleFileCache()); // C++14 - Replace with std::make_unique + m_pCache = std::make_unique(); m_forwardCacheSize = 0; } else @@ -181,14 +181,14 @@ bool CFileCache::Open(const CURL& url) const size_t back = cacheSize / 4; const size_t front = cacheSize - back; - m_pCache = std::unique_ptr(new CCircularCache(front, back)); // C++14 - Replace with std::make_unique + m_pCache = std::make_unique(front, back); m_forwardCacheSize = front; } if (m_flags & READ_MULTI_STREAM) { // If READ_MULTI_STREAM flag is set: Double buffering is required - m_pCache = std::unique_ptr(new CDoubleCache(m_pCache.release())); // C++14 - Replace with std::make_unique + m_pCache = std::make_unique(m_pCache.release()); } } From 7a4b9bda685ebe21c5dc97e7d9b0c818030f4d2d Mon Sep 17 00:00:00 2001 From: Matthias Reichl Date: Fri, 22 Sep 2023 23:41:51 +0200 Subject: [PATCH 258/811] keymaps: change remote poweroff action to show shutdown menu Signed-off-by: Matthias Reichl --- system/keymaps/remote.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/keymaps/remote.xml b/system/keymaps/remote.xml index c122b99188a67..baebf679c0173 100644 --- a/system/keymaps/remote.xml +++ b/system/keymaps/remote.xml @@ -50,7 +50,7 @@ VolumeUp VolumeDown Mute - ShutDown() + ActivateWindow(ShutdownMenu) ActivateWindow(Videos) ActivateWindow(Music) ActivateWindow(Pictures) From bc1e9d2be3d196d12eb9c3e64d1c592186ddd3d0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:01:09 +1000 Subject: [PATCH 259/811] [cmake] Multiconfig generators add stand alone target to build all internal depends --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa86e4cbb4bb..ff618fb7ed0dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,9 @@ endif() # Variable to indicate if the project is targeting a Multi Config Generator (VS/Xcode primarily) get_property(_multiconfig_generator GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(_multiconfig_generator) + add_custom_target(build_internal_depends) +endif() # Set CORE_BUILD_DIR set(CORE_BUILD_DIR build) From cddbf15a7d945e5b94153e898f7dc4a1679d4852 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:02:30 +1000 Subject: [PATCH 260/811] [cmake] FindCrossGUID add to multigen build_internal_depends target --- cmake/modules/FindCrossGUID.cmake | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 9f98b8a2c564d..610f0ebdfc8d0 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -94,18 +94,21 @@ if(NOT TARGET CrossGUID::CrossGUID) if(TARGET crossguid) add_dependencies(CrossGUID::CrossGUID crossguid) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET crossguid) buildCrossGUID() endif() + add_dependencies(build_internal_depends crossguid) endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP CrossGUID::CrossGUID) From 6df209062ddbae913422efdd6d1550d188c32ff2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:04:07 +1000 Subject: [PATCH 261/811] [cmake] FindFMT add to multiconfig gen build_internal_depends TARGET --- cmake/modules/FindFmt.cmake | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index a6b844532c6e6..b6fe3c1d48821 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -49,7 +49,7 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) # Check for existing FMT. If version >= FMT-VERSION file version, dont build find_package(FMT CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib + HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Build if ENABLE_INTERNAL_FMT, or if required version in find_package call is greater @@ -120,18 +120,21 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) if(TARGET fmt) add_dependencies(fmt::fmt fmt) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET fmt) buildFmt() endif() + add_dependencies(build_internal_depends fmt) endif() endif() From 9abf495f598abd9ea2fc8d263552695464544682 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:06:00 +1000 Subject: [PATCH 262/811] [cmake] FindNFS add to multigen build_internal_depends target --- cmake/modules/FindNFS.cmake | 45 +++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index c81f2e02ae95f..8ea9681c235cc 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -9,6 +9,19 @@ if(NOT TARGET libnfs::nfs) + macro(buildlibnfs) + set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF + -DENABLE_TESTS=OFF + -DENABLE_DOCUMENTATION=OFF + -DENABLE_UTILS=OFF + -DENABLE_EXAMPLES=OFF) + + BUILD_DEP_TARGET() + + set(_nfs_definitions HAS_NFS_SET_TIMEOUT + HAS_NFS_MOUNT_GETEXPORTS_TIMEOUT) + endmacro() + include(cmake/scripts/common/ModuleHelpers.cmake) set(MODULE_LC libnfs) @@ -17,24 +30,15 @@ if(NOT TARGET libnfs::nfs) # Search for cmake config. Suitable for all platforms including windows find_package(libnfs CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib + HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Check for existing LIBNFS. If version >= LIBNFS-VERSION file version, dont build # A corner case, but if a linux/freebsd user WANTS to build internal libnfs, build anyway if((libnfs_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_NFS) OR ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_NFS)) - - set(CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF - -DENABLE_TESTS=OFF - -DENABLE_DOCUMENTATION=OFF - -DENABLE_UTILS=OFF - -DENABLE_EXAMPLES=OFF) - - BUILD_DEP_TARGET() - - set(_nfs_definitions HAS_NFS_SET_TIMEOUT - HAS_NFS_MOUNT_GETEXPORTS_TIMEOUT) + # Build lib + buildlibnfs() else() if(NOT TARGET libnfs::nfs) # Try pkgconfig based search as last resort @@ -140,8 +144,21 @@ if(NOT TARGET libnfs::nfs) # Need to manually set this, as libnfs cmake config does not provide INTERFACE_INCLUDE_DIRECTORIES set_target_properties(libnfs::nfs PROPERTIES INTERFACE_INCLUDE_DIRECTORIES ${LIBNFS_INCLUDE_DIR}) - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libnfs::nfs) + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET libnfs) + buildlibnfs() + endif() + add_dependencies(build_internal_depends libnfs) + endif() - mark_as_advanced(LIBNFS_INCLUDE_DIR LIBNFS_LIBRARY) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP libnfs::nfs) endif() endif() From d3c492da82c28b03a7e208251fcda459aa89ef55 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:06:34 +1000 Subject: [PATCH 263/811] [cmake] FindPCRE add to multigen build_internal_depends target --- cmake/modules/FindPCRE.cmake | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index e650802184c5e..75843436e5296 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -67,7 +67,7 @@ if(NOT PCRE::pcre) # Check for existing PCRE. If version >= PCRE-VERSION file version, dont build find_package(PCRE CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib + HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) if((PCRE_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_PCRE) OR @@ -196,18 +196,21 @@ if(NOT PCRE::pcre) if(TARGET pcre) add_dependencies(PCRE::pcre pcre) add_dependencies(PCRE::pcrecpp pcre) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET pcre) buildPCRE() endif() + add_dependencies(build_internal_depends pcre) endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PCRE::pcre) From b1b267a6ef6416bf3910044080621255064c39d7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:07:40 +1000 Subject: [PATCH 264/811] [cmake] FindSpdlog add to multigen build_internal_depends target --- cmake/modules/FindSpdlog.cmake | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index 9ef855cbbe80b..40ec7e60ab90c 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -65,7 +65,7 @@ if(NOT TARGET spdlog::spdlog) # Check for existing SPDLOG. If version >= SPDLOG-VERSION file version, dont build find_package(SPDLOG CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib + HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_LC}_SEARCH_CONFIG}) if((SPDLOG_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_SPDLOG) OR @@ -164,18 +164,21 @@ if(NOT TARGET spdlog::spdlog) if(TARGET spdlog) add_dependencies(spdlog::spdlog spdlog) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET spdlog) buildSpdlog() endif() + add_dependencies(build_internal_depends spdlog) endif() endif() endif() From a10c47764b42de2b8434e134fe56b60edec9ac0e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:08:14 +1000 Subject: [PATCH 265/811] [cmake] FindTagLib add to multigen build_internal_depends target --- cmake/modules/FindTagLib.cmake | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 311d9e7ef8d58..158b7b2e4404c 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -112,20 +112,24 @@ if(NOT TARGET TagLib::TagLib) if(TARGET taglib) add_dependencies(TagLib::TagLib taglib) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET taglib) buildTagLib() endif() + add_dependencies(build_internal_depends taglib) endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP TagLib::TagLib) else() if(TagLib_FIND_REQUIRED) From 97672dd3f9c5f1c2573883888fc52bfa1448cd65 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 24 Sep 2023 13:09:05 +1000 Subject: [PATCH 266/811] [cmake] FindTinyXML2 add to multigen build_internal_depends target --- cmake/modules/FindTinyXML2.cmake | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index 709eeefabdf16..b3af281b62ce3 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -49,7 +49,7 @@ if(NOT TARGET tinyxml2::tinyxml2) SETUP_BUILD_VARS() find_package(TINYXML2 CONFIG QUIET - HINTS ${DEPENDS_PATH}/lib + HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) # Check for existing TINYXML2. If version >= TINYXML2-VERSION file version, dont build @@ -134,19 +134,23 @@ if(NOT TARGET tinyxml2::tinyxml2) if(TARGET tinyxml2) add_dependencies(tinyxml2::tinyxml2 tinyxml2) - else() - # Add internal build target when a Multi Config Generator is used - # We cant add a dependency based off a generator expression for targeted build types, - # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 - # therefore if the find heuristics only find the library, we add the internal build - # target to the project to allow user to manually trigger for any build type they need - # in case only a specific build type is actually available (eg Release found, Debug Required) - # This is mainly targeted for windows who required different runtime libs for different - # types, and they arent compatible - if(_multiconfig_generator) + endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET tinyxml2) buildTinyXML2() endif() + add_dependencies(build_internal_depends tinyxml2) endif() + endif() set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP tinyxml2::tinyxml2) From a5fe59cdbe46b491c9fb36623160053427ab1519 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:32:45 +1000 Subject: [PATCH 267/811] [cmake] FindCrossGUID add stricter search paths --- cmake/modules/FindCrossGUID.cmake | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 9f98b8a2c564d..5ba39e3cb40a1 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -38,17 +38,25 @@ if(NOT TARGET CrossGUID::CrossGUID) if(ENABLE_INTERNAL_CROSSGUID) buildCrossGUID() else() - if(PKG_CONFIG_FOUND) + find_package(PkgConfig) + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) pkg_check_modules(PC_CROSSGUID crossguid QUIET) set(CROSSGUID_VERSION ${PC_CROSSGUID_VERSION}) endif() find_path(CROSSGUID_INCLUDE_DIR NAMES crossguid/guid.hpp guid.h - HINTS ${DEPENDS_PATH}/include ${PC_CROSSGUID_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_CROSSGUID_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(CROSSGUID_LIBRARY_RELEASE NAMES crossguid - HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(CROSSGUID_LIBRARY_DEBUG NAMES crossguidd crossguid-dgb - HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_CROSSGUID_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) # NEW_CROSSGUID >= 0.2.0 release if(EXISTS "${CROSSGUID_INCLUDE_DIR}/crossguid/guid.hpp") From 574208b21069dbdab8c7c0c8f66f94c79d17476e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:46:57 +1000 Subject: [PATCH 268/811] [Eventclient] WiiRemote use Bluetooth::Bluetooth target --- tools/EventClients/Clients/WiiRemote/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/EventClients/Clients/WiiRemote/CMakeLists.txt b/tools/EventClients/Clients/WiiRemote/CMakeLists.txt index 8c797b2a92b30..0462fbe9c3527 100644 --- a/tools/EventClients/Clients/WiiRemote/CMakeLists.txt +++ b/tools/EventClients/Clients/WiiRemote/CMakeLists.txt @@ -11,12 +11,12 @@ set(HEADERS CWIID_WiiRemote.h) add_executable(${APP_NAME_LC}-wiiremote ${SOURCES} ${HEADERS}) target_include_directories(${APP_NAME_LC}-wiiremote - PRIVATE ${BLUETOOTH_INCLUDE_DIRS} + PRIVATE $ ${CWIID_INCLUDE_DIRS}) target_link_libraries(${APP_NAME_LC}-wiiremote PRIVATE ${SYSTEM_LDFLAGS} - ${BLUETOOTH_LIBRARIES} + Bluetooth::Bluetooth ${CWIID_LIBRARIES}) target_compile_options(${APP_NAME_LC}-wiiremote PRIVATE ${ARCH_DEFINES}) From 35ef8b175c5fe81e44c6028880e3e39cd600b0c7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 20:08:09 +1000 Subject: [PATCH 269/811] [cmake] unset *_LIBRARIES populated from select_library_configurations This will remove a lot of duplication due to the core_*_dep macros. With TARGET usage we dont need to append every library to a single variable. TARGETS will handle deduplication (to a certain point) --- cmake/modules/FindCrossGUID.cmake | 1 + cmake/modules/FindFmt.cmake | 1 + cmake/modules/FindPCRE.cmake | 2 ++ cmake/modules/FindSpdlog.cmake | 1 + cmake/modules/FindTagLib.cmake | 1 + cmake/modules/FindTinyXML2.cmake | 1 + 6 files changed, 7 insertions(+) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 5ba39e3cb40a1..a8331a2a75530 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -67,6 +67,7 @@ if(NOT TARGET CrossGUID::CrossGUID) # Select relevant lib build type (ie CROSSGUID_LIBRARY_RELEASE or CROSSGUID_LIBRARY_DEBUG) include(SelectLibraryConfigurations) select_library_configurations(CROSSGUID) + unset(CROSSGUID_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(CrossGUID diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index a6b844532c6e6..d75924c935448 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -162,6 +162,7 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) include(SelectLibraryConfigurations) select_library_configurations(FMT) + unset(FMT_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Fmt diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index e650802184c5e..8197a679483ab 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -147,6 +147,8 @@ if(NOT PCRE::pcre) include(SelectLibraryConfigurations) select_library_configurations(PCRECPP) select_library_configurations(PCRE) + unset(PCRECPP_LIBRARIES) + unset(PCRE_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PCRE diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index 9ef855cbbe80b..6fdd969f88474 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -129,6 +129,7 @@ if(NOT TARGET spdlog::spdlog) include(SelectLibraryConfigurations) select_library_configurations(SPDLOG) + unset(SPDLOG_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Spdlog diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 311d9e7ef8d58..cdfe87368209a 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -82,6 +82,7 @@ if(NOT TARGET TagLib::TagLib) include(SelectLibraryConfigurations) select_library_configurations(TAGLIB) + unset(TAGLIB_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(TagLib diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index 709eeefabdf16..1840a1e331f9b 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -109,6 +109,7 @@ if(NOT TARGET tinyxml2::tinyxml2) include(SelectLibraryConfigurations) select_library_configurations(TINYXML2) + unset(TINYXML2_LIBRARIES) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(TinyXML2 From 44db2e1640ea7f8039f28ec059b0b0ef2d6f3b48 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 15:28:51 +1000 Subject: [PATCH 270/811] [cmake] FindHarfBuzz update search paths and options --- cmake/modules/FindHarfBuzz.cmake | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/cmake/modules/FindHarfBuzz.cmake b/cmake/modules/FindHarfBuzz.cmake index 44b291c9b81b9..bef71c8f6c145 100644 --- a/cmake/modules/FindHarfBuzz.cmake +++ b/cmake/modules/FindHarfBuzz.cmake @@ -14,12 +14,14 @@ if(NOT TARGET HarfBuzz::HarfBuzz) endif() find_path(HARFBUZZ_INCLUDE_DIR NAMES harfbuzz/hb-ft.h hb-ft.h - HINTS ${PC_HARFBUZZ_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include + ${PC_HARFBUZZ_INCLUDEDIR} ${PC_HARFBUZZ_INCLUDE_DIRS} - PATH_SUFFIXES harfbuzz + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(HARFBUZZ_LIBRARY NAMES harfbuzz - HINTS ${PC_HARFBUZZ_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_HARFBUZZ_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) set(HARFBUZZ_VERSION ${PC_HARFBUZZ_VERSION}) From 4000df927488a9a3571575c9a31cb7c2c0f7d4f1 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 15:13:15 +1000 Subject: [PATCH 271/811] [cmake] FindGBM use HINTS instead of PATHS --- cmake/modules/FindGBM.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindGBM.cmake b/cmake/modules/FindGBM.cmake index f98a55cbc55f0..38b4174f854d0 100644 --- a/cmake/modules/FindGBM.cmake +++ b/cmake/modules/FindGBM.cmake @@ -13,10 +13,10 @@ if(NOT TARGET GBM::GBM) endif() find_path(GBM_INCLUDE_DIR NAMES gbm.h - PATHS ${PC_GBM_INCLUDEDIR} + HINTS ${PC_GBM_INCLUDEDIR} NO_CACHE) find_library(GBM_LIBRARY NAMES gbm - PATHS ${PC_GBM_LIBDIR} + HINTS ${PC_GBM_LIBDIR} NO_CACHE) set(GBM_VERSION ${PC_GBM_VERSION}) From 48267283963b379d874db9c8eb8a1c64c5563d1d Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 15:12:35 +1000 Subject: [PATCH 272/811] [cmake] FindFstrcmp update search options --- cmake/modules/FindFstrcmp.cmake | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cmake/modules/FindFstrcmp.cmake b/cmake/modules/FindFstrcmp.cmake index 2bf412c2ddb9e..fa754c5b8f35b 100644 --- a/cmake/modules/FindFstrcmp.cmake +++ b/cmake/modules/FindFstrcmp.cmake @@ -27,15 +27,20 @@ if(NOT TARGET fstrcmp::fstrcmp) BUILD_DEP_TARGET() else() + find_package(PkgConfig) if(PKG_CONFIG_FOUND) pkg_check_modules(PC_FSTRCMP fstrcmp QUIET) endif() find_path(FSTRCMP_INCLUDE_DIR NAMES fstrcmp.h - PATHS ${PC_FSTRCMP_INCLUDEDIR}) + HINTS ${DEPENDS_PATH}/include ${PC_FSTRCMP_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) find_library(FSTRCMP_LIBRARY NAMES fstrcmp - PATHS ${PC_FSTRCMP_LIBDIR}) + HINTS ${DEPENDS_PATH}/lib ${PC_FSTRCMP_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) set(FSTRCMP_VER ${PC_FSTRCMP_VERSION}) endif() @@ -56,5 +61,3 @@ if(NOT TARGET fstrcmp::fstrcmp) set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fstrcmp::fstrcmp) endif() - -mark_as_advanced(FSTRCMP_INCLUDE_DIR FSTRCMP_LIBRARY) From 09384f015c800e756a6d89353ad244fd383bd57e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 15:02:21 +1000 Subject: [PATCH 273/811] [cmake] FindFreeType update search paths for find_* --- cmake/modules/FindFreeType.cmake | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmake/modules/FindFreeType.cmake b/cmake/modules/FindFreeType.cmake index 3b73bc53f8ee1..f6bfcee7c4390 100644 --- a/cmake/modules/FindFreeType.cmake +++ b/cmake/modules/FindFreeType.cmake @@ -9,17 +9,21 @@ if(NOT TARGET FreeType::FreeType) find_package(PkgConfig) - if(PKG_CONFIG_FOUND) + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) pkg_check_modules(PC_FREETYPE freetype2 QUIET) endif() find_path(FREETYPE_INCLUDE_DIR NAMES freetype/freetype.h freetype.h - PATHS ${PC_FREETYPE_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include + ${PC_FREETYPE_INCLUDEDIR} ${PC_FREETYPE_INCLUDE_DIRS} PATH_SUFFIXES freetype2 + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(FREETYPE_LIBRARY NAMES freetype freetype246MT - PATHS ${PC_FREETYPE_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_FREETYPE_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) set(FREETYPE_VERSION ${PC_FREETYPE_VERSION}) From edc02018cc37b3b6459790c43e3c882ef7a1f8d3 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:55:09 +1000 Subject: [PATCH 274/811] [cmake] FindEGL use HINTS instead of PATHS --- cmake/modules/FindEGL.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindEGL.cmake b/cmake/modules/FindEGL.cmake index 7c048a24f3759..a8db654e12368 100644 --- a/cmake/modules/FindEGL.cmake +++ b/cmake/modules/FindEGL.cmake @@ -14,11 +14,11 @@ if(NOT TARGET EGL::EGL) endif() find_path(EGL_INCLUDE_DIR EGL/egl.h - PATHS ${PC_EGL_INCLUDEDIR} + HINTS ${PC_EGL_INCLUDEDIR} NO_CACHE) find_library(EGL_LIBRARY NAMES EGL egl - PATHS ${PC_EGL_LIBDIR} + HINTS ${PC_EGL_LIBDIR} NO_CACHE) set(EGL_VERSION ${PC_EGL_VERSION}) From 765ab9b2cfe0f03bf8cce5f0da37d42cbf34a4ec Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:53:24 +1000 Subject: [PATCH 275/811] [cmake] FindDetours add _SEARCH_CONFIG options to find_* calls --- cmake/modules/FindDetours.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmake/modules/FindDetours.cmake b/cmake/modules/FindDetours.cmake index 81cc0e9016028..e31bc8fb502f7 100644 --- a/cmake/modules/FindDetours.cmake +++ b/cmake/modules/FindDetours.cmake @@ -12,8 +12,10 @@ if(NOT TARGET windows::Detours) NO_CACHE) find_library(DETOURS_LIBRARY_RELEASE NAMES detours + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(DETOURS_LIBRARY_DEBUG NAMES detoursd + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) include(SelectLibraryConfigurations) From 525c2136ff40319f26b949fc8a4b05584d55e88c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:51:49 +1000 Subject: [PATCH 276/811] [cmake] FindDav1d add _SEARCH_CONFIG options to find_* calls --- cmake/modules/FindDav1d.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindDav1d.cmake b/cmake/modules/FindDav1d.cmake index f4312d27af839..8bfd4153814cf 100644 --- a/cmake/modules/FindDav1d.cmake +++ b/cmake/modules/FindDav1d.cmake @@ -35,16 +35,20 @@ if(NOT TARGET dav1d::dav1d) BUILD_DEP_TARGET() else() - if(PKG_CONFIG_FOUND) + find_package(PkgConfig) + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) pkg_check_modules(PC_DAV1D dav1d QUIET) endif() find_library(DAV1D_LIBRARY NAMES dav1d libdav1d HINTS ${DEPENDS_PATH}/lib ${PC_DAV1D_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_path(DAV1D_INCLUDE_DIR NAMES dav1d/dav1d.h HINTS ${DEPENDS_PATH}/include ${PC_DAV1D_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) set(DAV1D_VERSION ${PC_DAV1D_VERSION}) From d6be26cd21718ce1f2e85c193dec447220742b51 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:51:29 +1000 Subject: [PATCH 277/811] [cmake] FindDbus update HINTS/PATHS usage --- cmake/modules/FindDBus.cmake | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cmake/modules/FindDBus.cmake b/cmake/modules/FindDBus.cmake index cec6a6af8dfd9..fc2a68fcc38dc 100644 --- a/cmake/modules/FindDBus.cmake +++ b/cmake/modules/FindDBus.cmake @@ -15,13 +15,13 @@ if(NOT TARGET DBus::DBus) find_path(DBUS_INCLUDE_DIR NAMES dbus/dbus.h PATH_SUFFIXES dbus-1.0 - PATHS ${PC_DBUS_INCLUDE_DIR}) + HINTS ${PC_DBUS_INCLUDE_DIR}) find_path(DBUS_ARCH_INCLUDE_DIR NAMES dbus/dbus-arch-deps.h PATH_SUFFIXES dbus-1.0/include - PATHS ${PC_DBUS_LIBDIR} - /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}) + HINTS ${PC_DBUS_LIBDIR} + PATHS /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE}) find_library(DBUS_LIBRARY NAMES dbus-1 - PATHS ${PC_DBUS_LIBDIR}) + HINTS ${PC_DBUS_LIBDIR}) set(DBUS_VERSION ${PC_DBUS_VERSION}) @@ -31,7 +31,6 @@ if(NOT TARGET DBus::DBus) VERSION_VAR DBUS_VERSION) if(DBUS_FOUND) - add_library(DBus::DBus UNKNOWN IMPORTED) set_target_properties(DBus::DBus PROPERTIES IMPORTED_LOCATION "${DBUS_LIBRARY}" From 001210da3871f1a3638a2a5bcf08486dd50885fe Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 14:32:27 +1000 Subject: [PATCH 278/811] [cmake] FindCAP use HINTS instead of PATHS --- cmake/modules/FindCAP.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindCAP.cmake b/cmake/modules/FindCAP.cmake index 2f11161b45ff3..8cad7bab9f71f 100644 --- a/cmake/modules/FindCAP.cmake +++ b/cmake/modules/FindCAP.cmake @@ -14,10 +14,10 @@ if(NOT TARGET CAP::CAP) endif() find_path(CAP_INCLUDE_DIR NAMES sys/capability.h - PATHS ${PC_CAP_INCLUDEDIR} + HINTS ${PC_CAP_INCLUDEDIR} NO_CACHE) find_library(CAP_LIBRARY NAMES cap libcap - PATHS ${PC_CAP_LIBDIR} + HINTS ${PC_CAP_LIBDIR} NO_CACHE) set(CAP_VERSION ${PC_CAP_VERSION}) From fb69f530398ffbc52f61a20090711d39a2d7d7a2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 08:14:41 +1000 Subject: [PATCH 279/811] [cmake] FindBluetooth use HINTS instead of PATHS --- cmake/modules/FindBluetooth.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindBluetooth.cmake b/cmake/modules/FindBluetooth.cmake index 0ac691324eb34..70da7b1a4f83f 100644 --- a/cmake/modules/FindBluetooth.cmake +++ b/cmake/modules/FindBluetooth.cmake @@ -14,10 +14,10 @@ if(NOT TARGET Bluetooth::Bluetooth) endif() find_path(BLUETOOTH_INCLUDE_DIR NAMES bluetooth/bluetooth.h - PATHS ${PC_BLUETOOTH_INCLUDEDIR} + HINTS ${PC_BLUETOOTH_INCLUDEDIR} NO_CACHE) find_library(BLUETOOTH_LIBRARY NAMES bluetooth libbluetooth - PATHS ${PC_BLUETOOTH_LIBDIR} + HINTS ${PC_BLUETOOTH_LIBDIR} NO_CACHE) set(BLUETOOTH_VERSION ${PC_BLUETOOTH_VERSION}) From efb9b06f105f68743d398a99561e7d1a993bce71 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 08:14:20 +1000 Subject: [PATCH 280/811] [cmake] FindAvahi update search paths --- cmake/modules/FindAvahi.cmake | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cmake/modules/FindAvahi.cmake b/cmake/modules/FindAvahi.cmake index b79756840faff..6898217d14b15 100644 --- a/cmake/modules/FindAvahi.cmake +++ b/cmake/modules/FindAvahi.cmake @@ -15,16 +15,20 @@ if(NOT TARGET Avahi::Avahi) endif() find_path(AVAHI_CLIENT_INCLUDE_DIR NAMES avahi-client/client.h - PATHS ${PC_AVAHI_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include ${PC_AVAHI_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_path(AVAHI_COMMON_INCLUDE_DIR NAMES avahi-common/defs.h - PATHS ${PC_AVAHI_INCLUDEDIR} + HINTS ${DEPENDS_PATH}/include ${PC_AVAHI_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(AVAHI_CLIENT_LIBRARY NAMES avahi-client - PATHS ${PC_AVAHI_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_AVAHI_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) find_library(AVAHI_COMMON_LIBRARY NAMES avahi-common - PATHS ${PC_AVAHI_LIBDIR} + HINTS ${DEPENDS_PATH}/lib ${PC_AVAHI_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) set(AVAHI_VERSION ${PC_AVAHI_VERSION}) From 070bda87f9cba92c07dd6f7d43b25a0580988390 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 08:13:04 +1000 Subject: [PATCH 281/811] [cmake] FindAlsa update to HINTS search paths over PATHS --- cmake/modules/FindAlsa.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindAlsa.cmake b/cmake/modules/FindAlsa.cmake index 178e23033d78f..786ec924bb10b 100644 --- a/cmake/modules/FindAlsa.cmake +++ b/cmake/modules/FindAlsa.cmake @@ -14,10 +14,10 @@ if(NOT TARGET ALSA::ALSA) endif() find_path(ALSA_INCLUDE_DIR NAMES alsa/asoundlib.h - PATHS ${PC_ALSA_INCLUDEDIR} + HINTS ${PC_ALSA_INCLUDEDIR} NO_CACHE) find_library(ALSA_LIBRARY NAMES asound - PATHS ${PC_ALSA_LIBDIR} + HINTS ${PC_ALSA_LIBDIR} NO_CACHE) set(ALSA_VERSION ${PC_ALSA_VERSION}) From 25575f6548e9fdfc5bc0ef0639d5401041f2aa08 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 16 Sep 2023 23:15:05 +0200 Subject: [PATCH 282/811] [video] Video info dialog: Reinit cast list on re-open of dialog after cancelling resume playback dialog. --- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index 14d718f79359e..cfc53c08f74c7 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -751,6 +751,7 @@ void CGUIDialogVideoInfo::Play(bool resume) else if (!CGUIWindowVideoBase::ShowResumeMenu(*m_movieItem)) { // The Resume dialog was closed without any choice + SetMovie(m_movieItem.get()); // restore cast list, which was cleared on GUI_MSG_WINDOW_DEINIT Open(); return; } From 5c9afe3990ab481825d36f1ed63af418ff3d2d82 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 23 Sep 2023 22:21:04 +0200 Subject: [PATCH 283/811] [listproviders] Add attribute 'browse' to give skinners the possibility to control the 'more' button for a list. --- addons/skin.estuary/xml/Home.xml | 5 +++ addons/skin.estuary/xml/Includes_Home.xml | 15 +++++--- xbmc/listproviders/DirectoryProvider.cpp | 45 ++++++++++++++++++++--- xbmc/listproviders/DirectoryProvider.h | 12 +++++- 4 files changed, 66 insertions(+), 11 deletions(-) diff --git a/addons/skin.estuary/xml/Home.xml b/addons/skin.estuary/xml/Home.xml index cf896750007a5..66eab452da400 100644 --- a/addons/skin.estuary/xml/Home.xml +++ b/addons/skin.estuary/xml/Home.xml @@ -80,6 +80,7 @@ + @@ -214,6 +215,7 @@ + @@ -223,6 +225,7 @@ + @@ -739,6 +742,7 @@ + @@ -756,6 +760,7 @@ + diff --git a/addons/skin.estuary/xml/Includes_Home.xml b/addons/skin.estuary/xml/Includes_Home.xml index 7924a46f0ecca..753e1840828f7 100644 --- a/addons/skin.estuary/xml/Includes_Home.xml +++ b/addons/skin.estuary/xml/Includes_Home.xml @@ -27,6 +27,7 @@ false + auto $PARAM[widget_header] @@ -71,7 +72,7 @@ - $PARAM[content_path] + $PARAM[content_path] @@ -154,6 +155,7 @@ True ascending 15 + auto $PARAM[widget_header] @@ -204,7 +206,7 @@ - $PARAM[content_path] + $PARAM[content_path] @@ -216,6 +218,7 @@ 15 DefaultAudio.png false + auto $PARAM[widget_header] @@ -267,7 +270,7 @@ - $PARAM[content_path] + $PARAM[content_path] @@ -278,6 +281,7 @@ false false true + auto $PARAM[widget_header] @@ -374,7 +378,7 @@ - $PARAM[content_path] + $PARAM[content_path] @@ -412,6 +416,7 @@ $INFO[ListItem.Label] $INFO[ListItem.Title] 0 + auto $PARAM[widget_header] @@ -616,7 +621,7 @@ - $PARAM[content_path] + $PARAM[content_path] diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 76135d4ac7452..0062f82054942 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -55,8 +55,14 @@ class CDirectoryJob : public CJob const std::string& target, SortDescription sort, int limit, + CDirectoryProvider::BrowseMode browse, int parentID) - : m_url(url), m_target(target), m_sort(sort), m_limit(limit), m_parentID(parentID) + : m_url(url), + m_target(target), + m_sort(sort), + m_limit(limit), + m_browse(browse), + m_parentID(parentID) { } ~CDirectoryJob() override = default; @@ -102,9 +108,10 @@ class CDirectoryJob : public CJob if (items.HasProperty("node.target")) m_target = items.GetProperty("node.target").asString(); - if (limit < items.Size()) + if ((m_browse == CDirectoryProvider::BrowseMode::ALWAYS && !items.IsEmpty()) || + (m_browse == CDirectoryProvider::BrowseMode::AUTO && limit < items.Size())) { - // Add a special item to the end of the limited list, which can be used to open the + // Add a special item to the end of the list, which can be used to open the // full listing containg all items in the given target window. if (!m_target.empty()) { @@ -174,6 +181,7 @@ class CDirectoryJob : public CJob std::string m_target; SortDescription m_sort; unsigned int m_limit; + CDirectoryProvider::BrowseMode m_browse{CDirectoryProvider::BrowseMode::AUTO}; int m_parentID; std::vector m_items; std::map > m_thumbloaders; @@ -201,6 +209,10 @@ CDirectoryProvider::CDirectoryProvider(const TiXmlElement* element, int parentID if (limit) m_limit.SetLabel(limit, "", parentID); + const char* browse = element->Attribute("browse"); + if (browse) + m_browse.SetLabel(browse, "", parentID); + m_url.SetLabel(element->FirstChild()->ValueStr(), "", parentID); } } @@ -213,10 +225,12 @@ CDirectoryProvider::CDirectoryProvider(const CDirectoryProvider& other) m_sortMethod(other.m_sortMethod), m_sortOrder(other.m_sortOrder), m_limit(other.m_limit), + m_browse(other.m_browse), m_currentUrl(other.m_currentUrl), m_currentTarget(other.m_currentTarget), m_currentSort(other.m_currentSort), - m_currentLimit(other.m_currentLimit) + m_currentLimit(other.m_currentLimit), + m_currentBrowse(other.m_currentBrowse) { } @@ -240,6 +254,7 @@ bool CDirectoryProvider::Update(bool forceRefresh) fireJob |= UpdateURL(); fireJob |= UpdateSort(); fireJob |= UpdateLimit(); + fireJob |= UpdateBrowse(); fireJob &= !m_currentUrl.empty(); std::unique_lock lock(m_section); @@ -257,7 +272,7 @@ bool CDirectoryProvider::Update(bool forceRefresh) CServiceBroker::GetJobManager()->CancelJob(m_jobID); m_jobID = CServiceBroker::GetJobManager()->AddJob( new CDirectoryJob(m_currentUrl, m_target.GetLabel(m_parentID, false), m_currentSort, - m_currentLimit, m_parentID), + m_currentLimit, m_currentBrowse, m_parentID), this); } @@ -387,6 +402,7 @@ void CDirectoryProvider::Reset() m_currentSort.sortBy = SortByNone; m_currentSort.sortOrder = SortOrderAscending; m_currentLimit = 0; + m_currentBrowse = BrowseMode::AUTO; m_updateState = OK; } @@ -588,6 +604,25 @@ bool CDirectoryProvider::UpdateLimit() return true; } +bool CDirectoryProvider::UpdateBrowse() +{ + std::unique_lock lock(m_section); + const std::string stringValue{m_browse.GetLabel(m_parentID, false)}; + BrowseMode value{m_currentBrowse}; + if (StringUtils::EqualsNoCase(stringValue, "always")) + value = BrowseMode::ALWAYS; + else if (StringUtils::EqualsNoCase(stringValue, "auto")) + value = BrowseMode::AUTO; + else if (StringUtils::EqualsNoCase(stringValue, "never")) + value = BrowseMode::NEVER; + + if (value == m_currentBrowse) + return false; + + m_currentBrowse = value; + return true; +} + bool CDirectoryProvider::UpdateSort() { std::unique_lock lock(m_section); diff --git a/xbmc/listproviders/DirectoryProvider.h b/xbmc/listproviders/DirectoryProvider.h index c32a0f189e845..9a17aaae7720a 100644 --- a/xbmc/listproviders/DirectoryProvider.h +++ b/xbmc/listproviders/DirectoryProvider.h @@ -50,6 +50,13 @@ class CDirectoryProvider : DONE } UpdateState; + enum class BrowseMode + { + NEVER, + AUTO, // add browse item if list is longer than given limit + ALWAYS + }; + CDirectoryProvider(const TiXmlElement *element, int parentID); explicit CDirectoryProvider(const CDirectoryProvider& other); ~CDirectoryProvider() override; @@ -80,10 +87,12 @@ class CDirectoryProvider : KODI::GUILIB::GUIINFO::CGUIInfoLabel m_sortMethod; KODI::GUILIB::GUIINFO::CGUIInfoLabel m_sortOrder; KODI::GUILIB::GUIINFO::CGUIInfoLabel m_limit; + KODI::GUILIB::GUIINFO::CGUIInfoLabel m_browse; std::string m_currentUrl; std::string m_currentTarget; ///< \brief node.target property on the list as a whole SortDescription m_currentSort; - unsigned int m_currentLimit = 0; + unsigned int m_currentLimit{0}; + BrowseMode m_currentBrowse{BrowseMode::AUTO}; std::vector m_items; std::vector m_itemTypes; mutable CCriticalSection m_section; @@ -91,6 +100,7 @@ class CDirectoryProvider : bool UpdateURL(); bool UpdateLimit(); bool UpdateSort(); + bool UpdateBrowse(); void OnAddonEvent(const ADDON::AddonEvent& event); void OnAddonRepositoryEvent(const ADDON::CRepositoryUpdater::RepositoryUpdated& event); void OnPVRManagerEvent(const PVR::PVREvent& event); From 20859083a07601c9cbbb57cf75634bf97572fd02 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Mon, 25 Sep 2023 00:11:42 +0200 Subject: [PATCH 284/811] [webOS] RendererStarfish: Derive from CBaseRenderer The starfish video renderer is not based on GLES at all. The only reason for deriving from the GLES renderer was the setup of the render area. Rendering of the video is done automatically by the pipeline and all it needs is the region the video should be rendered to. --- .../HwDecRender/RendererStarfish.cpp | 74 ++++++++++++++----- .../HwDecRender/RendererStarfish.h | 20 ++--- 2 files changed, 67 insertions(+), 27 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp index d3b10e195ac1f..d268b6ca816ba 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp @@ -37,7 +37,34 @@ bool CRendererStarfish::Configure(const VideoPicture& picture, float fps, unsign GetFlagsColorPrimaries(picture.color_primaries) | GetFlagsStereoMode(picture.stereoMode); - return CLinuxRendererGLES::Configure(picture, fps, orientation); + m_format = picture.videoBuffer->GetFormat(); + m_sourceWidth = picture.iWidth; + m_sourceHeight = picture.iHeight; + m_renderOrientation = orientation; + + // Calculate the input frame aspect ratio. + CalculateFrameAspectRatio(picture.iDisplayWidth, picture.iDisplayHeight); + SetViewMode(m_videoSettings.m_ViewMode); + ManageRenderArea(); + + m_configured = true; + + return true; +} + +bool CRendererStarfish::IsConfigured() +{ + return m_configured; +} + +bool CRendererStarfish::ConfigChanged(const VideoPicture& picture) +{ + if (picture.videoBuffer->GetFormat() != m_format) + { + return true; + } + + return false; } bool CRendererStarfish::Register() @@ -71,6 +98,16 @@ bool CRendererStarfish::Supports(ERENDERFEATURE feature) const feature == RENDERFEATURE_ROTATION); } +bool CRendererStarfish::Supports(ESCALINGMETHOD method) const +{ + return false; +} + +bool CRendererStarfish::SupportsMultiPassRendering() +{ + return false; +} + void CRendererStarfish::AddVideoPicture(const VideoPicture& picture, int index) { } @@ -86,35 +123,36 @@ CRenderInfo CRendererStarfish::GetRenderInfo() return info; } -bool CRendererStarfish::LoadShadersHook() -{ - return true; -} - -bool CRendererStarfish::RenderHook(int index) +bool CRendererStarfish::IsGuiLayer() { - return true; + return false; } -bool CRendererStarfish::CreateTexture(int index) +bool CRendererStarfish::RenderCapture(CRenderCapture* capture) { - return true; + return false; } -void CRendererStarfish::DeleteTexture(int index) +void CRendererStarfish::UnInit() { + m_configured = false; } -bool CRendererStarfish::UploadTexture(int index) +void CRendererStarfish::Update() { - return true; -} + if (!m_configured) + { + return; + } -bool CRendererStarfish::IsGuiLayer() -{ - return false; + ManageRenderArea(); } -void CRendererStarfish::RenderUpdateVideo(bool clear, unsigned int flags, unsigned int alpha) +void CRendererStarfish::RenderUpdate( + int index, int index2, bool clear, unsigned int flags, unsigned int alpha) { + if (!m_configured) + { + return; + } } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h index 36139303c4045..ca52c57209ffc 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h @@ -10,7 +10,7 @@ #include "cores/VideoPlayer/VideoRenderers/LinuxRendererGLES.h" -class CRendererStarfish : public CLinuxRendererGLES +class CRendererStarfish : public CBaseRenderer { public: CRendererStarfish(); @@ -28,22 +28,24 @@ class CRendererStarfish : public CLinuxRendererGLES CRenderInfo GetRenderInfo() override; bool IsGuiLayer() override; + bool IsConfigured() override; bool Configure(const VideoPicture& picture, float fps, unsigned int orientation) override; bool Supports(ERENDERFEATURE feature) const override; + bool Supports(ESCALINGMETHOD method) const override; + bool SupportsMultiPassRendering() override; + void UnInit() override; + void Update() override; + void RenderUpdate( + int index, int index2, bool clear, unsigned int flags, unsigned int alpha) override; + bool RenderCapture(CRenderCapture* capture) override; + bool ConfigChanged(const VideoPicture& picture) override; protected: - // textures - bool UploadTexture(int index) override; - void RenderUpdateVideo(bool clear, unsigned int flags, unsigned int alpha) override; - void DeleteTexture(int index) override; - bool CreateTexture(int index) override; - // hooks for hw dec renderer - bool LoadShadersHook() override; - bool RenderHook(int index) override; void ManageRenderArea() override; private: CRect m_exportedSourceRect; CRect m_exportedDestRect; + bool m_configured{false}; }; From 2fadc908db675fc5d557378e8f404056d725c779 Mon Sep 17 00:00:00 2001 From: throwaway96 <68320646+throwaway96@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:38:48 -0400 Subject: [PATCH 285/811] [webOS] tune for Cortex-A53 This commit changes architecture CFLAGS on webOS from `-march=armv7-a -mtune=cortex-a9 -mfpu=neon` to `-mcpu=cortex-a9 -mtune=cortex-a53` (`-mcpu=cortex-a9` implies `-mfpu=neon-fp16`). This maintains compatibility with all webOS TVs while optimizing for a newer and more common core. `-mfloat-abi=softfp` should be kept for ABI compatibility with system libraries. --- tools/depends/configure.ac | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index b1bd2f5e56d2e..957a9e89c9d82 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -368,7 +368,7 @@ case $host in use_cpu="armeabi-v7a" fi use_toolchain="${use_toolchain:-/usr}" - platform_cflags="-fPIC -DPIC -march=armv7-a -mtune=cortex-a9 -mfloat-abi=softfp -mfpu=neon" + platform_cflags="-fPIC -DPIC -mcpu=cortex-a9 -mtune=cortex-a53 -mfloat-abi=softfp" platform_cxxflags="$platform_cflags -static-libstdc++" optimize_flags="-Os" platform_ldflags="-Wl,-rpath-link=$prefix/$deps_dir/lib" From 8cbd67856f099282dd57c30214df411ea39c5292 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Sep 2023 10:34:47 +1000 Subject: [PATCH 286/811] [cmake] add_custom_command only use OUTPUT genex for multi config generators --- CMakeLists.txt | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa86e4cbb4bb..8eb81cbd8f7a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,20 +433,27 @@ add_custom_target(gen_system_addons -P ${CMAKE_SOURCE_DIR}/cmake/scripts/common/GenerateSystemAddons.cmake WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +if(_multiconfig_generator) + # Generator expressions in add_custom_command(OUTPUT) are only available in cmake 3.20+ + # we dont really need config aware locations for a single config generator, so we only + # set this for multi config generators who all use newer cmake + set(CONFIG_VAR $) +endif() + # Pack skins and copy to correct build dir (MultiConfig Generator aware) add_custom_command( - OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp + OUTPUT ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${CONFIG_VAR}/gen_skin.timestamp COMMAND ${CMAKE_COMMAND} -DBUNDLEDIR=${_bundle_dir} -DTEXTUREPACKER_EXECUTABLE=$ -P ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake - COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$ - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp + COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${CONFIG_VAR} + COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${CONFIG_VAR}/gen_skin.timestamp DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/GeneratedPackSkins.cmake ${XBT_SOURCE_FILELIST} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMENT "Generating skin xbt" ) -add_custom_target(gen_skin_pack DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/$/gen_skin.timestamp) +add_custom_target(gen_skin_pack DEPENDS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/${CONFIG_VAR}/gen_skin.timestamp) # Packaging target. This generates system addon, xbt creation, copy files to build tree add_custom_target(generate-packaging ALL From 96b35aafd8b66e8054553972b825fa4290a90d0f Mon Sep 17 00:00:00 2001 From: throwaway96 <68320646+throwaway96@users.noreply.github.com> Date: Tue, 26 Sep 2023 02:31:45 -0400 Subject: [PATCH 287/811] fix native/waylandpp-scanner build with GCC 13 As of GCC 13, no longer transitively includes , so it must be explicitly included. This is just a copy of the patch for target/waylandpp. --- .../001-fix-gcc13-build.patch | 28 +++++++++++++++++++ .../depends/native/waylandpp-scanner/Makefile | 3 +- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 tools/depends/native/waylandpp-scanner/001-fix-gcc13-build.patch diff --git a/tools/depends/native/waylandpp-scanner/001-fix-gcc13-build.patch b/tools/depends/native/waylandpp-scanner/001-fix-gcc13-build.patch new file mode 100644 index 0000000000000..c440c1141eb41 --- /dev/null +++ b/tools/depends/native/waylandpp-scanner/001-fix-gcc13-build.patch @@ -0,0 +1,28 @@ +--- a/include/wayland-client.hpp ++++ b/include/wayland-client.hpp +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + #include + #include + +--- a/scanner/scanner.cpp ++++ b/scanner/scanner.cpp +@@ -24,6 +24,7 @@ + #include + #include + #include ++#include + + #include "pugixml.hpp" + +@@ -928,6 +929,7 @@ + << "#include " << std::endl + << "#include " << std::endl + << "#include " << std::endl ++ << "#include " << std::endl + << std::endl + << "#include " << std::endl; diff --git a/tools/depends/native/waylandpp-scanner/Makefile b/tools/depends/native/waylandpp-scanner/Makefile index 5b28efaa9e939..ccfc8caa9182f 100644 --- a/tools/depends/native/waylandpp-scanner/Makefile +++ b/tools/depends/native/waylandpp-scanner/Makefile @@ -1,7 +1,7 @@ include ../../Makefile.include PREFIX=$(NATIVEPREFIX) PLATFORM=$(NATIVEPLATFORM) -DEPS =../../Makefile.include Makefile ../../download-files.include +DEPS =../../Makefile.include Makefile ../../download-files.include 001-fix-gcc13-build.patch # lib name, version LIBNAME=waylandpp @@ -27,6 +27,7 @@ all: .installed-$(PLATFORM) $(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + cd $(PLATFORM); patch -p1 -i ../001-fix-gcc13-build.patch mkdir -p $(BUILDDIR) cd $(BUILDDIR); $(NATIVEPREFIX)/bin/cmake $(CMAKE_OPTIONS) .. From 07d64d593877c8cd7827111f42c2eb7d9e36d8a6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Sep 2023 16:47:33 +1000 Subject: [PATCH 288/811] [tools/depends][target] Toolchain fix search path issues for cross compile projects /usr was causing cmake to potentially search host system folders on linux jenkins projects we dont want to add this unless its some non default path provided from configure.ac and the dev --- tools/depends/target/Toolchain.cmake.in | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/depends/target/Toolchain.cmake.in b/tools/depends/target/Toolchain.cmake.in index 8122832a3ee81..a1d6b548a13e4 100644 --- a/tools/depends/target/Toolchain.cmake.in +++ b/tools/depends/target/Toolchain.cmake.in @@ -81,13 +81,18 @@ endif() # where is the target environment set(CMAKE_FIND_ROOT_PATH @prefix@/@deps_dir@) set(CMAKE_LIBRARY_PATH @prefix@/@deps_dir@/lib) -if(NOT "@use_toolchain@" STREQUAL "") - list(APPEND CMAKE_FIND_ROOT_PATH @use_toolchain@ @use_toolchain@/@use_host@ @use_toolchain@/@use_host@/sysroot @use_toolchain@/@use_host@/sysroot/usr @use_toolchain@/@use_host@/libc @use_toolchain@/lib/@use_host@/sysroot @use_toolchain@/usr @use_toolchain@/sysroot/usr) - set(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH}:@use_toolchain@/usr/lib/@use_host@:@use_toolchain@/lib/@use_host@") -endif() if(NOT "@use_sdk_path@" STREQUAL "") list(APPEND CMAKE_FIND_ROOT_PATH @use_sdk_path@ @use_sdk_path@/usr) endif() +# Currently this is only set to reject android by default +if(NOT "@use_toolchain@" STREQUAL "") + list(APPEND CMAKE_FIND_ROOT_PATH @use_toolchain@/@use_host@ @use_toolchain@/@use_host@/sysroot @use_toolchain@/@use_host@/sysroot/usr @use_toolchain@/@use_host@/libc @use_toolchain@/lib/@use_host@/sysroot @use_toolchain@/usr @use_toolchain@/sysroot/usr) + # Explicitly set this as last. This potentially is /usr which can then cause linux + # cross compilation to search paths that are not relevant to target arch (eg host libs) + # x86/x86_64 jenkins CI jobs require /usr for libs like iconv + list(APPEND CMAKE_FIND_ROOT_PATH @use_toolchain@) + set(CMAKE_LIBRARY_PATH "${CMAKE_LIBRARY_PATH}:@use_toolchain@/usr/lib/@use_host@:@use_toolchain@/lib/@use_host@") +endif() # add Android directories and tools if(CORE_SYSTEM_NAME STREQUAL android) From 7d756de4be033918cd9e3e7a4060107231a51bcc Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 21 Sep 2023 08:11:01 +1000 Subject: [PATCH 289/811] [cmake] FindAss update search paths/TARGET usage --- cmake/modules/FindASS.cmake | 100 ++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/cmake/modules/FindASS.cmake b/cmake/modules/FindASS.cmake index ca471f19a3562..8dd36bdc3b8f6 100644 --- a/cmake/modules/FindASS.cmake +++ b/cmake/modules/FindASS.cmake @@ -3,67 +3,55 @@ # ------- # Finds the ASS library # -# This will define the following variables:: -# -# ASS_FOUND - system has ASS -# ASS_INCLUDE_DIRS - the ASS include directory -# ASS_LIBRARIES - the ASS libraries -# -# and the following imported targets:: +# This will define the following target: # # ASS::ASS - The ASS library +# -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_ASS libass QUIET) +if(NOT TARGET ASS::ASS) + find_package(PkgConfig) + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) + pkg_check_modules(PC_ASS libass QUIET IMPORTED_TARGET) + + # INTERFACE_LINK_OPTIONS is incorrectly populated when cmake generation is executed + # when an existing build generation is already done. Just set this to blank + set_target_properties(PkgConfig::PC_ASS PROPERTIES INTERFACE_LINK_OPTIONS "") + + set(ASS_VERSION ${PC_ASS_VERSION}) + elseif(WIN32) + find_package(libass CONFIG QUIET REQUIRED + HINTS ${DEPENDS_PATH}/lib/cmake + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + set(ASS_VERSION ${libass_VERSION}) + endif() - if(KODI_DEPENDSBUILD) - # Darwin platforms have lib options like -framework CoreServices. pkgconfig return of - # PC_ASS_LDFLAGS splits this into a list -framework;CoreServices, and when passed to linker - # This then treats them as individual flags and appends -l to CoreServices. eg -framework;-lCoreServices - # This causes failures, as -lCoreServices isnt a lib that can be found. - # This just formats the list data to append frameworks (eg "-framework CoreServices") - if(PC_ASS_LDFLAGS AND "-framework" IN_LIST PC_ASS_LDFLAGS) - set(_framework_command OFF) - foreach(flag ${PC_ASS_LDFLAGS}) - if(flag STREQUAL "-framework") - set(_framework_command ON) - continue() - elseif(_framework_command) - list(APPEND ASS_LDFLAGS "-framework ${flag}") - set(_framework_command OFF) - else() - list(APPEND ASS_LDFLAGS ${flag}) - endif() - endforeach() - unset(_framework_command) + find_path(ASS_INCLUDE_DIR NAMES ass/ass.h + HINTS ${DEPENDS_PATH}/include ${PC_ASS_INCLUDEDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) + find_library(ASS_LIBRARY NAMES ass libass + HINTS ${DEPENDS_PATH}/lib ${PC_ASS_LIBDIR} + ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} + NO_CACHE) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(ASS + REQUIRED_VARS ASS_LIBRARY ASS_INCLUDE_DIR + VERSION_VAR ASS_VERSION) + + if(ASS_FOUND) + if(TARGET PkgConfig::PC_ASS) + add_library(ASS::ASS ALIAS PkgConfig::PC_ASS) + elseif(TARGET libass::libass) + # Kodi custom libass target used for windows platforms + add_library(ASS::ASS ALIAS libass::libass) else() - set(ASS_LDFLAGS ${PC_ASS_LDFLAGS}) + add_library(ASS::ASS UNKNOWN IMPORTED) + set_target_properties(ASS::ASS PROPERTIES + IMPORTED_LOCATION "${ASS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ASS_INCLUDE_DIR}") endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ASS::ASS) endif() endif() - -find_path(ASS_INCLUDE_DIR NAMES ass/ass.h - PATHS ${PC_ASS_INCLUDEDIR}) -find_library(ASS_LIBRARY NAMES ass libass - PATHS ${PC_ASS_LIBDIR}) - -set(ASS_VERSION ${PC_ASS_VERSION}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(ASS - REQUIRED_VARS ASS_LIBRARY ASS_INCLUDE_DIR - VERSION_VAR ASS_VERSION) - -if(ASS_FOUND) - set(ASS_LIBRARIES ${ASS_LIBRARY} ${ASS_LDFLAGS}) - set(ASS_INCLUDE_DIRS ${ASS_INCLUDE_DIR}) - - if(NOT TARGET ASS::ASS) - add_library(ASS::ASS UNKNOWN IMPORTED) - set_target_properties(ASS::ASS PROPERTIES - IMPORTED_LOCATION "${ASS_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ASS_INCLUDE_DIR}") - endif() -endif() - -mark_as_advanced(ASS_INCLUDE_DIR ASS_LIBRARY) From 40c6830800eb29d3c4cd3c06022bea4653c32fe2 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 26 Sep 2023 10:51:20 +1000 Subject: [PATCH 290/811] [cmake] libass set minimum search version to 0.15.0 A hard requirement of libass 0.15.0 was brought in with PR https://github.com/xbmc/xbmc/pull/23421 Force version check on libass find --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3aa86e4cbb4bb..7b2bce848acb3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -175,7 +175,7 @@ core_optional_dep(${optional_buildtools}) core_require_dep(${required_buildtools}) # Required dependencies. Keep in alphabetical order please -set(required_deps ASS +set(required_deps ASS>=0.15.0 Cdio CrossGUID Curl From 548ab280e61680f6429e19ad8821040bd41d7484 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Wed, 27 Sep 2023 00:03:49 +0200 Subject: [PATCH 291/811] [webOS] Don't assume the RUNPATH to be available Currently, the RUNPATH is assumed to be available on the deployment device. Generally, this is not true and breaks with when compiling with a more recent buildroot-nc4 toolchain --- tools/webOS/verify-symbols.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tools/webOS/verify-symbols.sh b/tools/webOS/verify-symbols.sh index 6123e09a49a94..095570bb4735f 100755 --- a/tools/webOS/verify-symbols.sh +++ b/tools/webOS/verify-symbols.sh @@ -14,8 +14,7 @@ if [ ! -d "${WEBOS_ROOTFS}" ]; then exit 1 fi -lib_search_paths="$(objdump -p ${EXE} | grep RUNPATH | tr -s ' ' | cut -d ' ' -f 3)" -lib_search_paths="${lib_search_paths}:${WEBOS_ROOTFS}/lib:${WEBOS_ROOTFS}/usr/lib:${WEBOS_LD_LIBRARY_PATH}" +lib_search_paths="${WEBOS_ROOTFS}/lib:${WEBOS_ROOTFS}/usr/lib:${WEBOS_LD_LIBRARY_PATH}" required_syms=$(nm --dynamic --extern-only --undefined-only "${EXE}" | grep ' [U] ' | tr -s ' ' | cut -d ' ' -f 3) From d6ad14304628e9e3949b3bc06307341581a234da Mon Sep 17 00:00:00 2001 From: fuzzard Date: Wed, 27 Sep 2023 14:17:18 +1000 Subject: [PATCH 292/811] [tools/depends][native] gettext force am_cv_func_iconv_works for apple host MacOS Sonoma (14.0) has a change in iconv that the gettext configure script fails when testing if iconv works. We just force it on --- tools/depends/native/gettext/Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tools/depends/native/gettext/Makefile b/tools/depends/native/gettext/Makefile index 5a8fee12205f7..f78aceab1f84b 100644 --- a/tools/depends/native/gettext/Makefile +++ b/tools/depends/native/gettext/Makefile @@ -22,6 +22,14 @@ CONFIGURE=./configure --prefix=$(PREFIX) \ --without-git --without-cvs \ --disable-shared --disable-curses --disable-acl --disable-c++ --disable-nls +ifeq ($(NATIVE_OS), osx) + # As per homebrew - https://github.com/Homebrew/homebrew-core/blob/f6df737d9479dd215185000a3dbd641185eafec2/Formula/g/gettext.rb#L52C1-L55 + # Sonoma iconv() has a regression w.r.t. transliteration, which happens to + # break gettext's configure check. Force it. + # Reported to Apple as FB13163914 + CONFIGURE+= am_cv_func_iconv_works=y +endif + LIBDYLIB=$(PLATFORM)/gettext-tools/src/.libs/libgettextsrc.a all: .installed-$(PLATFORM) From 9a4def05674ef3e21a6a9fab1fc0db4df27a82df Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 27 Sep 2023 09:31:29 +0200 Subject: [PATCH 293/811] [Android] Neon is being enabled by default --- cmake/scripts/android/ArchSetup.cmake | 1 - tools/depends/configure.ac | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cmake/scripts/android/ArchSetup.cmake b/cmake/scripts/android/ArchSetup.cmake index 49ba396194866..d76ce93a169fa 100644 --- a/cmake/scripts/android/ArchSetup.cmake +++ b/cmake/scripts/android/ArchSetup.cmake @@ -17,7 +17,6 @@ else() if(CPU STREQUAL armeabi-v7a) set(ARCH arm) set(NEON True) - set(NEON_FLAGS "-mfpu=neon") elseif(CPU STREQUAL arm64-v8a) set(ARCH aarch64) set(NEON True) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index b1bd2f5e56d2e..78f0e9ba27c4f 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -323,7 +323,7 @@ case $host in use_cpu="armeabi-v7a" fi if test "x$use_cpu" = "xarmeabi-v7a"; then - platform_cflags="${platform_cflags} -march=armv7-a -mtune=cortex-a9 -mfloat-abi=softfp -mfpu=neon" + platform_cflags="${platform_cflags} -march=armv7-a -mtune=cortex-a9 -mfloat-abi=softfp" fi platform_ldflags="${platform_ldflags} -Wl,--exclude-libs,libunwind.a" meson_cpu="arm" From b2c8bf3631a63bd41d4153aa245e186e26b2a0f6 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Tue, 5 Sep 2023 17:59:10 +1000 Subject: [PATCH 294/811] [cmake] FindIconv migrate to full TARGET usage --- cmake/modules/FindIconv.cmake | 52 ++++++++++++++++------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake index ae7d8d7f57897..67230c897b3d3 100644 --- a/cmake/modules/FindIconv.cmake +++ b/cmake/modules/FindIconv.cmake @@ -3,42 +3,36 @@ # -------- # Finds the ICONV library # -# This will define the following variables:: +# This will define the following target: # -# ICONV_FOUND - system has ICONV -# ICONV_INCLUDE_DIRS - the ICONV include directory -# ICONV_LIBRARIES - the ICONV libraries -# -# and the following imported targets:: -# -# ICONV::ICONV - The ICONV library - -find_path(ICONV_INCLUDE_DIR NAMES iconv.h) +# ICONV::ICONV - The ICONV library -find_library(ICONV_LIBRARY NAMES iconv libiconv c) +if(NOT TARGET ICONV::ICONV) + find_path(ICONV_INCLUDE_DIR NAMES iconv.h + HINTS ${DEPENDS_PATH}/include + NO_CACHE) -set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY}) -check_function_exists(iconv HAVE_ICONV_FUNCTION) -if(NOT HAVE_ICONV_FUNCTION) - check_function_exists(libiconv HAVE_LIBICONV_FUNCTION2) - set(HAVE_ICONV_FUNCTION ${HAVE_LIBICONV_FUNCTION2}) - unset(HAVE_LIBICONV_FUNCTION2) -endif() + find_library(ICONV_LIBRARY NAMES iconv libiconv c + HINTS ${DEPENDS_PATH}/lib + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Iconv - REQUIRED_VARS ICONV_LIBRARY ICONV_INCLUDE_DIR HAVE_ICONV_FUNCTION) + set(CMAKE_REQUIRED_LIBRARIES ${ICONV_LIBRARY}) + check_function_exists(iconv HAVE_ICONV_FUNCTION) + if(NOT HAVE_ICONV_FUNCTION) + check_function_exists(libiconv HAVE_LIBICONV_FUNCTION2) + set(HAVE_ICONV_FUNCTION ${HAVE_LIBICONV_FUNCTION2}) + unset(HAVE_LIBICONV_FUNCTION2) + endif() -if(ICONV_FOUND) - set(ICONV_LIBRARIES ${ICONV_LIBRARY}) - set(ICONV_INCLUDE_DIRS ${ICONV_INCLUDE_DIR}) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Iconv + REQUIRED_VARS ICONV_LIBRARY ICONV_INCLUDE_DIR HAVE_ICONV_FUNCTION) - if(NOT TARGET ICONV::ICONV) + if(ICONV_FOUND) add_library(ICONV::ICONV UNKNOWN IMPORTED) set_target_properties(ICONV::ICONV PROPERTIES - IMPORTED_LOCATION "${ICONV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ICONV_INCLUDE_DIR}") + IMPORTED_LOCATION "${ICONV_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ICONV_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ICONV::ICONV) endif() endif() - -mark_as_advanced(ICONV_INCLUDE_DIR ICONV_LIBRARY HAVE_ICONV_FUNCTION) From 0dddec4199edcf750bbef5dc0933e0721efdb75b Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 15:19:01 +1000 Subject: [PATCH 295/811] [cmake] FindOpenGLES move to full TARGET usage unify find_* calls for all platforms Actually allow using/finding GLESv3 headers for darwin_embedded platforms --- cmake/modules/FindOpenGLES.cmake | 84 +++++++++++-------- cmake/scripts/linux/Install.cmake | 4 +- xbmc/cores/RetroPlayer/buffers/CMakeLists.txt | 2 +- .../rendering/VideoRenderers/CMakeLists.txt | 2 +- .../VideoPlayer/VideoRenderers/CMakeLists.txt | 14 ++-- .../VideoRenderers/HwDecRender/CMakeLists.txt | 6 +- .../VideoShaders/CMakeLists.txt | 14 ++-- xbmc/guilib/CMakeLists.txt | 4 +- xbmc/pictures/CMakeLists.txt | 2 +- xbmc/rendering/CMakeLists.txt | 2 +- xbmc/rendering/gles/CMakeLists.txt | 2 +- xbmc/system_gl.h | 8 +- xbmc/utils/CMakeLists.txt | 2 +- xbmc/windowing/X11/CMakeLists.txt | 2 +- xbmc/windowing/android/CMakeLists.txt | 2 +- xbmc/windowing/gbm/CMakeLists.txt | 2 +- xbmc/windowing/wayland/CMakeLists.txt | 2 +- 17 files changed, 86 insertions(+), 68 deletions(-) diff --git a/cmake/modules/FindOpenGLES.cmake b/cmake/modules/FindOpenGLES.cmake index 3dbaa447fa2aa..49bad7027ff70 100644 --- a/cmake/modules/FindOpenGLES.cmake +++ b/cmake/modules/FindOpenGLES.cmake @@ -1,50 +1,62 @@ #.rst: # FindOpenGLES # ------------ -# Finds the OpenGLES2 library +# Finds the OpenGLES library # -# This will define the following variables:: +# This will define the following target: # -# OPENGLES_FOUND - system has OpenGLES -# OPENGLES_INCLUDE_DIRS - the OpenGLES include directory -# OPENGLES_LIBRARIES - the OpenGLES libraries -# OPENGLES_DEFINITIONS - the OpenGLES definitions +# OpenGL::GLES - The OpenGLES IMPORTED library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_OPENGLES glesv2 QUIET) -endif() +if(NOT TARGET OpenGL::GLES) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_OPENGLES glesv2 QUIET) + endif() -if(NOT CORE_SYSTEM_NAME STREQUAL darwin_embedded) - find_path(OPENGLES_INCLUDE_DIR GLES2/gl2.h - PATHS ${PC_OPENGLES_INCLUDEDIR}) - find_library(OPENGLES_gl_LIBRARY NAMES GLESv2 - PATHS ${PC_OPENGLES_LIBDIR}) -else() - find_library(OPENGLES_gl_LIBRARY NAMES OpenGLES - PATHS ${CMAKE_OSX_SYSROOT}/System/Library + find_library(OPENGLES_gl_LIBRARY NAMES GLESv2 OpenGLES + HINTS ${PC_OPENGLES_LIBDIR} ${CMAKE_OSX_SYSROOT}/System/Library PATH_SUFFIXES Frameworks - NO_DEFAULT_PATH) - set(OPENGLES_INCLUDE_DIR ${OPENGLES_gl_LIBRARY}/Headers) -endif() + NO_CACHE) + find_path(OPENGLES_INCLUDE_DIR NAMES GLES2/gl2.h ES2/gl.h + HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers + NO_CACHE) + find_path(OPENGLES3_INCLUDE_DIR NAMES GLES3/gl3.h ES3/gl.h + HINTS ${PC_OPENGLES_INCLUDEDIR} ${OPENGLES_gl_LIBRARY}/Headers + NO_CACHE) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(OpenGLES + REQUIRED_VARS OPENGLES_gl_LIBRARY OPENGLES_INCLUDE_DIR) -find_path(OPENGLES3_INCLUDE_DIR GLES3/gl3.h) + if(OPENGLES_FOUND) + if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) + # Cmake only added support for Frameworks as the IMPORTED_LOCATION as of 3.28 + # https://gitlab.kitware.com/cmake/cmake/-/merge_requests/8586 + # Until we move to cmake 3.28 as minimum, explicitly set to binary inside framework + if(OPENGLES_gl_LIBRARY MATCHES "/([^/]+)\\.framework$") + set(_gles_fw "${OPENGLES_gl_LIBRARY}/${CMAKE_MATCH_1}") + if(EXISTS "${_gles_fw}.tbd") + string(APPEND _gles_fw ".tbd") + endif() + set(OPENGLES_gl_LIBRARY ${_gles_fw}) + endif() + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OpenGLES - REQUIRED_VARS OPENGLES_gl_LIBRARY OPENGLES_INCLUDE_DIR) + add_library(OpenGL::GLES UNKNOWN IMPORTED) + set_target_properties(OpenGL::GLES PROPERTIES + IMPORTED_LOCATION "${OPENGLES_gl_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES_INCLUDE_DIR}") -find_path(OPENGLES3_INCLUDE_DIR GLES3/gl3.h - PATHS ${PC_OPENGLES_INCLUDEDIR}) + if(OPENGLES3_INCLUDE_DIR) + set_property(TARGET OpenGL::GLES APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES3_INCLUDE_DIR}") + set_target_properties(OpenGL::GLES PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_GLES=3) + else() + set_target_properties(OpenGL::GLES PROPERTIES + INTERFACE_COMPILE_DEFINITIONS HAS_GLES=2) + endif() -if(OPENGLES_FOUND) - set(OPENGLES_LIBRARIES ${OPENGLES_gl_LIBRARY}) - if(OPENGLES3_INCLUDE_DIR) - set(OPENGLES_INCLUDE_DIRS ${OPENGLES_INCLUDE_DIR} ${OPENGLES3_INCLUDE_DIR}) - set(OPENGLES_DEFINITIONS -DHAS_GLES=3) - mark_as_advanced(OPENGLES_INCLUDE_DIR OPENGLES3_INCLUDE_DIR OPENGLES_gl_LIBRARY) - else() - set(OPENGLES_INCLUDE_DIRS ${OPENGLES_INCLUDE_DIR}) - set(OPENGLES_DEFINITIONS -DHAS_GLES=2) - mark_as_advanced(OPENGLES_INCLUDE_DIR OPENGLES_gl_LIBRARY) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP OpenGL::GLES) endif() endif() diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 1564083b7cbe8..85d728c635d9a 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -3,12 +3,12 @@ if(X_FOUND) else() set(USE_X11 0) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GLES) set(USE_OPENGL 1) else() set(USE_OPENGL 0) endif() -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) set(USE_OPENGLES 1) else() set(USE_OPENGLES 0) diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt index c2702248158f4..bafd939e25094 100644 --- a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS BaseRenderBuffer.h RenderBufferManager.h ) -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES RenderBufferOpenGLES.cpp RenderBufferPoolOpenGLES.cpp) list(APPEND HEADERS RenderBufferOpenGLES.h diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt index ce9553852f305..345dd3fbd6e8e 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt @@ -8,7 +8,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows) list(APPEND HEADERS RPWinRenderer.h) endif() -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES RPRendererOpenGLES.cpp) list(APPEND HEADERS RPRendererOpenGLES.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt index 540b1e56dcaa3..b1e8b0ca4f4f1 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt @@ -29,7 +29,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) RenderCapture.h) endif() -if (OPENGL_FOUND OR OPENGLES_FOUND) +if (OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES FrameBufferObject.cpp) list(APPEND HEADERS FrameBufferObject.h) endif() @@ -43,12 +43,12 @@ if(OPENGL_FOUND) RenderCaptureGL.h) endif() -if(OPENGLES_FOUND AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR - "ios" IN_LIST CORE_PLATFORM_NAME_LC OR - "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR - "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR - "x11" IN_LIST CORE_PLATFORM_NAME_LC OR - "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) +if(TARGET OpenGL::GLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR + "ios" IN_LIST CORE_PLATFORM_NAME_LC OR + "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR + "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR + "x11" IN_LIST CORE_PLATFORM_NAME_LC OR + "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) list(APPEND SOURCES LinuxRendererGLES.cpp OverlayRendererGLES.cpp RenderCaptureGLES.cpp) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index e4da9252674e9..a35765c93c8de 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -10,7 +10,7 @@ if(VAAPI_FOUND) list(APPEND SOURCES RendererVAAPIGL.cpp) list(APPEND HEADERS RendererVAAPIGL.h) endif() - if(OPENGLES_FOUND) + if(TARGET OpenGL::GLES) list(APPEND SOURCES RendererVAAPIGLES.cpp) list(APPEND HEADERS RendererVAAPIGLES.h) endif() @@ -35,7 +35,7 @@ if(CORE_SYSTEM_NAME STREQUAL osx) endif() if(CORE_SYSTEM_NAME STREQUAL darwin_embedded) - if(OPENGLES_FOUND) + if(TARGET OpenGL::GLES) list(APPEND SOURCES RendererVTBGLES.cpp) list(APPEND HEADERS RendererVTBGLES.h) endif() @@ -56,7 +56,7 @@ if("gbm" IN_LIST CORE_PLATFORM_NAME_LC OR "wayland" IN_LIST CORE_PLATFORM_NAME_L VideoLayerBridgeDRMPRIME.h) endif() - if(OPENGLES_FOUND) + if(TARGET OpenGL::GLES) list(APPEND SOURCES RendererDRMPRIMEGLES.cpp DRMPRIMEEGL.cpp) list(APPEND HEADERS RendererDRMPRIMEGLES.h diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt index 6b71389043a05..52382483c1857 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt @@ -12,7 +12,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES ConversionMatrix.cpp) list(APPEND HEADERS ConversionMatrix.h) endif() @@ -26,12 +26,12 @@ if(OPENGL_FOUND) YUV2RGBShaderGL.h) endif() -if(OPENGLES_FOUND AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR - "ios" IN_LIST CORE_PLATFORM_NAME_LC OR - "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR - "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR - "x11" IN_LIST CORE_PLATFORM_NAME_LC OR - "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) +if(TARGET OpenGL::GLES AND ("android" IN_LIST CORE_PLATFORM_NAME_LC OR + "ios" IN_LIST CORE_PLATFORM_NAME_LC OR + "tvos" IN_LIST CORE_PLATFORM_NAME_LC OR + "gbm" IN_LIST CORE_PLATFORM_NAME_LC OR + "x11" IN_LIST CORE_PLATFORM_NAME_LC OR + "wayland" IN_LIST CORE_PLATFORM_NAME_LC)) list(APPEND SOURCES VideoFilterShaderGLES.cpp YUV2RGBShaderGLES.cpp) list(APPEND HEADERS VideoFilterShaderGLES.h diff --git a/xbmc/guilib/CMakeLists.txt b/xbmc/guilib/CMakeLists.txt index 51561cad64c3a..aa06c55a0b180 100644 --- a/xbmc/guilib/CMakeLists.txt +++ b/xbmc/guilib/CMakeLists.txt @@ -157,7 +157,7 @@ set(HEADERS DDSImage.h XBTF.h XBTFReader.h) -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES Shader.cpp TextureGL.cpp) list(APPEND HEADERS Shader.h @@ -170,7 +170,7 @@ if(OPENGL_FOUND OR OPENGLES_FOUND) GUITextureGL.h) endif() - if(OPENGLES_FOUND) + if(TARGET OpenGL::GLES) list(APPEND SOURCES GUIFontTTFGLES.cpp GUITextureGLES.cpp) list(APPEND HEADERS GUIFontTTFGLES.h diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index fe4601aa07f9e..efaf8160f5b41 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -31,7 +31,7 @@ if(OPENGL_FOUND) list(APPEND HEADERS SlideShowPictureGL.h) endif() -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) list(APPEND SOURCES SlideShowPictureGLES.cpp) list(APPEND HEADERS SlideShowPictureGLES.h) endif() diff --git a/xbmc/rendering/CMakeLists.txt b/xbmc/rendering/CMakeLists.txt index c212a9623e75e..02fcceb1cc6e1 100644 --- a/xbmc/rendering/CMakeLists.txt +++ b/xbmc/rendering/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES RenderSystem.cpp) set(HEADERS RenderSystem.h RenderSystemTypes.h) -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES MatrixGL.cpp) list(APPEND HEADERS MatrixGL.h) diff --git a/xbmc/rendering/gles/CMakeLists.txt b/xbmc/rendering/gles/CMakeLists.txt index 74cfe63ee5ac0..9276974875c1f 100644 --- a/xbmc/rendering/gles/CMakeLists.txt +++ b/xbmc/rendering/gles/CMakeLists.txt @@ -1,4 +1,4 @@ -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) set(SOURCES RenderSystemGLES.cpp ScreenshotSurfaceGLES.cpp GLESShader.cpp) diff --git a/xbmc/system_gl.h b/xbmc/system_gl.h index 04c4d55c660b5..ff7ead6b429d3 100644 --- a/xbmc/system_gl.h +++ b/xbmc/system_gl.h @@ -25,13 +25,19 @@ #endif #elif HAS_GLES >= 2 #if defined(TARGET_DARWIN) +// ios/tvos GLES3 headers include GLES2 definitions, so we can only include one. +#if HAS_GLES == 3 +#include +#include +#else #include #include +#endif #else #include #include -#endif #if HAS_GLES == 3 #include #endif #endif +#endif diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index fd9f4ab44f6fd..8bc8ddef11d73 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -201,7 +201,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) endif() -if(OPENGL_FOUND OR OPENGLES_FOUND) +if(OPENGL_FOUND OR TARGET OpenGL::GLES) list(APPEND SOURCES GLUtils.cpp) list(APPEND HEADERS GLUtils.h) endif() diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt index 91e13d6a410ac..88714b6a7d589 100644 --- a/xbmc/windowing/X11/CMakeLists.txt +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -29,7 +29,7 @@ if(OPENGL_FOUND) list(APPEND SOURCES VideoSyncOML.cpp) list(APPEND HEADERS VideoSyncOML.h) endif() -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) list(APPEND SOURCES WinSystemX11GLESContext.cpp) list(APPEND HEADERS WinSystemX11GLESContext.h) endif() diff --git a/xbmc/windowing/android/CMakeLists.txt b/xbmc/windowing/android/CMakeLists.txt index fd489d15875f1..c9462c0419dd7 100644 --- a/xbmc/windowing/android/CMakeLists.txt +++ b/xbmc/windowing/android/CMakeLists.txt @@ -10,7 +10,7 @@ set(HEADERS OSScreenSaverAndroid.h AndroidUtils.h VideoSyncAndroid.h) -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) list(APPEND SOURCES WinSystemAndroidGLESContext.cpp) list(APPEND HEADERS WinSystemAndroidGLESContext.h) endif() diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt index 254b2db5b6165..508618ae0e65f 100644 --- a/xbmc/windowing/gbm/CMakeLists.txt +++ b/xbmc/windowing/gbm/CMakeLists.txt @@ -18,7 +18,7 @@ if (OPENGL_FOUND) list(APPEND SOURCES WinSystemGbmGLContext.cpp) list(APPEND HEADERS WinSystemGbmGLContext.h) endif() -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) list(APPEND SOURCES WinSystemGbmGLESContext.cpp) list(APPEND HEADERS WinSystemGbmGLESContext.h) endif() diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt index 8d3e01518b11e..fc432d5cf5f81 100644 --- a/xbmc/windowing/wayland/CMakeLists.txt +++ b/xbmc/windowing/wayland/CMakeLists.txt @@ -58,7 +58,7 @@ if(OPENGL_FOUND) list(APPEND SOURCES WinSystemWaylandEGLContextGL.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGL.h) endif() -if(OPENGLES_FOUND) +if(TARGET OpenGL::GLES) list(APPEND SOURCES WinSystemWaylandEGLContextGLES.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGLES.h) endif() From 6c05fdeb469e5218427c3f6cd3a7699f8f76a2ac Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 15:40:43 +1000 Subject: [PATCH 296/811] [cmake] FindOpenGL migrate to full TARGET usage --- cmake/modules/FindOpenGl.cmake | 65 +++++++++++-------- xbmc/cores/RetroPlayer/buffers/CMakeLists.txt | 4 +- .../rendering/VideoRenderers/CMakeLists.txt | 4 +- .../VideoPlayer/VideoRenderers/CMakeLists.txt | 4 +- .../VideoRenderers/HwDecRender/CMakeLists.txt | 4 +- .../VideoShaders/CMakeLists.txt | 4 +- xbmc/guilib/CMakeLists.txt | 4 +- xbmc/pictures/CMakeLists.txt | 2 +- xbmc/rendering/CMakeLists.txt | 2 +- xbmc/rendering/gl/CMakeLists.txt | 2 +- xbmc/utils/CMakeLists.txt | 2 +- xbmc/video/dialogs/CMakeLists.txt | 2 +- xbmc/windowing/X11/CMakeLists.txt | 2 +- xbmc/windowing/gbm/CMakeLists.txt | 2 +- xbmc/windowing/osx/OpenGL/CMakeLists.txt | 2 +- xbmc/windowing/wayland/CMakeLists.txt | 2 +- 16 files changed, 58 insertions(+), 49 deletions(-) diff --git a/cmake/modules/FindOpenGl.cmake b/cmake/modules/FindOpenGl.cmake index e155cda5203a0..cabee46db3f92 100644 --- a/cmake/modules/FindOpenGl.cmake +++ b/cmake/modules/FindOpenGl.cmake @@ -3,38 +3,47 @@ # ---------- # Finds the FindOpenGl library # -# This will define the following variables:: +# This will define the following target: # -# OPENGL_FOUND - system has OpenGl -# OPENGL_INCLUDE_DIRS - the OpenGl include directory -# OPENGL_LIBRARIES - the OpenGl libraries -# OPENGL_DEFINITIONS - the OpenGl definitions +# OpenGL::GL - The OpenGL library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_OPENGL gl QUIET) -endif() +if(NOT TARGET OpenGL::GL) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_OPENGL gl QUIET) + endif() -if(NOT CORE_SYSTEM_NAME STREQUAL osx) - find_path(OPENGL_INCLUDE_DIR GL/gl.h - PATHS ${PC_OPENGL_gl_INCLUDEDIR}) - find_library(OPENGL_gl_LIBRARY NAMES GL - PATHS ${PC_OPENGL_gl_LIBDIR}) -else() - find_library(OPENGL_gl_LIBRARY NAMES OpenGL - PATHS ${CMAKE_OSX_SYSROOT}/System/Library + find_library(OPENGL_gl_LIBRARY NAMES GL OpenGL + HINTS ${PC_OPENGL_gl_LIBDIR} ${CMAKE_OSX_SYSROOT}/System/Library PATH_SUFFIXES Frameworks - NO_DEFAULT_PATH) - set(OPENGL_INCLUDE_DIR ${OPENGL_gl_LIBRARY}/Headers) -endif() + NO_CACHE) + find_path(OPENGL_INCLUDE_DIR NAMES GL/gl.h gl.h + HINTS ${PC_OPENGL_gl_INCLUDEDIR} ${OPENGL_gl_LIBRARY}/Headers + NO_CACHE) -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(OpenGl - REQUIRED_VARS OPENGL_gl_LIBRARY OPENGL_INCLUDE_DIR) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(OpenGl + REQUIRED_VARS OPENGL_gl_LIBRARY OPENGL_INCLUDE_DIR) -if(OPENGL_FOUND) - set(OPENGL_INCLUDE_DIRS ${OPENGL_INCLUDE_DIR}) - set(OPENGL_LIBRARIES ${OPENGL_gl_LIBRARY}) - set(OPENGL_DEFINITIONS -DHAS_GL=1) -endif() + if(OPENGL_FOUND) + if(CORE_SYSTEM_NAME STREQUAL osx) + # Cmake only added support for Frameworks as the IMPORTED_LOCATION as of 3.28 + # https://gitlab.kitware.com/cmake/cmake/-/merge_requests/8586 + # Until we move to cmake 3.28 as minimum, explicitly set to binary inside framework + if(OPENGL_gl_LIBRARY MATCHES "/([^/]+)\\.framework$") + set(_gl_fw "${OPENGL_gl_LIBRARY}/${CMAKE_MATCH_1}") + if(EXISTS "${_gl_fw}.tbd") + string(APPEND _gl_fw ".tbd") + endif() + set(OPENGL_gl_LIBRARY ${_gl_fw}) + endif() + endif() -mark_as_advanced(OPENGL_INCLUDE_DIR OPENGL_gl_LIBRARY) + add_library(OpenGL::GL UNKNOWN IMPORTED) + set_target_properties(OpenGL::GL PROPERTIES + IMPORTED_LOCATION "${OPENGL_gl_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${OPENGL_INCLUDE_DIR}" + INTERFACE_COMPILE_DEFINITIONS HAS_GL=1) + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP OpenGL::GL) + endif() +endif() diff --git a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt index bafd939e25094..91413f7527efc 100644 --- a/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/buffers/CMakeLists.txt @@ -10,14 +10,14 @@ set(HEADERS BaseRenderBuffer.h RenderBufferManager.h ) -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES RenderBufferOpenGLES.cpp RenderBufferPoolOpenGLES.cpp) list(APPEND HEADERS RenderBufferOpenGLES.h RenderBufferPoolOpenGLES.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES RenderBufferOpenGL.cpp RenderBufferPoolOpenGL.cpp) list(APPEND HEADERS RenderBufferOpenGL.h diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt index 345dd3fbd6e8e..4a2a65c7e2fa0 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/CMakeLists.txt @@ -8,12 +8,12 @@ if(CORE_SYSTEM_NAME STREQUAL windows) list(APPEND HEADERS RPWinRenderer.h) endif() -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES RPRendererOpenGLES.cpp) list(APPEND HEADERS RPRendererOpenGLES.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES RPRendererOpenGL.cpp) list(APPEND HEADERS RPRendererOpenGL.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt index b1e8b0ca4f4f1..bc367887afe3c 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/CMakeLists.txt @@ -29,12 +29,12 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) RenderCapture.h) endif() -if (OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES FrameBufferObject.cpp) list(APPEND HEADERS FrameBufferObject.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES LinuxRendererGL.cpp OverlayRendererGL.cpp RenderCaptureGL.cpp) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt index a35765c93c8de..8598052b418f7 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/CMakeLists.txt @@ -6,7 +6,7 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() if(VAAPI_FOUND) - if(OPENGL_FOUND) + if(TARGET OpenGL::GL) list(APPEND SOURCES RendererVAAPIGL.cpp) list(APPEND HEADERS RendererVAAPIGL.h) endif() @@ -28,7 +28,7 @@ if(TARGET VDPAU::VDPAU) endif() if(CORE_SYSTEM_NAME STREQUAL osx) - if(OPENGL_FOUND) + if(TARGET OpenGL::GL) list(APPEND SOURCES RendererVTBGL.cpp) list(APPEND HEADERS RendererVTBGL.h) endif() diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt index 52382483c1857..76f18f0f20b5d 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/CMakeLists.txt @@ -12,12 +12,12 @@ if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) endif() -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES ConversionMatrix.cpp) list(APPEND HEADERS ConversionMatrix.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES GLSLOutput.cpp VideoFilterShaderGL.cpp YUV2RGBShaderGL.cpp) diff --git a/xbmc/guilib/CMakeLists.txt b/xbmc/guilib/CMakeLists.txt index aa06c55a0b180..3e936cd9b9b82 100644 --- a/xbmc/guilib/CMakeLists.txt +++ b/xbmc/guilib/CMakeLists.txt @@ -157,13 +157,13 @@ set(HEADERS DDSImage.h XBTF.h XBTFReader.h) -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES Shader.cpp TextureGL.cpp) list(APPEND HEADERS Shader.h TextureGL.h) - if(OPENGL_FOUND) + if(TARGET OpenGL::GL) list(APPEND SOURCES GUIFontTTFGL.cpp GUITextureGL.cpp) list(APPEND HEADERS GUIFontTTFGL.h diff --git a/xbmc/pictures/CMakeLists.txt b/xbmc/pictures/CMakeLists.txt index efaf8160f5b41..7097942d6085b 100644 --- a/xbmc/pictures/CMakeLists.txt +++ b/xbmc/pictures/CMakeLists.txt @@ -26,7 +26,7 @@ set(HEADERS GUIDialogPictureInfo.h PictureThumbLoader.h SlideShowPicture.h) -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES SlideShowPictureGL.cpp) list(APPEND HEADERS SlideShowPictureGL.h) endif() diff --git a/xbmc/rendering/CMakeLists.txt b/xbmc/rendering/CMakeLists.txt index 02fcceb1cc6e1..9daaf0c726254 100644 --- a/xbmc/rendering/CMakeLists.txt +++ b/xbmc/rendering/CMakeLists.txt @@ -3,7 +3,7 @@ set(SOURCES RenderSystem.cpp) set(HEADERS RenderSystem.h RenderSystemTypes.h) -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES MatrixGL.cpp) list(APPEND HEADERS MatrixGL.h) diff --git a/xbmc/rendering/gl/CMakeLists.txt b/xbmc/rendering/gl/CMakeLists.txt index 6aadafa23bf6f..71c1ee8d322a6 100644 --- a/xbmc/rendering/gl/CMakeLists.txt +++ b/xbmc/rendering/gl/CMakeLists.txt @@ -1,4 +1,4 @@ -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) set(SOURCES RenderSystemGL.cpp ScreenshotSurfaceGL.cpp GLShader.cpp) diff --git a/xbmc/utils/CMakeLists.txt b/xbmc/utils/CMakeLists.txt index 8bc8ddef11d73..68c5c666afe2a 100644 --- a/xbmc/utils/CMakeLists.txt +++ b/xbmc/utils/CMakeLists.txt @@ -201,7 +201,7 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL Clang) set_source_files_properties(Mime.cpp PROPERTIES COMPILE_FLAGS -O0) endif() -if(OPENGL_FOUND OR TARGET OpenGL::GLES) +if(TARGET OpenGL::GL OR TARGET OpenGL::GLES) list(APPEND SOURCES GLUtils.cpp) list(APPEND HEADERS GLUtils.h) endif() diff --git a/xbmc/video/dialogs/CMakeLists.txt b/xbmc/video/dialogs/CMakeLists.txt index b2ec11f8b84e9..d29f35cd79aae 100644 --- a/xbmc/video/dialogs/CMakeLists.txt +++ b/xbmc/video/dialogs/CMakeLists.txt @@ -18,7 +18,7 @@ set(HEADERS GUIDialogAudioSettings.h GUIDialogVideoOSD.h GUIDialogVideoSettings.h) -if(OPENGL_FOUND OR CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) +if(TARGET OpenGL::GL OR CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) list(APPEND SOURCES GUIDialogCMSSettings.cpp) list(APPEND HEADERS GUIDialogCMSSettings.h) endif() diff --git a/xbmc/windowing/X11/CMakeLists.txt b/xbmc/windowing/X11/CMakeLists.txt index 88714b6a7d589..192d8225ace15 100644 --- a/xbmc/windowing/X11/CMakeLists.txt +++ b/xbmc/windowing/X11/CMakeLists.txt @@ -23,7 +23,7 @@ if(GLX_FOUND) VideoSyncGLX.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES WinSystemX11GLContext.cpp) list(APPEND HEADERS WinSystemX11GLContext.h) list(APPEND SOURCES VideoSyncOML.cpp) diff --git a/xbmc/windowing/gbm/CMakeLists.txt b/xbmc/windowing/gbm/CMakeLists.txt index 508618ae0e65f..91085f8b7f6a5 100644 --- a/xbmc/windowing/gbm/CMakeLists.txt +++ b/xbmc/windowing/gbm/CMakeLists.txt @@ -14,7 +14,7 @@ set(HEADERS OptionalsReg.h WinSystemGbmEGLContext.h GBMDPMSSupport.h) -if (OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES WinSystemGbmGLContext.cpp) list(APPEND HEADERS WinSystemGbmGLContext.h) endif() diff --git a/xbmc/windowing/osx/OpenGL/CMakeLists.txt b/xbmc/windowing/osx/OpenGL/CMakeLists.txt index a57b817e9ffa0..c0136fcd97000 100644 --- a/xbmc/windowing/osx/OpenGL/CMakeLists.txt +++ b/xbmc/windowing/osx/OpenGL/CMakeLists.txt @@ -1,4 +1,4 @@ -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) set(SOURCES WinSystemOSXGL.mm) set(HEADERS WinSystemOSXGL.h) diff --git a/xbmc/windowing/wayland/CMakeLists.txt b/xbmc/windowing/wayland/CMakeLists.txt index fc432d5cf5f81..d29b97c685001 100644 --- a/xbmc/windowing/wayland/CMakeLists.txt +++ b/xbmc/windowing/wayland/CMakeLists.txt @@ -54,7 +54,7 @@ if(TARGET EGL::EGL) list(APPEND HEADERS WinSystemWaylandEGLContext.h) endif() -if(OPENGL_FOUND) +if(TARGET OpenGL::GL) list(APPEND SOURCES WinSystemWaylandEGLContextGL.cpp) list(APPEND HEADERS WinSystemWaylandEGLContextGL.h) endif() From a30bb554690dac6120351cf7a4bc57841600445a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 10 Sep 2023 15:44:36 +1000 Subject: [PATCH 297/811] [cmake] FindPulseAudio migrate to full TARGET usage --- CMakeLists.txt | 2 +- cmake/modules/FindPulseAudio.cmake | 90 +++++++++++++-------------- cmake/scripts/linux/Install.cmake | 2 +- xbmc/cores/AudioEngine/CMakeLists.txt | 2 +- 4 files changed, 48 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac77465c0bdd5..a7acd26853877 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,7 +270,7 @@ endif() set(outputFilterRegex "addons/xbmc.json") find_addon_xml_in_files(${outputFilterRegex}) -if(TARGET ALSA::ALSA AND PULSEAUDIO_FOUND) +if(TARGET ALSA::ALSA AND TARGET PulseAudio::PulseAudio) list(APPEND AUDIO_BACKENDS_LIST "alsa+pulseaudio") endif() diff --git a/cmake/modules/FindPulseAudio.cmake b/cmake/modules/FindPulseAudio.cmake index ce70a5fd6602d..c35a405a797a3 100644 --- a/cmake/modules/FindPulseAudio.cmake +++ b/cmake/modules/FindPulseAudio.cmake @@ -3,68 +3,68 @@ # -------------- # Finds the PulseAudio library # -# This will define the following variables:: +# This will define the following target: # -# PULSEAUDIO_FOUND - system has the PulseAudio library -# PULSEAUDIO_INCLUDE_DIRS - the PulseAudio include directory -# PULSEAUDIO_LIBRARIES - the libraries needed to use PulseAudio -# PULSEAUDIO_DEFINITIONS - the definitions needed to use PulseAudio -# -# and the following imported targets:: -# -# PulseAudio::PulseAudio - The PulseAudio library +# PulseAudio::PulseAudio - The PulseAudio library +# PulseAudio::PulseAudioSimple - The PulseAudio simple library +# PulseAudio::PulseAudioMainloop - The PulseAudio mainloop library -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_PULSEAUDIO libpulse>=11.0.0 QUIET) - pkg_check_modules(PC_PULSEAUDIO_MAINLOOP libpulse-mainloop-glib>=11.0.0 QUIET) - pkg_check_modules(PC_PULSEAUDIO_SIMPLE libpulse-simple>=11.0.0 QUIET) -endif() +if(NOT TARGET PulseAudio::PulseAudio) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_PULSEAUDIO libpulse>=11.0.0 QUIET) + pkg_check_modules(PC_PULSEAUDIO_MAINLOOP libpulse-mainloop-glib>=11.0.0 QUIET) + pkg_check_modules(PC_PULSEAUDIO_SIMPLE libpulse-simple>=11.0.0 QUIET) + endif() -find_path(PULSEAUDIO_INCLUDE_DIR NAMES pulse/pulseaudio.h pulse/simple.h - PATHS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS}) + find_path(PULSEAUDIO_INCLUDE_DIR NAMES pulse/pulseaudio.h pulse/simple.h + HINTS ${PC_PULSEAUDIO_INCLUDEDIR} ${PC_PULSEAUDIO_INCLUDE_DIRS} + NO_CACHE) -find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse - PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) + find_library(PULSEAUDIO_LIBRARY NAMES pulse libpulse + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} + NO_CACHE) -find_library(PULSEAUDIO_SIMPLE_LIBRARY NAMES pulse-simple libpulse-simple - PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) + find_library(PULSEAUDIO_SIMPLE_LIBRARY NAMES pulse-simple libpulse-simple + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} + NO_CACHE) -find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib - PATHS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS}) + find_library(PULSEAUDIO_MAINLOOP_LIBRARY NAMES pulse-mainloop pulse-mainloop-glib libpulse-mainloop-glib + HINTS ${PC_PULSEAUDIO_LIBDIR} ${PC_PULSEAUDIO_LIBRARY_DIRS} + NO_CACHE) -if(PC_PULSEAUDIO_VERSION) - set(PULSEAUDIO_VERSION_STRING ${PC_PULSEAUDIO_VERSION}) -elseif(PULSEAUDIO_INCLUDE_DIR AND EXISTS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h") - file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulseaudio_version_str REGEX "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\".*\"\\).*") - string(REGEX REPLACE "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]+)\"\\).*" "\\1" PULSEAUDIO_VERSION_STRING "${pulseaudio_version_str}") - unset(pulseaudio_version_str) -endif() + if(PC_PULSEAUDIO_VERSION) + set(PULSEAUDIO_VERSION_STRING ${PC_PULSEAUDIO_VERSION}) + elseif(PULSEAUDIO_INCLUDE_DIR AND EXISTS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h") + file(STRINGS "${PULSEAUDIO_INCLUDE_DIR}/pulse/version.h" pulseaudio_version_str REGEX "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\".*\"\\).*") + string(REGEX REPLACE "^#define[\t ]+pa_get_headers_version\\(\\)[\t ]+\\(\"([^\"]+)\"\\).*" "\\1" PULSEAUDIO_VERSION_STRING "${pulseaudio_version_str}") + unset(pulseaudio_version_str) + endif() -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PulseAudio - REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY PULSEAUDIO_SIMPLE_LIBRARY PULSEAUDIO_INCLUDE_DIR - VERSION_VAR PULSEAUDIO_VERSION_STRING) + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(PulseAudio + REQUIRED_VARS PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY PULSEAUDIO_SIMPLE_LIBRARY PULSEAUDIO_INCLUDE_DIR + VERSION_VAR PULSEAUDIO_VERSION_STRING) -if(PULSEAUDIO_FOUND) - set(PULSEAUDIO_INCLUDE_DIRS ${PULSEAUDIO_INCLUDE_DIR}) - set(PULSEAUDIO_LIBRARIES ${PULSEAUDIO_LIBRARY} ${PULSEAUDIO_MAINLOOP_LIBRARY} ${PULSEAUDIO_SIMPLE_LIBRARY}) - set(PULSEAUDIO_DEFINITIONS -DHAS_PULSEAUDIO=1) - list(APPEND AUDIO_BACKENDS_LIST "pulseaudio") - set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) + if(PULSEAUDIO_FOUND) + list(APPEND AUDIO_BACKENDS_LIST "pulseaudio") + set(AUDIO_BACKENDS_LIST ${AUDIO_BACKENDS_LIST} PARENT_SCOPE) + + add_library(PulseAudio::PulseAudioSimple UNKNOWN IMPORTED) + set_target_properties(PulseAudio::PulseAudioSimple PROPERTIES + IMPORTED_LOCATION "${PULSEAUDIO_SIMPLE_LIBRARY}") - if(NOT TARGET PulseAudio::PulseAudioMainloop) add_library(PulseAudio::PulseAudioMainloop UNKNOWN IMPORTED) set_target_properties(PulseAudio::PulseAudioMainloop PROPERTIES IMPORTED_LOCATION "${PULSEAUDIO_MAINLOOP_LIBRARY}") - endif() - if(NOT TARGET PulseAudio::PulseAudio) + add_library(PulseAudio::PulseAudio UNKNOWN IMPORTED) set_target_properties(PulseAudio::PulseAudio PROPERTIES IMPORTED_LOCATION "${PULSEAUDIO_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${PULSEAUDIO_INCLUDE_DIR}" INTERFACE_COMPILE_DEFINITIONS HAVE_LIBPULSE=1 - INTERFACE_LINK_LIBRARIES PulseAudio::PulseAudioMainloop) + INTERFACE_LINK_LIBRARIES "PulseAudio::PulseAudioMainloop;PulseAudio::PulseAudioSimple") + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP PulseAudio::PulseAudio) endif() endif() - -mark_as_advanced(PULSEAUDIO_INCLUDE_DIR PULSEAUDIO_LIBRARY PULSEAUDIO_MAINLOOP_LIBRARY PULSEAUDIO_SIMPLE_LIBRARY) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 85d728c635d9a..8f0941cdc9ce0 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -23,7 +23,7 @@ set(APP_INCLUDE_DIR ${includedir}/${APP_NAME_LC}) # Set XBMC_STANDALONE_SH_PULSE so we can insert PulseAudio block into kodi-standalone if(EXISTS ${CMAKE_SOURCE_DIR}/tools/Linux/kodi-standalone.sh.pulse) - if(ENABLE_PULSEAUDIO AND PULSEAUDIO_FOUND) + if(ENABLE_PULSEAUDIO AND TARGET PulseAudio::PulseAudio) file(READ "${CMAKE_SOURCE_DIR}/tools/Linux/kodi-standalone.sh.pulse" pulse_content) set(XBMC_STANDALONE_SH_PULSE ${pulse_content}) endif() diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt index 07a00751675ac..bc57f3620c5d3 100644 --- a/xbmc/cores/AudioEngine/CMakeLists.txt +++ b/xbmc/cores/AudioEngine/CMakeLists.txt @@ -63,7 +63,7 @@ if(TARGET ALSA::ALSA) endif() endif() -if(PULSEAUDIO_FOUND) +if(TARGET PulseAudio::PulseAudio) list(APPEND SOURCES Sinks/AESinkPULSE.cpp) list(APPEND HEADERS Sinks/AESinkPULSE.h) endif() From b3542a899eb1670de91190403f6174a6b309d6b7 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 27 Sep 2023 11:57:15 +0100 Subject: [PATCH 298/811] [upnp] Fix smart playlist folder definitions --- xbmc/playlists/PlayListFactory.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/playlists/PlayListFactory.cpp b/xbmc/playlists/PlayListFactory.cpp index dc45bcce2d8dd..66c508633b6de 100644 --- a/xbmc/playlists/PlayListFactory.cpp +++ b/xbmc/playlists/PlayListFactory.cpp @@ -134,12 +134,12 @@ bool CPlayListFactory::IsPlaylist(const CFileItem& item) bool CPlayListFactory::IsPlaylist(const CURL& url) { return URIUtils::HasExtension(url, - ".m3u|.m3u8|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf"); + ".m3u|.m3u8|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf|.xsp"); } bool CPlayListFactory::IsPlaylist(const std::string& filename) { return URIUtils::HasExtension(filename, - ".m3u|.m3u8|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf"); + ".m3u|.m3u8|.b4s|.pls|.strm|.wpl|.asx|.ram|.url|.pxml|.xspf|.xsp"); } From 6ebfec6590bf5ab12b6994b8f4d335e2e17237e4 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 27 Sep 2023 17:21:14 +0100 Subject: [PATCH 299/811] [db][streamdetails] Reintroduce external subtitles into streamdetails --- xbmc/cores/VideoPlayer/DVDFileInfo.cpp | 29 ++++++++++++++++++++++++++ xbmc/cores/VideoPlayer/DVDFileInfo.h | 6 ++++++ 2 files changed, 35 insertions(+) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp index eb62cf24ea5dd..bd89cb4629c40 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.cpp +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.cpp @@ -317,6 +317,7 @@ bool CDVDFileInfo::GetFileStreamDetails(CFileItem *pItem) if (pDemuxer) { bool retVal = DemuxerToStreamDetails(pInputStream, pDemuxer, pItem->GetVideoInfoTag()->m_streamDetails, strFileNameAndPath); + ProcessExternalSubtitles(pItem); delete pDemuxer; return retVal; } @@ -429,6 +430,34 @@ bool CDVDFileInfo::DemuxerToStreamDetails(const std::shared_ptr return retVal; } +void CDVDFileInfo::ProcessExternalSubtitles(CFileItem* item) +{ + std::vector externalSubtitles; + const std::string videoPath = item->GetDynPath(); + + CUtil::ScanForExternalSubtitles(videoPath, externalSubtitles); + + for (const auto& externalSubtitle : externalSubtitles) + { + // if vobsub subtitle: + if (URIUtils::GetExtension(externalSubtitle) == ".idx") + { + std::string subFile; + if (CUtil::FindVobSubPair(externalSubtitles, externalSubtitle, subFile)) + AddExternalSubtitleToDetails(videoPath, item->GetVideoInfoTag()->m_streamDetails, + externalSubtitle, subFile); + } + else + { + if (!CUtil::IsVobSub(externalSubtitles, externalSubtitle)) + { + AddExternalSubtitleToDetails(videoPath, item->GetVideoInfoTag()->m_streamDetails, + externalSubtitle); + } + } + } +} + bool CDVDFileInfo::AddExternalSubtitleToDetails(const std::string &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename) { std::string ext = URIUtils::GetExtension(filename); diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.h b/xbmc/cores/VideoPlayer/DVDFileInfo.h index 0129d6ac4c7d3..542858c7889b1 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.h +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.h @@ -54,4 +54,10 @@ class CDVDFileInfo * \param[out] details The external subtitle file's StreamDetails. */ static bool AddExternalSubtitleToDetails(const std::string &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename = ""); + + /** \brief Checks external subtitles for a giving item and adds any existing to the item stream details + * \param item The video item + * \sa AddExternalSubtitleToDetails + */ + static void ProcessExternalSubtitles(CFileItem* item); }; From c1c16158eb393db4919b0cd9bc4f50b9d6ec1ec2 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Thu, 28 Sep 2023 15:39:40 +0200 Subject: [PATCH 300/811] FFmpeg: flag --enable-neon has no actual effects on Android targets --- tools/depends/target/ffmpeg/Makefile | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/depends/target/ffmpeg/Makefile b/tools/depends/target/ffmpeg/Makefile index 82f86ef00861b..0a00827ba24c6 100644 --- a/tools/depends/target/ffmpeg/Makefile +++ b/tools/depends/target/ffmpeg/Makefile @@ -35,12 +35,6 @@ else BASE_URL := http://mirrors.kodi.tv/build-deps/sources endif -ifeq ($(OS), android) - ifeq ($(findstring arm64, $(CPU)), arm64) - CMAKE_ARGS+= -DENABLE_NEON=YES - endif -endif - include ../../download-files.include all: .installed-$(PLATFORM) From 27f8999c0d1f6efdb06efeba7f36b66c9452f8ba Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 15 Sep 2023 20:18:23 +0200 Subject: [PATCH 301/811] [Android] Replace enum HDRTypes with Display.HdrCapabilities constants --- xbmc/windowing/android/AndroidUtils.cpp | 24 ++++++++++++++---------- xbmc/windowing/android/AndroidUtils.h | 10 ---------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/xbmc/windowing/android/AndroidUtils.cpp b/xbmc/windowing/android/AndroidUtils.cpp index 43f3c9e7251cf..6b3cdf8568178 100644 --- a/xbmc/windowing/android/AndroidUtils.cpp +++ b/xbmc/windowing/android/AndroidUtils.cpp @@ -122,14 +122,14 @@ static void fetchDisplayModes() namespace { -const std::map hdrTypeMap = { - {CAndroidUtils::HDRTypes::HDR10, "HDR10"}, - {CAndroidUtils::HDRTypes::HLG, "HLG"}, - {CAndroidUtils::HDRTypes::HDR10_PLUS, "HDR10+"}, - {CAndroidUtils::HDRTypes::DOLBY_VISION, "Dolby Vision"}}; - std::string HdrTypeString(int type) { + const std::map hdrTypeMap = { + {CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10, "HDR10"}, + {CJNIDisplayHdrCapabilities::HDR_TYPE_HLG, "HLG"}, + {CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10_PLUS, "HDR10+"}, + {CJNIDisplayHdrCapabilities::HDR_TYPE_DOLBY_VISION, "Dolby Vision"}}; + auto hdr = hdrTypeMap.find(type); if (hdr != hdrTypeMap.end()) return hdr->second; @@ -406,16 +406,20 @@ CHDRCapabilities CAndroidUtils::GetDisplayHDRCapabilities() CHDRCapabilities caps; const std::vector types = GetDisplaySupportedHdrTypes(); - if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10) != types.end()) + if (std::find(types.begin(), types.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10) != + types.end()) caps.SetHDR10(); - if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HLG) != types.end()) + if (std::find(types.begin(), types.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HLG) != + types.end()) caps.SetHLG(); - if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::HDR10_PLUS) != types.end()) + if (std::find(types.begin(), types.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_HDR10_PLUS) != + types.end()) caps.SetHDR10Plus(); - if (std::find(types.begin(), types.end(), CAndroidUtils::HDRTypes::DOLBY_VISION) != types.end()) + if (std::find(types.begin(), types.end(), CJNIDisplayHdrCapabilities::HDR_TYPE_DOLBY_VISION) != + types.end()) caps.SetDolbyVision(); return caps; diff --git a/xbmc/windowing/android/AndroidUtils.h b/xbmc/windowing/android/AndroidUtils.h index ee110654ff144..e008e5d37e54d 100644 --- a/xbmc/windowing/android/AndroidUtils.h +++ b/xbmc/windowing/android/AndroidUtils.h @@ -34,16 +34,6 @@ class CAndroidUtils : public ISettingCallback static bool SupportsMediaCodecMimeType(const std::string& mimeType); - // Android specific HDR type mapping - // https://developer.android.com/reference/android/view/Display.HdrCapabilities#constants_1 - enum HDRTypes - { - DOLBY_VISION = 1, - HDR10 = 2, - HLG = 3, - HDR10_PLUS = 4 - }; - static std::vector GetDisplaySupportedHdrTypes(); static CHDRCapabilities GetDisplayHDRCapabilities(); static std::pair GetDolbyVisionCapabilities(); From bbbf2f92e8cdb852bd2c3393938733907341692a Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Fri, 15 Sep 2023 20:25:38 +0200 Subject: [PATCH 302/811] bump libandroidjni --- tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION b/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION index d8ee7b58f2d38..430cb5254c46e 100644 --- a/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION +++ b/tools/depends/target/libandroidjni/LIBANDROIDJNI-VERSION @@ -1,6 +1,6 @@ LIBNAME=libandroidjni -VERSION=d2d606d06010c8e58f624f8502f03906695542e9 +VERSION=7c1f1b326c14e0cf4d646b63a2ac448d7a8debe0 BASE_URL=https://github.com/xbmc/libandroidjni/archive ARCHIVE=$(VERSION).tar.gz -SHA512=a4f54e7c8ae16cf9ae8c4cbde448a9c39d1c82916d68ac43830a55c77a77924a2de9062fe467d13eed25998e561e3310ab65613560c6f00b68826a0dad6e6c6e +SHA512=dcfa6a5472c102089f0e6816ea2755fa59388e43f37cf9ac2d23bdadc801f1c4d8c9ab95b0eba1644d31efc6bbf8829b72e837ef35d212e5a1e07fcb2ebba039 BYPRODUCT=libandroidjni.a From 19d6333011453226ce24f7503afa979dcc3d74c2 Mon Sep 17 00:00:00 2001 From: Acidzero2020 <32792082+Acidzero2020@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:56:08 +0200 Subject: [PATCH 303/811] strm fix resume --- xbmc/video/VideoUtils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index 2f61e2c4742aa..c42674d1b9d45 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -676,7 +676,7 @@ ResumeInformation GetNonFolderItemResumeInformation(const CFileItem& item) return {}; // do not resume playlists, except strm files - if (!item.IsType("strm") && item.IsPlayList()) + if (!item.IsType(".strm") && item.IsPlayList()) return {}; // do not resume Live TV and 'deleted' items (e.g. trashed pvr recordings) From 3c89d309efd16ec283c733fb540c55e0664aa17c Mon Sep 17 00:00:00 2001 From: fuzzard Date: Fri, 29 Sep 2023 14:51:50 +1000 Subject: [PATCH 304/811] [cmake] add EXCLUDE_FROM_ALL when adding to build_internal_depends --- cmake/modules/FindCrossGUID.cmake | 1 + cmake/modules/FindFmt.cmake | 1 + cmake/modules/FindNFS.cmake | 1 + cmake/modules/FindPCRE.cmake | 1 + cmake/modules/FindSpdlog.cmake | 1 + cmake/modules/FindTagLib.cmake | 1 + cmake/modules/FindTinyXML2.cmake | 1 + 7 files changed, 7 insertions(+) diff --git a/cmake/modules/FindCrossGUID.cmake b/cmake/modules/FindCrossGUID.cmake index 90d14c466ebad..38444f44333e8 100644 --- a/cmake/modules/FindCrossGUID.cmake +++ b/cmake/modules/FindCrossGUID.cmake @@ -116,6 +116,7 @@ if(NOT TARGET CrossGUID::CrossGUID) if(_multiconfig_generator) if(NOT TARGET crossguid) buildCrossGUID() + set_target_properties(crossguid PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends crossguid) endif() diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index 667605399afc7..0c263982198d4 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -133,6 +133,7 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) if(_multiconfig_generator) if(NOT TARGET fmt) buildFmt() + set_target_properties(fmt PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends fmt) endif() diff --git a/cmake/modules/FindNFS.cmake b/cmake/modules/FindNFS.cmake index 8ea9681c235cc..f1528a188bfd1 100644 --- a/cmake/modules/FindNFS.cmake +++ b/cmake/modules/FindNFS.cmake @@ -155,6 +155,7 @@ if(NOT TARGET libnfs::nfs) if(_multiconfig_generator) if(NOT TARGET libnfs) buildlibnfs() + set_target_properties(libnfs PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends libnfs) endif() diff --git a/cmake/modules/FindPCRE.cmake b/cmake/modules/FindPCRE.cmake index d499feedcb68f..3ef52a2f25e31 100644 --- a/cmake/modules/FindPCRE.cmake +++ b/cmake/modules/FindPCRE.cmake @@ -211,6 +211,7 @@ if(NOT PCRE::pcre) if(_multiconfig_generator) if(NOT TARGET pcre) buildPCRE() + set_target_properties(pcre PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends pcre) endif() diff --git a/cmake/modules/FindSpdlog.cmake b/cmake/modules/FindSpdlog.cmake index 6b77ebe24a00a..a4ff560efa30f 100644 --- a/cmake/modules/FindSpdlog.cmake +++ b/cmake/modules/FindSpdlog.cmake @@ -178,6 +178,7 @@ if(NOT TARGET spdlog::spdlog) if(_multiconfig_generator) if(NOT TARGET spdlog) buildSpdlog() + set_target_properties(spdlog PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends spdlog) endif() diff --git a/cmake/modules/FindTagLib.cmake b/cmake/modules/FindTagLib.cmake index 50c1a83f6b660..e58723ba71c08 100644 --- a/cmake/modules/FindTagLib.cmake +++ b/cmake/modules/FindTagLib.cmake @@ -126,6 +126,7 @@ if(NOT TARGET TagLib::TagLib) if(_multiconfig_generator) if(NOT TARGET taglib) buildTagLib() + set_target_properties(taglib PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends taglib) endif() diff --git a/cmake/modules/FindTinyXML2.cmake b/cmake/modules/FindTinyXML2.cmake index 47f4f5668724f..a75c370f7e175 100644 --- a/cmake/modules/FindTinyXML2.cmake +++ b/cmake/modules/FindTinyXML2.cmake @@ -148,6 +148,7 @@ if(NOT TARGET tinyxml2::tinyxml2) if(_multiconfig_generator) if(NOT TARGET tinyxml2) buildTinyXML2() + set_target_properties(tinyxml2 PROPERTIES EXCLUDE_FROM_ALL TRUE) endif() add_dependencies(build_internal_depends tinyxml2) endif() From 53da935a2f9b630a43018c32a52a144919d356ab Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 27 Sep 2023 14:44:35 +0100 Subject: [PATCH 305/811] [video] use ffmpeg av_display_rotation_get for rotation from metadata --- .../VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 1f09cccc03d99..24fbc18518156 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -40,6 +40,7 @@ extern "C" { #include "libavutil/channel_layout.h" +#include "libavutil/display.h" #include "libavutil/pixdesc.h" } @@ -1721,9 +1722,17 @@ CDemuxStream* CDVDDemuxFFmpeg::AddStream(int streamIdx) *reinterpret_cast(side_data)); } - AVDictionaryEntry* rtag = av_dict_get(pStream->metadata, "rotate", NULL, 0); - if (rtag) - st->iOrientation = atoi(rtag->value); + uint8_t* displayMatrixSideData = + av_stream_get_side_data(pStream, AV_PKT_DATA_DISPLAYMATRIX, nullptr); + if (displayMatrixSideData) + { + const double tetha = + av_display_rotation_get(reinterpret_cast(displayMatrixSideData)); + if (!std::isnan(tetha)) + { + st->iOrientation = ((static_cast(-tetha) % 360) + 360) % 360; + } + } // detect stereoscopic mode std::string stereoMode = GetStereoModeFromMetadata(pStream->metadata); From 81ce5ee79e4c6f6453ba42198cb3454a2763894f Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 29 Sep 2023 09:53:39 +0100 Subject: [PATCH 306/811] [utils] Introduce LanCheckMode to remove old offlinecheck bool in IsHostOnLAN --- xbmc/addons/interfaces/Network.cpp | 4 +++- xbmc/network/WakeOnAccess.cpp | 2 +- xbmc/network/upnp/UPnPInternal.cpp | 4 ++-- xbmc/utils/URIUtils.cpp | 6 ++++-- xbmc/utils/URIUtils.h | 14 +++++++++++++- 5 files changed, 23 insertions(+), 7 deletions(-) diff --git a/xbmc/addons/interfaces/Network.cpp b/xbmc/addons/interfaces/Network.cpp index 63151f3b78c82..8e7526329324e 100644 --- a/xbmc/addons/interfaces/Network.cpp +++ b/xbmc/addons/interfaces/Network.cpp @@ -140,7 +140,9 @@ bool Interface_Network::is_host_on_lan(void* kodiBase, const char* hostname, boo return false; } - return URIUtils::IsHostOnLAN(hostname, offLineCheck); + //! TODO change to use the LanCheckMode enum class on the API signature + return URIUtils::IsHostOnLAN(hostname, offLineCheck ? LanCheckMode::ANY_PRIVATE_SUBNET + : LanCheckMode::ONLY_LOCAL_SUBNET); } char* Interface_Network::dns_lookup(void* kodiBase, const char* url, bool* ret) diff --git a/xbmc/network/WakeOnAccess.cpp b/xbmc/network/WakeOnAccess.cpp index 7e3679938304b..36ba50ef12481 100644 --- a/xbmc/network/WakeOnAccess.cpp +++ b/xbmc/network/WakeOnAccess.cpp @@ -640,7 +640,7 @@ void CWakeOnAccess::QueueMACDiscoveryForHost(const std::string& host) { if (IsEnabled()) { - if (URIUtils::IsHostOnLAN(host, true)) + if (URIUtils::IsHostOnLAN(host, LanCheckMode::ANY_PRIVATE_SUBNET)) CServiceBroker::GetJobManager()->AddJob(new CMACDiscoveryJob(host), this); else CLog::Log(LOGINFO, "{} - skip Mac discovery for non-local host '{}'", __FUNCTION__, host); diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index 0508c58f890d2..fddc3608c00ed 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -1130,8 +1130,8 @@ struct ResourcePrioritySort prio += 400; NPT_Url url(res.m_Uri); - if (URIUtils::IsHostOnLAN((const char*)url.GetHost(), false)) - prio += 300; + if (URIUtils::IsHostOnLAN(url.GetHost().GetChars(), LanCheckMode::ONLY_LOCAL_SUBNET)) + prio += 300; if (res.m_ProtocolInfo.GetProtocol() == "xbmc-get") prio += 200; diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index c780da6be0162..5d6eb92d8bca2 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -675,7 +675,7 @@ static bool addr_match(uint32_t addr, const char* target, const char* submask) return (addr & mask) == (addr2 & mask); } -bool URIUtils::IsHostOnLAN(const std::string& host, bool offLineCheck) +bool URIUtils::IsHostOnLAN(const std::string& host, LanCheckMode lanCheckMode) { if(host.length() == 0) return false; @@ -695,7 +695,9 @@ bool URIUtils::IsHostOnLAN(const std::string& host, bool offLineCheck) if(address != INADDR_NONE) { - if (offLineCheck) // check if in private range, ref https://en.wikipedia.org/wiki/Private_network + if (lanCheckMode == + LanCheckMode:: + ANY_PRIVATE_SUBNET) // check if in private range, ref https://en.wikipedia.org/wiki/Private_network { if ( addr_match(address, "192.168.0.0", "255.255.0.0") || diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h index 85ba81bd81218..dac3ff57d8230 100644 --- a/xbmc/utils/URIUtils.h +++ b/xbmc/utils/URIUtils.h @@ -15,6 +15,17 @@ class CURL; class CAdvancedSettings; class CFileItem; +/*! \brief Defines the methodology to find hosts on a given subnet + \sa IsHostOnLAN +*/ +enum class LanCheckMode +{ + /*! \brief Only match if the host is on the same subnet as the kodi host */ + ONLY_LOCAL_SUBNET, + /*! \brief Match the host if it belongs to any private subnet */ + ANY_PRIVATE_SUBNET +}; + class URIUtils { public: @@ -147,7 +158,8 @@ class URIUtils static bool IsNfs(const std::string& strFile); static bool IsOnDVD(const std::string& strFile); static bool IsOnLAN(const std::string& strFile); - static bool IsHostOnLAN(const std::string& hostName, bool offLineCheck = false); + static bool IsHostOnLAN(const std::string& hostName, + LanCheckMode lanCheckMode = LanCheckMode::ONLY_LOCAL_SUBNET); static bool IsPlugin(const std::string& strFile); static bool IsScript(const std::string& strFile); static bool IsRAR(const std::string& strFile); From 2d6b5b03deba8a27928102a7e9ee56a3f67b9811 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 29 Sep 2023 09:58:46 +0100 Subject: [PATCH 307/811] [utils] propagate LanCheckMode for IsOnLAN --- xbmc/utils/URIUtils.cpp | 12 ++++++------ xbmc/utils/URIUtils.h | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index 5d6eb92d8bca2..54feea3981db1 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -639,16 +639,16 @@ bool URIUtils::IsOnDVD(const std::string& strFile) return false; } -bool URIUtils::IsOnLAN(const std::string& strPath) +bool URIUtils::IsOnLAN(const std::string& strPath, LanCheckMode lanCheckMode) { if(IsMultiPath(strPath)) - return IsOnLAN(CMultiPathDirectory::GetFirstPath(strPath)); + return IsOnLAN(CMultiPathDirectory::GetFirstPath(strPath), lanCheckMode); if(IsStack(strPath)) - return IsOnLAN(CStackDirectory::GetFirstStackedFile(strPath)); + return IsOnLAN(CStackDirectory::GetFirstStackedFile(strPath), lanCheckMode); if(IsSpecial(strPath)) - return IsOnLAN(CSpecialProtocol::TranslatePath(strPath)); + return IsOnLAN(CSpecialProtocol::TranslatePath(strPath), lanCheckMode); if(IsPlugin(strPath)) return false; @@ -658,14 +658,14 @@ bool URIUtils::IsOnLAN(const std::string& strPath) CURL url(strPath); if (HasParentInHostname(url)) - return IsOnLAN(url.GetHostName()); + return IsOnLAN(url.GetHostName(), lanCheckMode); if(!IsRemote(strPath)) return false; const std::string& host = url.GetHostName(); - return IsHostOnLAN(host); + return IsHostOnLAN(host, lanCheckMode); } static bool addr_match(uint32_t addr, const char* target, const char* submask) diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h index dac3ff57d8230..f682e3dfec8d0 100644 --- a/xbmc/utils/URIUtils.h +++ b/xbmc/utils/URIUtils.h @@ -157,7 +157,8 @@ class URIUtils static bool IsMusicDb(const std::string& strFile); static bool IsNfs(const std::string& strFile); static bool IsOnDVD(const std::string& strFile); - static bool IsOnLAN(const std::string& strFile); + static bool IsOnLAN(const std::string& strFile, + LanCheckMode lanCheckMode = LanCheckMode::ONLY_LOCAL_SUBNET); static bool IsHostOnLAN(const std::string& hostName, LanCheckMode lanCheckMode = LanCheckMode::ONLY_LOCAL_SUBNET); static bool IsPlugin(const std::string& strFile); From 57b1c1e057305110b7b70c5a540406f6a369f4ad Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 27 Sep 2023 18:00:19 +0100 Subject: [PATCH 308/811] [edl] Allow edl for files on private subnets --- xbmc/cores/VideoPlayer/Edl.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/Edl.cpp b/xbmc/cores/VideoPlayer/Edl.cpp index 51f7ef72ca8a1..a3bbea05ad03a 100644 --- a/xbmc/cores/VideoPlayer/Edl.cpp +++ b/xbmc/cores/VideoPlayer/Edl.cpp @@ -49,10 +49,10 @@ bool CEdl::ReadEditDecisionLists(const CFileItem& fileItem, const float fFramesP /* * Only check for edit decision lists if the movie is on the local hard drive, or accessed over a - * network share. + * network share (even if from a different private network). */ const std::string& strMovie = fileItem.GetDynPath(); - if ((URIUtils::IsHD(strMovie) || URIUtils::IsOnLAN(strMovie)) && + if ((URIUtils::IsHD(strMovie) || URIUtils::IsOnLAN(strMovie, LanCheckMode::ANY_PRIVATE_SUBNET)) && !URIUtils::IsInternetStream(strMovie)) { CLog::Log(LOGDEBUG, From 95d690ff335fd1121a8583c221dfd26dac51d017 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Fri, 29 Sep 2023 11:58:31 +0100 Subject: [PATCH 309/811] [guilib] let oninfo propagate from the item to the container if the container cannot handle the item info --- xbmc/guilib/GUIBaseContainer.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/guilib/GUIBaseContainer.cpp b/xbmc/guilib/GUIBaseContainer.cpp index cfc3b589a6330..237ced88a8f5c 100644 --- a/xbmc/guilib/GUIBaseContainer.cpp +++ b/xbmc/guilib/GUIBaseContainer.cpp @@ -404,14 +404,14 @@ bool CGUIBaseContainer::OnAction(const CAction &action) case ACTION_SHOW_INFO: if (m_listProvider) { - int selected = GetSelectedItem(); + const int selected = GetSelectedItem(); if (selected >= 0 && selected < static_cast(m_items.size())) { - m_listProvider->OnInfo(m_items[selected]); - return true; + if (m_listProvider->OnInfo(m_items[selected])) + return true; } } - else if (OnInfo()) + if (OnInfo()) return true; else if (action.GetID()) return OnClick(action.GetID()); From d8ed2755d8d80eac1edd2d09d58ade18d202c4fd Mon Sep 17 00:00:00 2001 From: Ningyuan Li Date: Thu, 28 Sep 2023 11:25:53 +0900 Subject: [PATCH 310/811] Acb (pre-webOS 5.0) support --- cmake/modules/FindAcbAPI.cmake | 37 ++++++++++ cmake/platform/linux/webos.cmake | 2 +- cmake/scripts/webos/Install.cmake | 3 +- .../DVDCodecs/Video/DVDVideoCodecStarfish.cpp | 71 +++++++++++++++++-- .../DVDCodecs/Video/DVDVideoCodecStarfish.h | 4 ++ .../VideoRenderers/BaseRenderer.cpp | 8 ++- .../VideoPlayer/VideoRenderers/BaseRenderer.h | 4 ++ .../HwDecRender/RendererStarfish.cpp | 20 +++++- .../HwDecRender/RendererStarfish.h | 1 + .../wayland/WinSystemWaylandWebOS.cpp | 5 ++ .../windowing/wayland/WinSystemWaylandWebOS.h | 2 + 11 files changed, 146 insertions(+), 11 deletions(-) create mode 100644 cmake/modules/FindAcbAPI.cmake diff --git a/cmake/modules/FindAcbAPI.cmake b/cmake/modules/FindAcbAPI.cmake new file mode 100644 index 0000000000000..89bdc38392a13 --- /dev/null +++ b/cmake/modules/FindAcbAPI.cmake @@ -0,0 +1,37 @@ +#.rst: +# FindAcbAPI +# -------- +# Finds the AcbAPI library +# +# This will define the following target: +# +# ACBAPI::ACBAPI - The acbAPI library + +if(NOT TARGET ACBAPI::ACBAPI) + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + pkg_check_modules(PC_ACBAPI libAcbAPI QUIET) + endif() + + find_path(ACBAPI_INCLUDE_DIR NAMES appswitching-control-block/AcbAPI.h + PATHS ${PC_ACBAPI_INCLUDEDIR} + NO_CACHE) + find_library(ACBAPI_LIBRARY NAMES AcbAPI + PATHS ${PC_ACBAPI_LIBDIR} + NO_CACHE) + + set(ACBAPI_VERSION ${PC_ACBAPI_VERSION}) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(AcbAPI + REQUIRED_VARS ACBAPI_LIBRARY ACBAPI_INCLUDE_DIR + VERSION_VAR ACBAPI_VERSION) + + if(ACBAPI_FOUND) + add_library(ACBAPI::ACBAPI UNKNOWN IMPORTED) + set_target_properties(ACBAPI::ACBAPI PROPERTIES + IMPORTED_LOCATION "${ACBAPI_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ACBAPI_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ACBAPI::ACBAPI) + endif() +endif() diff --git a/cmake/platform/linux/webos.cmake b/cmake/platform/linux/webos.cmake index c740ecd27739b..ce60f79ccefdc 100644 --- a/cmake/platform/linux/webos.cmake +++ b/cmake/platform/linux/webos.cmake @@ -4,7 +4,7 @@ include(${CMAKE_SOURCE_DIR}/cmake/platform/${CORE_SYSTEM_NAME}/wayland.cmake) # saves reworking other assumptions for linux windowing as the platform name. list(APPEND CORE_PLATFORM_NAME_LC wayland) -list(APPEND PLATFORM_REQUIRED_DEPS WaylandProtocolsWebOS PlayerAPIs PlayerFactory WebOSHelpers) +list(APPEND PLATFORM_REQUIRED_DEPS WaylandProtocolsWebOS PlayerAPIs PlayerFactory WebOSHelpers AcbAPI) list(APPEND ARCH_DEFINES -DTARGET_WEBOS) set(ENABLE_PULSEAUDIO OFF CACHE BOOL "" FORCE) set(TARGET_WEBOS TRUE) diff --git a/cmake/scripts/webos/Install.cmake b/cmake/scripts/webos/Install.cmake index ab894aa9df5e5..fde84c14793d0 100644 --- a/cmake/scripts/webos/Install.cmake +++ b/cmake/scripts/webos/Install.cmake @@ -29,7 +29,8 @@ set(APP_INSTALL_DIRS ${CMAKE_BINARY_DIR}/addons ${CMAKE_BINARY_DIR}/system ${CMAKE_BINARY_DIR}/userdata) set(APP_TOOLCHAIN_FILES ${TOOLCHAIN}/${HOST}/sysroot/lib/libatomic.so.1 - ${TOOLCHAIN}/${HOST}/sysroot/lib/libcrypt.so.1) + ${TOOLCHAIN}/${HOST}/sysroot/lib/libcrypt.so.1 + ${DEPENDS_PATH}/lib/libAcbAPI.so.1) set(BIN_ADDONS_DIR ${DEPENDS_PATH}/addons) file(WRITE ${CMAKE_BINARY_DIR}/install.cmake " diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp index c393fd6b6d1ab..f919443451a9a 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.cpp @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -47,6 +48,20 @@ constexpr unsigned int MAX_SRC_BUFFER_LEVEL = 8 * 1024 * 1024; // 8 MB CDVDVideoCodecStarfish::CDVDVideoCodecStarfish(CProcessInfo& processInfo) : CDVDVideoCodec(processInfo), m_starfishMediaAPI(std::make_unique()) { + using namespace KODI::WINDOWING::WAYLAND; + auto winSystem = static_cast(CServiceBroker::GetWinSystem()); + if (!winSystem->SupportsExportedWindow()) + { + m_acbId = AcbAPI_create(); + if (m_acbId) + { + if (!AcbAPI_initialize(m_acbId, PLAYER_TYPE_MSE, getenv("APPID"), &AcbCallback)) + { + AcbAPI_destroy(m_acbId); + m_acbId = 0; + } + } + } } CDVDVideoCodecStarfish::~CDVDVideoCodecStarfish() @@ -200,10 +215,12 @@ bool CDVDVideoCodecStarfish::OpenInternal(CDVDStreamInfo& hints, CDVDCodecOption using namespace KODI::WINDOWING::WAYLAND; auto winSystem = static_cast(CServiceBroker::GetWinSystem()); - std::string exportedWindowName = winSystem->GetExportedWindowName(); - payloadArg["mediaTransportType"] = "BUFFERSTREAM"; - payloadArg["option"]["windowId"] = exportedWindowName; + if (winSystem->SupportsExportedWindow()) + { + std::string exportedWindowName = winSystem->GetExportedWindowName(); + payloadArg["option"]["windowId"] = exportedWindowName; + } payloadArg["option"]["appId"] = CCompileInfo::GetPackage(); payloadArg["option"]["externalStreamingInfo"]["contents"]["codec"]["video"] = m_codecname; payloadArg["option"]["externalStreamingInfo"]["contents"]["esInfo"]["pauseAtDecodeTime"] = true; @@ -257,7 +274,9 @@ bool CDVDVideoCodecStarfish::OpenInternal(CDVDStreamInfo& hints, CDVDCodecOption m_videobuffer.iDisplayWidth = m_hints.width; m_videobuffer.iDisplayHeight = m_hints.height; m_videobuffer.stereoMode = m_hints.stereo_mode; - m_videobuffer.videoBuffer = new CStarfishVideoBuffer(0); + CStarfishVideoBuffer* starfishVideoBuffer = new CStarfishVideoBuffer(0); + m_videobuffer.videoBuffer = starfishVideoBuffer; + starfishVideoBuffer->m_acbId = m_acbId; m_opened = true; @@ -280,6 +299,12 @@ void CDVDVideoCodecStarfish::Dispose() m_starfishMediaAPI->Unload(); + if (m_acbId) + { + AcbAPI_finalize(m_acbId); + AcbAPI_destroy(m_acbId); + } + ms_instanceGuard.exchange(false); } @@ -533,17 +558,43 @@ void CDVDVideoCodecStarfish::PlayerCallback(const int32_t type, case PF_EVENT_TYPE_STR_RESOURCE_INFO: m_newFrame = true; break; + case PF_EVENT_TYPE_STR_VIDEO_INFO: + if (m_acbId) + AcbAPI_setMediaVideoData(m_acbId, logstr.c_str()); + break; case PF_EVENT_TYPE_STR_STATE_UPDATE__LOADCOMPLETED: + if (m_acbId) + { + AcbAPI_setSinkType(m_acbId, SINK_TYPE_MAIN); + AcbAPI_setMediaId(m_acbId, m_starfishMediaAPI->getMediaID()); + AcbAPI_setState(m_acbId, APPSTATE_FOREGROUND, PLAYSTATE_LOADED, nullptr); + } m_starfishMediaAPI->Play(); m_state = StarfishState::FLUSHED; break; + case PF_EVENT_TYPE_STR_STATE_UPDATE__PLAYING: + if (m_acbId) + AcbAPI_setState(m_acbId, APPSTATE_FOREGROUND, PLAYSTATE_PLAYING, nullptr); + break; + case PF_EVENT_TYPE_STR_STATE_UPDATE__PAUSED: + if (m_acbId) + AcbAPI_setState(m_acbId, APPSTATE_FOREGROUND, PLAYSTATE_PAUSED, nullptr); + break; case PF_EVENT_TYPE_STR_STATE_UPDATE__ENDOFSTREAM: m_state = StarfishState::EOS; break; + case PF_EVENT_TYPE_STR_STATE_UPDATE__UNLOADCOMPLETED: + if (m_acbId) + AcbAPI_setState(m_acbId, APPSTATE_FOREGROUND, PLAYSTATE_UNLOADED, nullptr); + break; case PF_EVENT_TYPE_INT_ERROR: + CLog::LogF(LOGERROR, "CDVDVideoCodecStarfish: Pipeline INT_ERROR numValue: {}, strValue: {}", + numValue, logstr); + m_state = StarfishState::ERROR; + break; case PF_EVENT_TYPE_STR_ERROR: - CLog::LogF(LOGERROR, "CDVDVideoCodecStarfish: Pipeline error numValue: {}, strValue: {}", - type, numValue, logstr); + CLog::LogF(LOGERROR, "CDVDVideoCodecStarfish: Pipeline STR_ERROR numValue: {}, strValue: {}", + numValue, logstr); m_state = StarfishState::ERROR; break; } @@ -556,3 +607,11 @@ void CDVDVideoCodecStarfish::PlayerCallback(const int32_t type, { static_cast(data)->PlayerCallback(type, numValue, strValue); } + +void CDVDVideoCodecStarfish::AcbCallback( + long acbId, long taskId, long eventType, long appState, long playState, const char* reply) +{ + CLog::LogF(LOGDEBUG, + "ACB callback: acbId={}, taskId={}, eventType={}, appState={}, playState={}, reply={}", + acbId, taskId, eventType, appState, playState, reply); +} \ No newline at end of file diff --git a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h index ce24b1fce9718..1ee1fb94b1bee 100644 --- a/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h +++ b/xbmc/cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h @@ -25,6 +25,7 @@ class CStarfishVideoBuffer : public CVideoBuffer public: explicit CStarfishVideoBuffer(int id) : CVideoBuffer(id) {} AVPixelFormat GetFormat() override { return AV_PIX_FMT_NONE; } + long m_acbId{0}; }; enum class StarfishState @@ -95,7 +96,10 @@ class CDVDVideoCodecStarfish : public CDVDVideoCodec const int64_t numValue, const char* strValue, void* data); + static void AcbCallback( + long acbId, long taskId, long eventType, long appState, long playState, const char* reply); std::unique_ptr m_starfishMediaAPI; + long m_acbId{0}; CDVDStreamInfo m_hints; std::string m_codecname; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp index b8649daaa8796..a451bc4be7c59 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.cpp @@ -218,7 +218,8 @@ void CBaseRenderer::CalcNormalRenderRect(float offsetX, return; // clip as needed - if (!(CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() || CServiceBroker::GetWinSystem()->GetGfxContext().IsCalibrating())) + if (m_alwaysClip || !(CServiceBroker::GetWinSystem()->GetGfxContext().IsFullScreenVideo() || + CServiceBroker::GetWinSystem()->GetGfxContext().IsCalibrating())) { CRect original(m_destRect); m_destRect.Intersect(CRect(offsetX, offsetY, offsetX + width, offsetY + height)); @@ -501,6 +502,11 @@ void CBaseRenderer::MarkDirty() CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(m_destRect); } +void CBaseRenderer::EnableAlwaysClip() +{ + m_alwaysClip = true; +} + void CBaseRenderer::SetVideoSettings(const CVideoSettings &settings) { m_videoSettings = settings; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h index 67871f9b9dfee..3fdac4149a96d 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/BaseRenderer.h @@ -114,6 +114,7 @@ class CBaseRenderer virtual void ReorderDrawPoints(); virtual EShaderFormat GetShaderFormat(); void MarkDirty(); + void EnableAlwaysClip(); //@todo drop those void saveRotatedCoords();//saves the current state of m_rotatedDestCoords @@ -141,4 +142,7 @@ class CBaseRenderer AVPixelFormat m_format = AV_PIX_FMT_NONE; CVideoSettings m_videoSettings; + +private: + bool m_alwaysClip = false; }; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp index d268b6ca816ba..b2231ff84f3a6 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp @@ -16,6 +16,8 @@ #include "utils/log.h" #include "windowing/wayland/WinSystemWaylandWebOS.h" +#include + CRendererStarfish::CRendererStarfish() { CLog::LogF(LOGINFO, "CRendererStarfish: Instanced"); @@ -32,6 +34,12 @@ CBaseRenderer* CRendererStarfish::Create(CVideoBuffer* buffer) bool CRendererStarfish::Configure(const VideoPicture& picture, float fps, unsigned int orientation) { + auto buffer = static_cast(picture.videoBuffer); + m_acbId = buffer->m_acbId; + if (m_acbId) + { + EnableAlwaysClip(); + } m_iFlags = GetFlagsChromaPosition(picture.chroma_position) | GetFlagsColorMatrix(picture.color_space, picture.iWidth, picture.iHeight) | GetFlagsColorPrimaries(picture.color_primaries) | @@ -84,8 +92,16 @@ void CRendererStarfish::ManageRenderArea() CRect{0, 0, static_cast(m_sourceWidth), static_cast(m_sourceHeight)}; using namespace KODI::WINDOWING::WAYLAND; auto winSystem = static_cast(CServiceBroker::GetWinSystem()); - - winSystem->SetExportedWindow(origRect, m_sourceRect, m_destRect); + if (winSystem->SupportsExportedWindow()) + { + winSystem->SetExportedWindow(origRect, m_sourceRect, m_destRect); + } + else if (m_acbId) + { + AcbAPI_setCustomDisplayWindow(m_acbId, m_sourceRect.x1, m_sourceRect.y1, m_sourceRect.Width(), + m_sourceRect.Height(), m_destRect.x1, m_destRect.y1, + m_destRect.Width(), m_destRect.Height(), false, nullptr); + } m_exportedSourceRect = m_sourceRect; m_exportedDestRect = m_destRect; } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h index ca52c57209ffc..5b261e60a77fa 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h @@ -48,4 +48,5 @@ class CRendererStarfish : public CBaseRenderer CRect m_exportedSourceRect; CRect m_exportedDestRect; bool m_configured{false}; + long m_acbId{0}; }; diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp index ec1567b2a2518..18be9a613ca60 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp @@ -127,6 +127,11 @@ bool CWinSystemWaylandWebOS::SetExportedWindow(CRect orig, CRect src, CRect dest return false; } +bool CWinSystemWaylandWebOS::SupportsExportedWindow() +{ + return m_webosForeign; +} + IShellSurface* CWinSystemWaylandWebOS::CreateShellSurface(const std::string& name) { return new CShellSurfaceWebOSShell(*this, *GetConnection(), GetMainSurface(), name, diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h index 5584d5eb32790..a9427e299f675 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h @@ -40,6 +40,8 @@ class CWinSystemWaylandWebOS : public CWinSystemWayland */ bool SetExportedWindow(CRect orig, CRect src, CRect dest); + bool SupportsExportedWindow(); + IShellSurface* CreateShellSurface(const std::string& name) override; bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; ~CWinSystemWaylandWebOS() noexcept override; From 95ec06329b4839d53995c60a660eb0125993eba5 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 24 Sep 2023 19:16:07 +0200 Subject: [PATCH 311/811] [windows] Fix CGUIMediaWindow::WaitGetDirectoryItems to leave 'wait for update job finished loop' also if the update was canceled via CGUIMediaWindow::CancelUpdateItems. --- xbmc/windows/GUIMediaWindow.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 8a95881c85b46..22e614b79024e 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -2281,6 +2281,7 @@ bool CGUIMediaWindow::WaitGetDirectoryItems(CGetDirectoryItems &items) else { m_updateJobActive = true; + m_updateAborted = false; m_updateEvent.Reset(); CServiceBroker::GetJobManager()->Submit( [&]() { @@ -2289,14 +2290,21 @@ bool CGUIMediaWindow::WaitGetDirectoryItems(CGetDirectoryItems &items) }, nullptr, CJob::PRIORITY_NORMAL); - while (!m_updateEvent.Wait(1ms)) + // Loop until either the job ended or update canceled via CGUIMediaWindow::CancelUpdateItems. + while (!m_updateAborted && !m_updateEvent.Wait(1ms)) { if (!ProcessRenderLoop(false)) break; } - if (m_updateAborted || !items.m_result) + if (m_updateAborted) { + CLog::LogF(LOGDEBUG, "Get directory items job was canceled."); + ret = false; + } + else if (!items.m_result) + { + CLog::LogF(LOGDEBUG, "Get directory items job was unsuccessful."); ret = false; } } From 3c30614a96067575983db9b5394dd3c857f98670 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Sat, 30 Sep 2023 02:18:20 +0200 Subject: [PATCH 312/811] [PosixTimezone] Refactor timezone reading --- xbmc/platform/posix/PosixTimezone.cpp | 99 ++++++++++++++++----------- xbmc/platform/posix/PosixTimezone.h | 14 ++-- 2 files changed, 67 insertions(+), 46 deletions(-) diff --git a/xbmc/platform/posix/PosixTimezone.cpp b/xbmc/platform/posix/PosixTimezone.cpp index 1a1f7d59fbd5f..823394b4168d8 100644 --- a/xbmc/platform/posix/PosixTimezone.cpp +++ b/xbmc/platform/posix/PosixTimezone.cpp @@ -6,21 +6,23 @@ * See LICENSES/README.md for more information. */ -#include -#include "PlatformDefs.h" #include "PosixTimezone.h" -#include "utils/SystemInfo.h" #include "ServiceBroker.h" -#include "utils/StringUtils.h" #include "XBDateTime.h" -#include "settings/lib/Setting.h" -#include "settings/lib/SettingDefinitions.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" -#include +#include "settings/lib/Setting.h" +#include "settings/lib/SettingDefinitions.h" +#include "utils/StringUtils.h" +#include "utils/SystemInfo.h" #include +#include +#include +#include + +#include "PlatformDefs.h" CPosixTimezone::CPosixTimezone() { @@ -195,44 +197,61 @@ void CPosixTimezone::SetTimezone(const std::string& timezoneName) std::string CPosixTimezone::GetOSConfiguredTimezone() { - char timezoneName[255]; + std::string timezoneName; - // try Slackware approach first - ssize_t rlrc = readlink("/etc/localtime-copied-from" - , timezoneName, sizeof(timezoneName)-1); + // try Slackware approach first + timezoneName = ReadFromLocaltime("/etc/localtime-copied-from"); - // RHEL and maybe other distros make /etc/localtime a symlink - if (rlrc == -1) - rlrc = readlink("/etc/localtime", timezoneName, sizeof(timezoneName)-1); + // RHEL and maybe other distros make /etc/localtime a symlink + if (timezoneName.empty()) + timezoneName = ReadFromLocaltime("/etc/localtime"); - if (rlrc != -1) - { - timezoneName[rlrc] = '\0'; - - char* p = strrchr(timezoneName,'/'); - if (p) - { // we want the previous '/' - char* q = p; - *q = 0; - p = strrchr(timezoneName,'/'); - *q = '/'; - if (p) - p++; - } - return p; - } + // now try Debian approach + if (timezoneName.empty()) + timezoneName = ReadFromTimezone("/etc/timezone"); - // now try Debian approach - timezoneName[0] = 0; - FILE* fp = fopen("/etc/timezone", "r"); - if (fp) - { - if (fgets(timezoneName, sizeof(timezoneName), fp)) - timezoneName[strlen(timezoneName)-1] = '\0'; - fclose(fp); - } + return timezoneName; +} + +std::string CPosixTimezone::ReadFromLocaltime(const std::string_view filename) +{ + char path[PATH_MAX]; + ssize_t len = readlink(filename.data(), path, sizeof(path) - 1); + + if (len == -1) + return ""; + + path[len] = '\0'; + // Read the timezone starting from the second last occurrence of / + std::string str = path; + size_t pos = str.rfind('/'); + if (pos == std::string::npos) + return ""; + + pos = str.rfind('/', pos - 1); + if (pos == std::string::npos) + return ""; + + return str.substr(pos + 1); +} + +std::string CPosixTimezone::ReadFromTimezone(const std::string_view filename) +{ + std::string timezoneName; + std::FILE* file = std::fopen(filename.data(), "r"); + + if (file != nullptr) + { + char tz[255]; + if (std::fgets(tz, sizeof(tz), file) != nullptr) + { + timezoneName = tz; + } + + std::fclose(file); + } - return timezoneName; + return timezoneName; } void CPosixTimezone::SettingOptionsTimezoneCountriesFiller( diff --git a/xbmc/platform/posix/PosixTimezone.h b/xbmc/platform/posix/PosixTimezone.h index 076e87fa51e98..26a7f0934079c 100644 --- a/xbmc/platform/posix/PosixTimezone.h +++ b/xbmc/platform/posix/PosixTimezone.h @@ -46,12 +46,14 @@ class CPosixTimezone : public ISettingCallback, public ISettingsHandler void* data); private: - std::vector m_counties; - std::map m_countryByCode; - std::map m_countryByName; - - std::map > m_timezonesByCountryCode; - std::map m_countriesByTimezoneName; + std::string ReadFromLocaltime(std::string_view filename); + std::string ReadFromTimezone(std::string_view filename); + std::vector m_counties; + std::map m_countryByCode; + std::map m_countryByName; + + std::map> m_timezonesByCountryCode; + std::map m_countriesByTimezoneName; }; extern CPosixTimezone g_timezone; From 4077304453eeb5d0db6e00960ba4dbdea2d0d8dd Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Sun, 27 Aug 2023 16:40:32 +0100 Subject: [PATCH 313/811] [macos] Add smctemp to depends --- cmake/modules/FindSmctemp.cmake | 30 ++++ cmake/platform/osx/osx.cmake | 1 + tools/depends/target/Makefile | 1 + tools/depends/target/smctemp/Makefile | 30 ++++ tools/depends/target/smctemp/SMCTEMP-VERSION | 5 + xbmc/platform/darwin/osx/CMakeLists.txt | 6 +- xbmc/platform/darwin/osx/CPUInfoOsx.cpp | 17 ++- xbmc/platform/darwin/osx/GPUInfoMacOS.cpp | 6 +- xbmc/platform/darwin/osx/smc.c | 148 ------------------- xbmc/platform/darwin/osx/smc.h | 101 ------------- 10 files changed, 86 insertions(+), 259 deletions(-) create mode 100644 cmake/modules/FindSmctemp.cmake create mode 100644 tools/depends/target/smctemp/Makefile create mode 100644 tools/depends/target/smctemp/SMCTEMP-VERSION delete mode 100644 xbmc/platform/darwin/osx/smc.c delete mode 100644 xbmc/platform/darwin/osx/smc.h diff --git a/cmake/modules/FindSmctemp.cmake b/cmake/modules/FindSmctemp.cmake new file mode 100644 index 0000000000000..a96aa3375e2f9 --- /dev/null +++ b/cmake/modules/FindSmctemp.cmake @@ -0,0 +1,30 @@ +#.rst: +# FindSmctemp +# ------- +# Finds the smctemp library +# +# This will define the following imported targets:: +# +# SMCTEMP::SMCTEMP - The smctemp library + +if(NOT TARGET SMCTEMP::SMCTEMP) + + find_path(SMCTEMP_INCLUDE_DIR NAMES smctemp.h + PATHS ${PC_SMCTEMP_INCLUDEDIR} NO_CACHE) + find_library(SMCTEMP_LIBRARY NAMES smctemp + PATHS ${PC_SMCTEMP_LIBDIR} NO_CACHE) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Smctemp + REQUIRED_VARS SMCTEMP_LIBRARY SMCTEMP_INCLUDE_DIR + VERSION_VAR SMCTEMP_VERSION) + + if(SMCTEMP_FOUND) + add_library(SMCTEMP::SMCTEMP UNKNOWN IMPORTED) + set_target_properties(SMCTEMP::SMCTEMP PROPERTIES + IMPORTED_LOCATION "${SMCTEMP_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${SMCTEMP_INCLUDE_DIR}") + + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP SMCTEMP::SMCTEMP) + endif() +endif() diff --git a/cmake/platform/osx/osx.cmake b/cmake/platform/osx/osx.cmake index c46bfbb12efbe..50cea0abb7374 100644 --- a/cmake/platform/osx/osx.cmake +++ b/cmake/platform/osx/osx.cmake @@ -20,3 +20,4 @@ else() endif() set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") +list(APPEND PLATFORM_REQUIRED_DEPS Smctemp) diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index c968d71f8d5ec..9c3f8ca488a8c 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -67,6 +67,7 @@ endif ifeq ($(OS),osx) EXCLUDED_DEPENDS = libusb + DEPENDS += smctemp ifneq ($(CPU),arm64) ifeq ($(WINDOW_SYSTEM),sdl) DEPENDS += libsdl diff --git a/tools/depends/target/smctemp/Makefile b/tools/depends/target/smctemp/Makefile new file mode 100644 index 0000000000000..b257625403b21 --- /dev/null +++ b/tools/depends/target/smctemp/Makefile @@ -0,0 +1,30 @@ +include ../../Makefile.include SMCTEMP-VERSION ../../download-files.include +DEPS = ../../Makefile.include SMCTEMP-VERSION Makefile ../../download-files.include \ + +LIBDYLIB=$(PLATFORM)/$(BYPRODUCT) + +ifeq ($(CPU), arm64) + CXXFLAGS += -DARCH_TYPE_ARM64 +else + CXXFLAGS += -DARCH_TYPE_X86_64 +endif + +all: .installed-$(PLATFORM) + +$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) + rm -rf $(PLATFORM)/*; mkdir -p $(PLATFORM) + cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) + +$(LIBDYLIB): $(PLATFORM) + $(MAKE) CXX="$(CXX)" CXXFLAGS="$(CXXFLAGS)" AR=$(AR) RANLIB=$(RANLIB) -C $(PLATFORM) staticlib + +.installed-$(PLATFORM): $(LIBDYLIB) + $(MAKE) DEST_PREFIX=$(PREFIX) -C $(PLATFORM) installstaticlib + touch $@ + +clean: + $(MAKE) -C $(PLATFORM) clean + rm -f .installed-$(PLATFORM) + +distclean:: + rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/tools/depends/target/smctemp/SMCTEMP-VERSION b/tools/depends/target/smctemp/SMCTEMP-VERSION new file mode 100644 index 0000000000000..4eb38567cbbdd --- /dev/null +++ b/tools/depends/target/smctemp/SMCTEMP-VERSION @@ -0,0 +1,5 @@ +LIBNAME=smctemp +VERSION=0.2.1 +ARCHIVE=$(LIBNAME)-$(VERSION).tar.gz +SHA512=8671836ed3f16122ffc84a1e91b463c8405526b1500fa9f5816a9f9eff1fd598c86958e894d2b8b25ae798da57b536766729b28dcc6b7c69fe2dc3c818f18290 +BYPRODUCT=libsmctemp.a diff --git a/xbmc/platform/darwin/osx/CMakeLists.txt b/xbmc/platform/darwin/osx/CMakeLists.txt index 01db68f8e89b8..3824fd2545ba0 100644 --- a/xbmc/platform/darwin/osx/CMakeLists.txt +++ b/xbmc/platform/darwin/osx/CMakeLists.txt @@ -2,15 +2,13 @@ set(SOURCES CocoaInterface.mm CPUInfoOsx.cpp GPUInfoMacOS.cpp HotKeyController.m - PlatformDarwinOSX.cpp - smc.c) + PlatformDarwinOSX.cpp) set(HEADERS CocoaInterface.h CPUInfoOsx.h GPUInfoMacOS.h HotKeyController.h - PlatformDarwinOSX.h - smc.h) + PlatformDarwinOSX.h) if(ENABLE_XBMCHELPER) list(APPEND SOURCES XBMCHelper.cpp) diff --git a/xbmc/platform/darwin/osx/CPUInfoOsx.cpp b/xbmc/platform/darwin/osx/CPUInfoOsx.cpp index c100d3aac965b..e1eb271ff4c6f 100644 --- a/xbmc/platform/darwin/osx/CPUInfoOsx.cpp +++ b/xbmc/platform/darwin/osx/CPUInfoOsx.cpp @@ -8,14 +8,17 @@ #include "CPUInfoOsx.h" +#include "ServiceBroker.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" #include "utils/Temperature.h" -#include "platform/darwin/osx/smc.h" #include "platform/posix/PosixResourceCounter.h" #include #include +#include #include #include @@ -118,13 +121,19 @@ float CCPUInfoOsx::GetCPUFrequency() bool CCPUInfoOsx::GetTemperature(CTemperature& temperature) { - int value = SMCGetTemperature(SMC_KEY_CPU_TEMP); + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_cpuTempCmd.empty()) + { + return CheckUserTemperatureCommand(temperature); + } - temperature = CTemperature::CreateFromCelsius(value); - if (temperature == CTemperature::CreateFromCelsius(0.0)) + smctemp::SmcTemp smcTemp = smctemp::SmcTemp(); + const double value = smcTemp.GetCpuTemp(); + if (value <= 0.0) { temperature.SetValid(false); + return false; } + temperature = CTemperature::CreateFromCelsius(value); return true; } diff --git a/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp b/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp index 5594d9bbf2730..4d20a54c93e63 100644 --- a/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp +++ b/xbmc/platform/darwin/osx/GPUInfoMacOS.cpp @@ -8,7 +8,7 @@ #include "GPUInfoMacOS.h" -#include "platform/darwin/osx/smc.h" +#include std::unique_ptr CGPUInfo::GetGPUInfo() { @@ -22,9 +22,11 @@ bool CGPUInfoMacOS::SupportsPlatformTemperature() const bool CGPUInfoMacOS::GetGPUPlatformTemperature(CTemperature& temperature) const { - double temperatureValue = SMCGetTemperature(SMC_KEY_GPU_TEMP); + smctemp::SmcTemp smcTemp = smctemp::SmcTemp(); + const double temperatureValue = smcTemp.GetGpuTemp(); if (temperatureValue <= 0.0) { + temperature.SetValid(false); return false; } temperature = CTemperature::CreateFromCelsius(temperatureValue); diff --git a/xbmc/platform/darwin/osx/smc.c b/xbmc/platform/darwin/osx/smc.c deleted file mode 100644 index 559ceab11efa2..0000000000000 --- a/xbmc/platform/darwin/osx/smc.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Apple System Management Control (SMC) Tool - * Copyright (C) 2006 devnull - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSES/README.md for more information. - */ - -#include "smc.h" - -#include -#include - -#include - -static io_connect_t conn; - -UInt32 _strtoul(const char *str, int size, int base) -{ - UInt32 total = 0; - int i; - - for (i = 0; i < size; i++) - { - if (base == 16) - total += str[i] << (size - 1 - i) * 8; - else - total += (unsigned char) (str[i] << (size - 1 - i) * 8); - } - return total; -} - -void _ultostr(char *str, UInt32 val) -{ - str[0] = '\0'; - sprintf(str, "%c%c%c%c", - (unsigned int) val >> 24, - (unsigned int) val >> 16, - (unsigned int) val >> 8, - (unsigned int) val); -} - -kern_return_t SMCOpen(void) -{ - mach_port_t masterPort; - io_iterator_t iterator; - io_object_t device; - - kern_return_t result = IOMasterPort(MACH_PORT_NULL, &masterPort); - - CFMutableDictionaryRef matchingDictionary = IOServiceMatching("AppleSMC"); - result = IOServiceGetMatchingServices(masterPort, matchingDictionary, &iterator); - if (result != kIOReturnSuccess) - { - printf("Error: IOServiceGetMatchingServices() = %08x\n", result); - return 1; - } - - device = IOIteratorNext(iterator); - IOObjectRelease(iterator); - if (device == 0) - { - printf("Error: no SMC found\n"); - return 1; - } - - result = IOServiceOpen(device, mach_task_self(), 0, &conn); - IOObjectRelease(device); - if (result != kIOReturnSuccess) - { - printf("Error: IOServiceOpen() = %08x\n", result); - return 1; - } - - return kIOReturnSuccess; -} - -kern_return_t SMCClose() -{ - return IOServiceClose(conn); -} - - -kern_return_t SMCCall(int index, SMCKeyData_t *inputStructure, SMCKeyData_t *outputStructure) -{ - size_t structureInputSize; - size_t structureOutputSize; - - structureInputSize = sizeof(SMCKeyData_t); - structureOutputSize = sizeof(SMCKeyData_t); - - return IOConnectCallStructMethod( conn, index, - // inputStructure - inputStructure, structureInputSize, - // outputStructure - outputStructure, &structureOutputSize ); -} - -kern_return_t SMCReadKey(UInt32ConstChar_t key, SMCVal_t *val) -{ - kern_return_t result; - SMCKeyData_t inputStructure = {}; - SMCKeyData_t outputStructure = {}; - - memset(val, 0, sizeof(SMCVal_t)); - - inputStructure.key = _strtoul(key, 4, 16); - inputStructure.data8 = SMC_CMD_READ_KEYINFO; - - result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure); - if (result != kIOReturnSuccess) - return result; - - val->dataSize = outputStructure.keyInfo.dataSize; - _ultostr(val->dataType, outputStructure.keyInfo.dataType); - inputStructure.keyInfo.dataSize = val->dataSize; - inputStructure.data8 = SMC_CMD_READ_BYTES; - - result = SMCCall(KERNEL_INDEX_SMC, &inputStructure, &outputStructure); - if (result != kIOReturnSuccess) - return result; - - memcpy(val->bytes, outputStructure.bytes, sizeof(outputStructure.bytes)); - - return kIOReturnSuccess; -} - -double SMCGetTemperature(const char *key) -{ - SMCVal_t val; - kern_return_t result; - SMCOpen(); - result = SMCReadKey(key, &val); - SMCClose(); - if (result == kIOReturnSuccess) { - // read succeeded - check returned value - if (val.dataSize > 0) { - if (strcmp(val.dataType, DATATYPE_SP78) == 0) { - // convert fp78 value to temperature - int intValue = (val.bytes[0] * 256 + val.bytes[1]) >> 2; - return intValue / 64.0; - } - } - } - // read failed - return 0.0; -} - diff --git a/xbmc/platform/darwin/osx/smc.h b/xbmc/platform/darwin/osx/smc.h deleted file mode 100644 index 64fe19418f362..0000000000000 --- a/xbmc/platform/darwin/osx/smc.h +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Apple System Management Control (SMC) Tool - * Copyright (C) 2006 devnull - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSES/README.md for more information. - */ - -#pragma once - -#include - -#define SMC_VERSION "0.01" - -#define OP_NONE 0 -#define OP_LIST 1 -#define OP_READ 2 -#define OP_READ_FAN 3 -#define OP_WRITE 4 - -#define KERNEL_INDEX_SMC 2 - -#define SMC_CMD_READ_BYTES 5 -#define SMC_CMD_WRITE_BYTES 6 -#define SMC_CMD_READ_INDEX 8 -#define SMC_CMD_READ_KEYINFO 9 -#define SMC_CMD_READ_PLIMIT 11 -#define SMC_CMD_READ_VERS 12 - -#define DATATYPE_FPE2 "fpe2" -#define DATATYPE_UINT8 "ui8 " -#define DATATYPE_UINT16 "ui16" -#define DATATYPE_UINT32 "ui32" -#define DATATYPE_SP78 "sp78" - -// key values -#define SMC_KEY_CPU_TEMP "TC0D" -#define SMC_KEY_GPU_TEMP "TG0D" -#define SMC_KEY_FAN0_RPM_MIN "F0Mn" -#define SMC_KEY_FAN1_RPM_MIN "F1Mn" -#define SMC_KEY_FAN0_RPM_CUR "F0Ac" -#define SMC_KEY_FAN1_RPM_CUR "F1Ac" - - -typedef struct { - char major; - char minor; - char build; - char reserved[1]; - UInt16 release; -} SMCKeyData_vers_t; - -typedef struct { - UInt16 version; - UInt16 length; - UInt32 cpuPLimit; - UInt32 gpuPLimit; - UInt32 memPLimit; -} SMCKeyData_pLimitData_t; - -typedef struct { - UInt32 dataSize; - UInt32 dataType; - char dataAttributes; -} SMCKeyData_keyInfo_t; - -typedef char SMCBytes_t[32]; - -typedef struct { - UInt32 key; - SMCKeyData_vers_t vers; - SMCKeyData_pLimitData_t pLimitData; - SMCKeyData_keyInfo_t keyInfo; - char result; - char status; - char data8; - UInt32 data32; - SMCBytes_t bytes; -} SMCKeyData_t; - -typedef const char UInt32ConstChar_t[5]; -typedef char UInt32Char_t[5]; - -typedef struct { - UInt32Char_t key; - UInt32 dataSize; - UInt32Char_t dataType; - SMCBytes_t bytes; -} SMCVal_t; - -#ifdef __cplusplus -extern "C" -{ -#endif - -// prototypes -double SMCGetTemperature(const char *key); - -#ifdef __cplusplus -} -#endif From 85bc6c2db061952079d413638be738ea864a467a Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 21 Sep 2023 21:02:36 +0100 Subject: [PATCH 314/811] [tests] Improve cpu tests from cmd --- xbmc/utils/test/TestCPUInfo.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/utils/test/TestCPUInfo.cpp b/xbmc/utils/test/TestCPUInfo.cpp index b8ca20e70e756..a411e2cfccef4 100644 --- a/xbmc/utils/test/TestCPUInfo.cpp +++ b/xbmc/utils/test/TestCPUInfo.cpp @@ -51,6 +51,7 @@ TEST_F(TestCPUInfo, GetTemperature) CTemperature t; EXPECT_TRUE(CServiceBroker::GetCPUInfo()->GetTemperature(t)); EXPECT_TRUE(t.IsValid()); + EXPECT_EQ(t.ToCelsius(), 50); } TEST_F(TestCPUInfo, CoreInfo) From 8f2925e1f08fcac3792bb7baa57589fb9b86737e Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 21 Sep 2023 21:04:00 +0100 Subject: [PATCH 315/811] [tests] Fixup test name --- xbmc/utils/test/TestGPUInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/utils/test/TestGPUInfo.cpp b/xbmc/utils/test/TestGPUInfo.cpp index 331a62c3bd907..495bc0cc2427e 100644 --- a/xbmc/utils/test/TestGPUInfo.cpp +++ b/xbmc/utils/test/TestGPUInfo.cpp @@ -24,7 +24,7 @@ class TestGPUInfo : public ::testing::Test #if defined(TARGET_WINDOWS) TEST_F(TestGPUInfo, DISABLED_GetTemperatureFromCmd) #else -TEST_F(TestGPUInfo, GetTemperature) +TEST_F(TestGPUInfo, GetTemperatureFromCmd) #endif { CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_gpuTempCmd = "echo '50 c'"; From bcc3d35d6154c98a369406b3247138174fbe46e8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Sun, 1 Oct 2023 10:33:08 +0100 Subject: [PATCH 316/811] [docs] minors for streamdetails --- xbmc/cores/VideoPlayer/DVDFileInfo.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/DVDFileInfo.h b/xbmc/cores/VideoPlayer/DVDFileInfo.h index 542858c7889b1..8bf69f72b6a86 100644 --- a/xbmc/cores/VideoPlayer/DVDFileInfo.h +++ b/xbmc/cores/VideoPlayer/DVDFileInfo.h @@ -55,7 +55,7 @@ class CDVDFileInfo */ static bool AddExternalSubtitleToDetails(const std::string &path, CStreamDetails &details, const std::string& filename, const std::string& subfilename = ""); - /** \brief Checks external subtitles for a giving item and adds any existing to the item stream details + /** \brief Checks external subtitles for a given item and adds any existing ones to the item stream details * \param item The video item * \sa AddExternalSubtitleToDetails */ From 78e4b225484b69bdfc6ba33e451897fd807fd85f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:09:47 +0200 Subject: [PATCH 317/811] [video] Eliminate custom resume string calculation implementations in favour to new function VIDEO_UTILS::GetResumeString. --- xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 36 ++-------------------- xbmc/pvr/guilib/PVRGUIActionsPlayback.h | 8 ----- xbmc/video/ContextMenus.cpp | 5 +-- xbmc/video/VideoUtils.cpp | 28 +++++++++++++++++ xbmc/video/VideoUtils.h | 7 +++++ xbmc/video/windows/GUIWindowVideoBase.cpp | 37 +++-------------------- xbmc/video/windows/GUIWindowVideoBase.h | 6 ---- 7 files changed, 45 insertions(+), 82 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index 038f1d5ecab4d..9e8878eaf71c6 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -10,7 +10,6 @@ #include "FileItem.h" #include "ServiceBroker.h" -#include "Util.h" #include "application/ApplicationEnums.h" #include "cores/DataCacheCore.h" #include "dialogs/GUIDialogContextMenu.h" @@ -57,38 +56,10 @@ CPVRGUIActionsPlayback::CPVRGUIActionsPlayback() { } -std::string CPVRGUIActionsPlayback::GetResumeLabel(const CFileItem& item) const -{ - const VIDEO_UTILS::ResumeInformation resumeInfo = VIDEO_UTILS::GetItemResumeInformation(item); - if (resumeInfo.isResumable) - { - if (resumeInfo.startOffset > 0) - { - std::string resumeString = StringUtils::Format( - g_localizeStrings.Get(12022), - StringUtils::SecondsToTimeString( - static_cast(CUtil::ConvertMilliSecsToSecsInt(resumeInfo.startOffset)), - TIME_FORMAT_HH_MM_SS)); - if (resumeInfo.partNumber > 0) - { - const std::string partString = - StringUtils::Format(g_localizeStrings.Get(23051), resumeInfo.partNumber); - resumeString += " (" + partString + ")"; - } - return resumeString; - } - else - { - return g_localizeStrings.Get(13362); // Continue watching - } - } - return {}; -} - bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem& item) const { bool bPlayIt(true); - std::string resumeString(GetResumeLabel(item)); + const std::string resumeString = VIDEO_UTILS::GetResumeString(item); if (!resumeString.empty()) { CContextButtons choices; @@ -106,8 +77,7 @@ bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem& item) const bool CPVRGUIActionsPlayback::ResumePlayRecording(const CFileItem& item, bool bFallbackToPlay) const { - bool bCanResume = !GetResumeLabel(item).empty(); - if (bCanResume) + if (VIDEO_UTILS::GetItemResumeInformation(item).isResumable) { const_cast(&item)->SetStartOffset(STARTOFFSET_RESUME); } @@ -119,7 +89,7 @@ bool CPVRGUIActionsPlayback::ResumePlayRecording(const CFileItem& item, bool bFa return false; } - return PlayRecording(item, false); + return PlayRecording(item, false /* skip resume check */); } void CPVRGUIActionsPlayback::CheckAndSwitchToFullscreen(bool bFullscreen) const diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.h b/xbmc/pvr/guilib/PVRGUIActionsPlayback.h index e5b50bf0cf43e..91016d40db045 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.h +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.h @@ -32,14 +32,6 @@ class CPVRGUIActionsPlayback : public IPVRComponent CPVRGUIActionsPlayback(); ~CPVRGUIActionsPlayback() override = default; - /*! - * @brief Get a localized resume play label, if the given item can be resumed. - * @param item containing a recording or an epg tag. - * @return the localized resume play label that can be used for instance as context menu item - * label or an empty string if resume is not possible. - */ - std::string GetResumeLabel(const CFileItem& item) const; - /*! * @brief Resume a previously not completely played recording. * @param item containing a recording or an epg tag. diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index 0fadc7efe629b..c72c321e6687d 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -9,6 +9,7 @@ #include "ContextMenus.h" #include "Autorun.h" +#include "FileItem.h" #include "GUIUserMessages.h" #include "ServiceBroker.h" #include "application/Application.h" @@ -16,9 +17,9 @@ #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "utils/URIUtils.h" +#include "video/VideoInfoTag.h" #include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" -#include "video/windows/GUIWindowVideoBase.h" #include @@ -156,7 +157,7 @@ bool CVideoBrowse::Execute(const std::shared_ptr& item) const std::string CVideoResume::GetLabel(const CFileItem& item) const { - return CGUIWindowVideoBase::GetResumeString(item.GetItemToPlay()); + return VIDEO_UTILS::GetResumeString(item.GetItemToPlay()); } bool CVideoResume::IsVisible(const CFileItem& itemIn) const diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index c42674d1b9d45..ba3afaaad950a 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -22,6 +22,7 @@ #include "filesystem/VideoDatabaseDirectory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" #include "settings/MediaSettings.h" @@ -768,4 +769,31 @@ ResumeInformation GetItemResumeInformation(const CFileItem& item) return GetFolderItemResumeInformation(item); } +std::string GetResumeString(const CFileItem& item) +{ + const ResumeInformation resumeInfo = GetItemResumeInformation(item); + if (resumeInfo.isResumable) + { + if (resumeInfo.startOffset > 0) + { + std::string resumeString = StringUtils::Format( + g_localizeStrings.Get(12022), + StringUtils::SecondsToTimeString( + static_cast(CUtil::ConvertMilliSecsToSecsInt(resumeInfo.startOffset)), + TIME_FORMAT_HH_MM_SS)); + if (resumeInfo.partNumber > 0) + { + const std::string partString = + StringUtils::Format(g_localizeStrings.Get(23051), resumeInfo.partNumber); + resumeString += " (" + partString + ")"; + } + return resumeString; + } + else + { + return g_localizeStrings.Get(13362); // Continue watching + } + } + return {}; +} } // namespace VIDEO_UTILS diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h index 826af6319a886..b61ddafe2dea5 100644 --- a/xbmc/video/VideoUtils.h +++ b/xbmc/video/VideoUtils.h @@ -82,4 +82,11 @@ struct ResumeInformation */ ResumeInformation GetItemResumeInformation(const CFileItem& item); +/*! + \brief Get a localized resume string for the given item, if it is resumable. + \param item The item to retrieve the resume string for + \return The resume string or empty string in case the item is not resumable. + */ +std::string GetResumeString(const CFileItem& item); + } // namespace VIDEO_UTILS diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 2d9d522a05a31..50d9287cd7681 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -556,7 +556,7 @@ bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& choices.Add(SELECT_ACTION_PLAYPART, 20324); // Play Part } - std::string resumeString = GetResumeString(*item); + const std::string resumeString = VIDEO_UTILS::GetResumeString(*item); if (!resumeString.empty()) { choices.Add(SELECT_ACTION_RESUME, resumeString); @@ -726,39 +726,11 @@ void CGUIWindowVideoBase::LoadVideoInfo(CFileItemList& items, } } -std::string CGUIWindowVideoBase::GetResumeString(const CFileItem &item) -{ - const VIDEO_UTILS::ResumeInformation resumeInfo = VIDEO_UTILS::GetItemResumeInformation(item); - if (resumeInfo.isResumable) - { - if (resumeInfo.startOffset > 0) - { - std::string resumeString = StringUtils::Format( - g_localizeStrings.Get(12022), - StringUtils::SecondsToTimeString( - static_cast(CUtil::ConvertMilliSecsToSecsInt(resumeInfo.startOffset)), - TIME_FORMAT_HH_MM_SS)); - if (resumeInfo.partNumber > 0) - { - const std::string partString = - StringUtils::Format(g_localizeStrings.Get(23051), resumeInfo.partNumber); - resumeString += " (" + partString + ")"; - } - return resumeString; - } - else - { - return g_localizeStrings.Get(13362); // Continue watching - } - } - return {}; -} - bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item) { if (!item.IsLiveTV()) { - std::string resumeString = GetResumeString(item); + const std::string resumeString = VIDEO_UTILS::GetResumeString(item); if (!resumeString.empty()) { // prompt user whether they wish to resume CContextButtons choices; @@ -779,8 +751,7 @@ bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player) if (iItem < 0 || iItem >= m_vecItems->Size()) return true; CFileItemPtr item = m_vecItems->Get(iItem); - std::string resumeString = GetResumeString(*item); - + const std::string resumeString = VIDEO_UTILS::GetResumeString(*item); if (!resumeString.empty()) { CContextButtons choices; @@ -904,7 +875,7 @@ bool CGUIWindowVideoBase::OnPlayStackPart(int iItem) // ISO stack if (CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) { - std::string resumeString = CGUIWindowVideoBase::GetResumeString(*(parts[selectedFile].get())); + const std::string resumeString = VIDEO_UTILS::GetResumeString(*(parts[selectedFile].get())); stack->SetStartOffset(0); if (!resumeString.empty()) { diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index afb18012363e0..b911de76af6c9 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -68,12 +68,6 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse */ static void OnAssignContent(const std::string &path); - /*! \brief checks the database for a resume position and puts together a string - \param item selected item - \return string containing the resume position or an empty string if there is no resume position - */ - static std::string GetResumeString(const CFileItem &item); - /*! \brief Load video information from the database for these items (public static version) Useful for grabbing information for file listings, from watched status to full metadata \param items the items to load information for. From 29785dd689a40102e2d5792464e0de0a6b3798b7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 29 Sep 2023 08:02:07 +0200 Subject: [PATCH 318/811] [video] Factor out video select action processing. --- .../resources/strings.po | 24 +- cmake/treedata/common/video.txt | 1 + xbmc/listproviders/DirectoryProvider.cpp | 7 +- xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 2 + xbmc/video/guilib/CMakeLists.txt | 6 + xbmc/video/guilib/VideoSelectAction.h | 29 ++ .../guilib/VideoSelectActionProcessor.cpp | 152 ++++++++++ .../video/guilib/VideoSelectActionProcessor.h | 48 +++ xbmc/video/windows/GUIWindowVideoBase.cpp | 284 +++++++----------- xbmc/video/windows/GUIWindowVideoBase.h | 23 +- 10 files changed, 381 insertions(+), 195 deletions(-) create mode 100644 xbmc/video/guilib/CMakeLists.txt create mode 100644 xbmc/video/guilib/VideoSelectAction.h create mode 100644 xbmc/video/guilib/VideoSelectActionProcessor.cpp create mode 100644 xbmc/video/guilib/VideoSelectActionProcessor.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 2bfe7e1992938..0a0190cc18598 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -955,9 +955,8 @@ msgstr "" #: xbmc/dialogs/GUIDialogPlayEject.cpp #: xbmc/games/windows/GUIWindowGames.cpp #: xbmc/music/ContextMenus.h -#: xbmc/music/MusicUtils.cpp #: xbmc/video/ContextMenus.cpp -#: xbmc/video/windows/GUIWindowVideoBase.cpp +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp #: system/settings/settings.xml msgctxt "#208" msgid "Play" @@ -5723,10 +5722,9 @@ msgstr "" #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml #: xbmc/Autorun.cpp -#: xbmc/pvr/PVRContextMenus.cpp -#: xbmc/pvr/PVRGUIActions.cpp +#: xbmc/pvr/PVRGUIActionsPlayback.cpp #: xbmc/video/ContextMenus.cpp -#: xbmc/video/windows/GUIWindowVideoBase.cpp +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp msgctxt "#12021" msgid "Play from beginning" msgstr "" @@ -6928,11 +6926,13 @@ msgctxt "#13346" msgid "Movie information" msgstr "" +#. Label for 'queue item' (adds an item to a playlist), used at several places #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml #: system/settings/settings.xml #: xbmc/music/ContextMenus.h -#: xbmc/video/ContextMenus.cpp +#: xbmc/video/ContextMenus.h +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp msgctxt "#13347" msgid "Queue item" msgstr "" @@ -13303,6 +13303,9 @@ msgctxt "#20323" msgid "Movie plot" msgstr "" +#. Label for context menu entry to start playback of a part of a video stack +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp +#: xbmc/video/windows/GUIWindowVideoBase.cpp msgctxt "#20324" msgid "Play part..." msgstr "" @@ -15318,11 +15321,17 @@ msgctxt "#22080" msgid "Choose" msgstr "" +#. Label for video select action 'show info' +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp #: system/settings/settings.xml msgctxt "#22081" msgid "Show information" msgstr "" +#. Label to denote that there is something 'more' +#: xbmc/listproviders/DirectoryProvider.cpp +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp +#: system/settings/settings.xml msgctxt "#22082" msgid "More..." msgstr "" @@ -15342,6 +15351,9 @@ msgctxt "#23050" msgid "Activate teletext" msgstr "" +#. Label for disc stack part and number +#: xbmc/video/guilib/VideoSelectActionProcessor.cpp +#: xbmc/video/VideoUtils.cpp msgctxt "#23051" msgid "Part {0:d}" msgstr "" diff --git a/cmake/treedata/common/video.txt b/cmake/treedata/common/video.txt index ab534535d3f94..9349eaeafcca1 100644 --- a/cmake/treedata/common/video.txt +++ b/cmake/treedata/common/video.txt @@ -1,5 +1,6 @@ xbmc/video video xbmc/video/dialogs video/dialogs +xbmc/video/guilib video/guilib xbmc/video/jobs video/jobs xbmc/video/tags video/tags xbmc/video/windows video/windows diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 0062f82054942..aaf0418d7031a 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -471,9 +471,10 @@ bool CDirectoryProvider::OnClick(const CGUIListItemPtr &item) { CFileItem fileItem(*std::static_pointer_cast(item)); - if (fileItem.HasVideoInfoTag() - && CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION) == SELECT_ACTION_INFO - && OnInfo(item)) + if (fileItem.HasVideoInfoTag() && + CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_MYVIDEOS_SELECTACTION) == VIDEO::GUILIB::SELECT_ACTION_INFO && + OnInfo(item)) return true; if (fileItem.HasProperty("node.target_url")) diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index ff402542fd1c7..4a3f4d7fc36a0 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -28,6 +28,7 @@ #include "settings/SettingsComponent.h" #include "utils/URIUtils.h" #include "video/VideoLibraryQueue.h" +#include "video/guilib/VideoSelectAction.h" #include "video/windows/GUIWindowVideoBase.h" #include @@ -35,6 +36,7 @@ #include using namespace PVR; +using namespace VIDEO::GUILIB; CGUIWindowPVRRecordingsBase::CGUIWindowPVRRecordingsBase(bool bRadio, int id, diff --git a/xbmc/video/guilib/CMakeLists.txt b/xbmc/video/guilib/CMakeLists.txt new file mode 100644 index 0000000000000..fd16dee4cbef9 --- /dev/null +++ b/xbmc/video/guilib/CMakeLists.txt @@ -0,0 +1,6 @@ +set(SOURCES VideoSelectActionProcessor.cpp) + +set(HEADERS VideoSelectAction.h + VideoSelectActionProcessor.h) + +core_add_library(video_guilib) diff --git a/xbmc/video/guilib/VideoSelectAction.h b/xbmc/video/guilib/VideoSelectAction.h new file mode 100644 index 0000000000000..731e0cf35bbf1 --- /dev/null +++ b/xbmc/video/guilib/VideoSelectAction.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 VIDEO +{ +namespace GUILIB +{ +// Note: Do not change the numerical values of the elements. Some of them are used as values for +// the integer setting SETTING_MYVIDEOS_SELECTACTION. +enum SelectAction +{ + SELECT_ACTION_CHOOSE = 0, + SELECT_ACTION_PLAY_OR_RESUME = 1, + SELECT_ACTION_RESUME = 2, + SELECT_ACTION_INFO = 3, + SELECT_ACTION_MORE = 4, + SELECT_ACTION_PLAY = 5, + SELECT_ACTION_PLAYPART = 6, + SELECT_ACTION_QUEUE = 7, +}; +} // namespace GUILIB +} // namespace VIDEO diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.cpp b/xbmc/video/guilib/VideoSelectActionProcessor.cpp new file mode 100644 index 0000000000000..7a75a6bad3241 --- /dev/null +++ b/xbmc/video/guilib/VideoSelectActionProcessor.cpp @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "VideoSelectActionProcessor.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogSelect.h" +#include "filesystem/Directory.h" +#include "filesystem/StackDirectory.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "video/VideoInfoTag.h" +#include "video/VideoUtils.h" + +using namespace VIDEO::GUILIB; + +SelectAction CVideoSelectActionProcessorBase::GetDefaultSelectAction() +{ + return static_cast(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_MYVIDEOS_SELECTACTION)); +} + +bool CVideoSelectActionProcessorBase::Process() +{ + return Process(GetDefaultSelectAction()); +} + +bool CVideoSelectActionProcessorBase::Process(SelectAction selectAction) +{ + switch (selectAction) + { + case SELECT_ACTION_CHOOSE: + { + const SelectAction action = ChooseVideoItemSelectAction(); + if (action < 0) + return true; // User cancelled the context menu. We're done. + + return Process(action); + } + + case SELECT_ACTION_PLAY_OR_RESUME: + { + const SelectAction action = ChoosePlayOrResume(m_item); + if (action < 0) + return true; // User cancelled the select menu. We're done. + + return Process(action); + } + + case SELECT_ACTION_PLAYPART: + { + const unsigned int part = ChooseStackItemPartNumber(); + if (part < 1) // part numbers are 1-based + return false; + + return OnPlayPartSelected(part); + } + + case SELECT_ACTION_RESUME: + return OnResumeSelected(); + + case SELECT_ACTION_PLAY: + return OnPlaySelected(); + + case SELECT_ACTION_QUEUE: + return OnQueueSelected(); + + case SELECT_ACTION_INFO: + return OnInfoSelected(); + + case SELECT_ACTION_MORE: + return OnMoreSelected(); + + default: + break; + } + return false; // We did not handle the action. +} + +unsigned int CVideoSelectActionProcessorBase::ChooseStackItemPartNumber() const +{ + CFileItemList parts; + XFILE::CDirectory::GetDirectory(m_item.GetDynPath(), parts, "", XFILE::DIR_FLAG_DEFAULTS); + + for (int i = 0; i < parts.Size(); ++i) + parts[i]->SetLabel(StringUtils::Format(g_localizeStrings.Get(23051), i + 1)); // Part # + + CGUIDialogSelect* dialog = + CServiceBroker::GetGUI()->GetWindowManager().GetWindow( + WINDOW_DIALOG_SELECT); + + dialog->Reset(); + dialog->SetHeading(CVariant{20324}); // Play part... + dialog->SetItems(parts); + dialog->Open(); + + if (!dialog->IsConfirmed()) + return 0; // User cancelled the dialog. + + return dialog->GetSelectedItem() + 1; // part numbers are 1-based +} + +SelectAction CVideoSelectActionProcessorBase::ChoosePlayOrResume(const CFileItem& item) +{ + SelectAction action = SELECT_ACTION_PLAY; + + const std::string resumeString = VIDEO_UTILS::GetResumeString(item); + if (!resumeString.empty()) + { + CContextButtons choices; + + choices.Add(SELECT_ACTION_RESUME, resumeString); + choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning + + action = static_cast(CGUIDialogContextMenu::ShowAndGetChoice(choices)); + } + + return action; +} + +SelectAction CVideoSelectActionProcessorBase::ChooseVideoItemSelectAction() const +{ + CContextButtons choices; + + const std::string resumeString = VIDEO_UTILS::GetResumeString(m_item); + if (!resumeString.empty()) + { + choices.Add(SELECT_ACTION_RESUME, resumeString); + choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning + } + else + { + choices.Add(SELECT_ACTION_PLAY, 208); // Play + } + + choices.Add(SELECT_ACTION_INFO, 22081); // Show information + choices.Add(SELECT_ACTION_QUEUE, 13347); // Queue item + choices.Add(SELECT_ACTION_MORE, 22082); // More + + return static_cast(CGUIDialogContextMenu::ShowAndGetChoice(choices)); +} diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.h b/xbmc/video/guilib/VideoSelectActionProcessor.h new file mode 100644 index 0000000000000..c896a386da7df --- /dev/null +++ b/xbmc/video/guilib/VideoSelectActionProcessor.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "video/guilib/VideoSelectAction.h" + +class CFileItem; + +namespace VIDEO +{ +namespace GUILIB +{ +class CVideoSelectActionProcessorBase +{ +public: + explicit CVideoSelectActionProcessorBase(CFileItem& item) : m_item(item) {} + virtual ~CVideoSelectActionProcessorBase() = default; + + static SelectAction GetDefaultSelectAction(); + + bool Process(); + bool Process(SelectAction selectAction); + + static SelectAction ChoosePlayOrResume(const CFileItem& item); + +protected: + virtual bool OnPlayPartSelected(unsigned int part) = 0; + virtual bool OnResumeSelected() = 0; + virtual bool OnPlaySelected() = 0; + virtual bool OnQueueSelected() = 0; + virtual bool OnInfoSelected() = 0; + virtual bool OnMoreSelected() = 0; + + CFileItem& m_item; + +private: + CVideoSelectActionProcessorBase() = delete; + SelectAction ChooseVideoItemSelectAction() const; + unsigned int ChooseStackItemPartNumber() const; +}; +} // namespace GUILIB +} // namespace VIDEO diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 50d9287cd7681..d9d70db760ba7 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -57,11 +57,13 @@ #include "video/VideoLibraryQueue.h" #include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include "view/GUIViewState.h" using namespace XFILE; using namespace VIDEODATABASEDIRECTORY; using namespace VIDEO; +using namespace VIDEO::GUILIB; using namespace ADDON; using namespace PVR; using namespace KODI::MESSAGING; @@ -168,7 +170,7 @@ bool CGUIWindowVideoBase::OnMessage(CGUIMessage& message) } // not playing video, or playback speed == 1 - return OnResumeItem(iItem); + return OnPlayOrResumeItem(iItem); } else if (iAction == ACTION_DELETE_ITEM) { @@ -507,11 +509,6 @@ void CGUIWindowVideoBase::OnQueueItem(int iItem, bool first) m_viewControl.SetSelectedItem(iItem + 1); } -bool CGUIWindowVideoBase::OnClick(int iItem, const std::string &player) -{ - return CGUIMediaWindow::OnClick(iItem, player); -} - bool CGUIWindowVideoBase::OnSelect(int iItem) { if (iItem < 0 || iItem >= m_vecItems->Size()) @@ -525,88 +522,90 @@ bool CGUIWindowVideoBase::OnSelect(int iItem) !StringUtils::StartsWith(path, "newplaylist://") && !StringUtils::StartsWith(path, "newtag://") && !StringUtils::StartsWith(path, "script://")) - return OnFileAction(iItem, CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION), ""); + return OnFileAction(iItem, CVideoSelectActionProcessorBase::GetDefaultSelectAction(), ""); return CGUIMediaWindow::OnSelect(iItem); } -bool CGUIWindowVideoBase::OnFileAction(int iItem, int action, const std::string& player) +namespace { - CFileItemPtr item = m_vecItems->Get(iItem); - if (!item) +class CVideoSelectActionProcessor : public CVideoSelectActionProcessorBase +{ +public: + CVideoSelectActionProcessor(CGUIWindowVideoBase& window, + CFileItem& item, + int itemIndex, + const std::string& player) + : CVideoSelectActionProcessorBase(item), + m_window(window), + m_itemIndex(itemIndex), + m_player(player) { - return false; + // Reset the current start offset. The actual resume + // option is set by the processor, based on the action passed. + m_item.SetStartOffset(0); } - // Reset the current start offset. The actual resume - // option is set in the switch, based on the action passed. - item->SetStartOffset(0); - - switch (action) +protected: + bool OnPlayPartSelected(unsigned int part) override { - case SELECT_ACTION_CHOOSE: - { - CContextButtons choices; - - if (item->IsVideoDb()) - { - std::string itemPath(item->GetPath()); - itemPath = item->GetVideoInfoTag()->m_strFileNameAndPath; - if (URIUtils::IsStack(itemPath) && CFileItem(CStackDirectory::GetFirstStackedFile(itemPath),false).IsDiscImage()) - choices.Add(SELECT_ACTION_PLAYPART, 20324); // Play Part - } - - const std::string resumeString = VIDEO_UTILS::GetResumeString(*item); - if (!resumeString.empty()) - { - choices.Add(SELECT_ACTION_RESUME, resumeString); - choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning - } - else - choices.Add(SELECT_ACTION_PLAY, 208); // Play - - choices.Add(SELECT_ACTION_INFO, 22081); // Info - choices.Add(SELECT_ACTION_MORE, 22082); // More - int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); - if (value < 0) - return true; + return m_window.OnPlayStackPart(m_itemIndex, part); + } - return OnFileAction(iItem, value, player); - } - break; - case SELECT_ACTION_PLAY_OR_RESUME: - return OnResumeItem(iItem, player); - case SELECT_ACTION_INFO: - return OnItemInfo(iItem); - case SELECT_ACTION_MORE: - OnPopupMenu(iItem); - return true; - case SELECT_ACTION_RESUME: - item->SetStartOffset(STARTOFFSET_RESUME); - if (item->m_bIsFolder) + bool OnResumeSelected() override + { + m_item.SetStartOffset(STARTOFFSET_RESUME); + if (m_item.m_bIsFolder) { - PlayItem(iItem, player); + // resume playback of the folder + m_window.PlayItem(m_itemIndex, m_player); return true; } - break; - case SELECT_ACTION_PLAYPART: - if (!OnPlayStackPart(iItem)) - return false; - break; - case SELECT_ACTION_QUEUE: - OnQueueItem(iItem); - return true; - case SELECT_ACTION_PLAY: - if (item->m_bIsFolder) + // resume playback of the video + return m_window.OnClick(m_itemIndex); + } + + bool OnPlaySelected() override + { + if (m_item.m_bIsFolder) { - PlayItem(iItem, player); + // play the folder + m_window.PlayItem(m_itemIndex, m_player); return true; } - break; - default: - break; + // play the video + return m_window.OnClick(m_itemIndex); + } + + bool OnQueueSelected() override + { + m_window.OnQueueItem(m_itemIndex); + return true; + } + + bool OnInfoSelected() override { return m_window.OnItemInfo(m_itemIndex); } + + bool OnMoreSelected() override + { + m_window.OnPopupMenu(m_itemIndex); + return true; } - return OnClick(iItem, player); + +private: + CGUIWindowVideoBase& m_window; + const int m_itemIndex{-1}; + const std::string m_player; +}; +} // namespace + +bool CGUIWindowVideoBase::OnFileAction(int iItem, SelectAction action, const std::string& player) +{ + const std::shared_ptr item = m_vecItems->Get(iItem); + if (!item) + return false; + + CVideoSelectActionProcessor proc(*this, *item, iItem, player); + return proc.Process(action); } bool CGUIWindowVideoBase::OnItemInfo(int iItem) @@ -730,47 +729,18 @@ bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item) { if (!item.IsLiveTV()) { - const std::string resumeString = VIDEO_UTILS::GetResumeString(item); - if (!resumeString.empty()) - { // prompt user whether they wish to resume - CContextButtons choices; - choices.Add(1, resumeString); - choices.Add(2, 12021); // Play from beginning - int retVal = CGUIDialogContextMenu::ShowAndGetChoice(choices); - if (retVal < 0) - return false; // don't do anything - if (retVal == 1) - item.SetStartOffset(STARTOFFSET_RESUME); - } + const SelectAction action = CVideoSelectActionProcessor::ChoosePlayOrResume(item); + if (action == SELECT_ACTION_RESUME) + item.SetStartOffset(STARTOFFSET_RESUME); + else if (action != SELECT_ACTION_PLAY) + return false; // don't do anything } return true; } -bool CGUIWindowVideoBase::OnResumeItem(int iItem, const std::string &player) +bool CGUIWindowVideoBase::OnPlayOrResumeItem(int iItem, const std::string& player) { - if (iItem < 0 || iItem >= m_vecItems->Size()) return true; - CFileItemPtr item = m_vecItems->Get(iItem); - - const std::string resumeString = VIDEO_UTILS::GetResumeString(*item); - if (!resumeString.empty()) - { - CContextButtons choices; - choices.Add(SELECT_ACTION_RESUME, resumeString); - choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning - int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); - if (value < 0) - return true; - return OnFileAction(iItem, value, player); - } - - if (item->m_bIsFolder) - { - // resuming directories isn't fully supported yet. play all of its content. - PlayItem(iItem, player); - return true; - } - - return OnFileAction(iItem, SELECT_ACTION_PLAY, player); + return OnFileAction(iItem, SELECT_ACTION_PLAY_OR_RESUME, player); } void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &buttons) @@ -840,78 +810,55 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but CGUIMediaWindow::GetContextButtons(itemNumber, buttons); } -bool CGUIWindowVideoBase::OnPlayStackPart(int iItem) +bool CGUIWindowVideoBase::OnPlayStackPart(int itemIndex, unsigned int partNumber) { - if (iItem < 0 || iItem >= m_vecItems->Size()) + // part numbers are 1-based. + if (partNumber < 1) return false; - CFileItemPtr stack = m_vecItems->Get(iItem); - std::string path(stack->GetPath()); - if (stack->IsVideoDb()) - path = stack->GetVideoInfoTag()->m_strFileNameAndPath; - - if (!URIUtils::IsStack(path)) + if (itemIndex < 0 || itemIndex >= m_vecItems->Size()) return false; - CFileItemList parts; - CDirectory::GetDirectory(path, parts, "", DIR_FLAG_DEFAULTS); + const std::shared_ptr item = m_vecItems->Get(itemIndex); + const std::string path = item->GetDynPath(); - for (int i = 0; i < parts.Size(); i++) - parts[i]->SetLabel(StringUtils::Format(g_localizeStrings.Get(23051), i + 1)); - - CGUIDialogSelect* pDialog = CServiceBroker::GetGUI()->GetWindowManager().GetWindow(WINDOW_DIALOG_SELECT); + if (!URIUtils::IsStack(path)) + return false; - pDialog->Reset(); - pDialog->SetHeading(CVariant{20324}); - pDialog->SetItems(parts); - pDialog->Open(); + if (CFileItem(CStackDirectory::GetFirstStackedFile(path), false).IsDiscImage()) + { + // disc image stack + CFileItemList parts; + CDirectory::GetDirectory(path, parts, "", DIR_FLAG_DEFAULTS); - if (!pDialog->IsConfirmed()) - return false; + const int value = CVideoSelectActionProcessor::ChoosePlayOrResume(*parts[partNumber - 1]); + if (value == SELECT_ACTION_RESUME) + { + const VIDEO_UTILS::ResumeInformation resumeInfo = + VIDEO_UTILS::GetItemResumeInformation(*parts[partNumber - 1]); + item->SetStartOffset(resumeInfo.startOffset); + } + else if (value != SELECT_ACTION_PLAY) + return false; // if not selected PLAY, then we changed our mind so return - int selectedFile = pDialog->GetSelectedItem(); - if (selectedFile >= 0) + item->m_lStartPartNumber = partNumber; + } + else { - // ISO stack - if (CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) + // video file stack + if (partNumber > 1) { - const std::string resumeString = VIDEO_UTILS::GetResumeString(*(parts[selectedFile].get())); - stack->SetStartOffset(0); - if (!resumeString.empty()) - { - CContextButtons choices; - choices.Add(SELECT_ACTION_RESUME, resumeString); - choices.Add(SELECT_ACTION_PLAY, 12021); // Play from beginning - int value = CGUIDialogContextMenu::ShowAndGetChoice(choices); - if (value == SELECT_ACTION_RESUME) - { - const VIDEO_UTILS::ResumeInformation resumeInfo = - VIDEO_UTILS::GetItemResumeInformation(*parts[selectedFile]); - stack->SetStartOffset(resumeInfo.startOffset); - stack->m_lStartPartNumber = resumeInfo.partNumber; - } - else if (value != SELECT_ACTION_PLAY) - return false; // if not selected PLAY, then we changed our mind so return - } - stack->m_lStartPartNumber = selectedFile + 1; + std::vector times; + if (m_database.GetStackTimes(path, times)) + item->SetStartOffset(times[partNumber - 1]); } - // regular stack else { - if (selectedFile > 0) - { - std::vector times; - if (m_database.GetStackTimes(path,times)) - stack->SetStartOffset(times[selectedFile - 1]); - } - else - stack->SetStartOffset(0); + item->SetStartOffset(0); } - - } - - return true; + // play the video + return OnClick(itemIndex); } bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) @@ -928,14 +875,7 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) } case CONTEXT_BUTTON_PLAY_PART: { - if (OnPlayStackPart(itemNumber)) - { - // call CGUIMediaWindow::OnClick() as otherwise autoresume will kick in - CGUIMediaWindow::OnClick(itemNumber); - return true; - } - else - return false; + return OnFileAction(itemNumber, SELECT_ACTION_PLAYPART, ""); } case CONTEXT_BUTTON_PLAY_WITH: { @@ -956,7 +896,7 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) // any other select actions but play or resume, resume, play or playpart // don't make any sense here since the user already decided that he'd // like to play the item (just with a specific player) - VideoSelectAction selectAction = (VideoSelectAction)CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(CSettings::SETTING_MYVIDEOS_SELECTACTION); + SelectAction selectAction = CVideoSelectActionProcessorBase::GetDefaultSelectAction(); if (selectAction != SELECT_ACTION_PLAY_OR_RESUME && selectAction != SELECT_ACTION_RESUME && selectAction != SELECT_ACTION_PLAY && diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index b911de76af6c9..5776de227a2e8 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -11,22 +11,18 @@ #include "playlists/PlayListTypes.h" #include "video/VideoDatabase.h" #include "video/VideoThumbLoader.h" +#include "video/guilib/VideoSelectAction.h" #include "windows/GUIMediaWindow.h" -enum VideoSelectAction +namespace { - SELECT_ACTION_CHOOSE = 0, - SELECT_ACTION_PLAY_OR_RESUME, - SELECT_ACTION_RESUME, - SELECT_ACTION_INFO, - SELECT_ACTION_MORE, - SELECT_ACTION_PLAY, - SELECT_ACTION_PLAYPART, - SELECT_ACTION_QUEUE -}; +class CVideoSelectActionProcessor; +} // unnamed namespace class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObserver { + friend class ::CVideoSelectActionProcessor; + public: CGUIWindowVideoBase(int id, const std::string &xmlFile); ~CGUIWindowVideoBase(void) override; @@ -96,7 +92,6 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse virtual void DoSearch(const std::string& strSearch, CFileItemList& items) {} std::string GetStartFolder(const std::string &dir) override; - bool OnClick(int iItem, const std::string &player = "") override; bool OnSelect(int iItem) override; /*! \brief react to an Info action on a view item \param item the selected item @@ -108,10 +103,10 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse \param action the action to perform \return true if the action is performed, false otherwise */ - bool OnFileAction(int item, int action, const std::string& player); + bool OnFileAction(int item, VIDEO::GUILIB::SelectAction action, const std::string& player); void OnRestartItem(int iItem, const std::string &player = ""); - bool OnResumeItem(int iItem, const std::string &player = ""); + bool OnPlayOrResumeItem(int iItem, const std::string& player = ""); void PlayItem(int iItem, const std::string &player = ""); bool OnPlayMedia(int iItem, const std::string &player = "") override; bool OnPlayAndQueueMedia(const CFileItemPtr& item, const std::string& player = "") override; @@ -128,7 +123,7 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse static bool StackingAvailable(const CFileItemList &items); - bool OnPlayStackPart(int item); + bool OnPlayStackPart(int itemIndex, unsigned int partNumber); CGUIDialogProgress* m_dlgProgress; CVideoDatabase m_database; From 8bf9fb513655cb7096be11d862ef1bd983c5a972 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:33:58 +0200 Subject: [PATCH 319/811] [strings] help text for default video select action: Enhance and complete (add decription for 'information' and 'queue'). --- addons/resource.language.en_gb/resources/strings.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 0a0190cc18598..cb5a0b9cfd31d 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -19487,7 +19487,7 @@ msgstr "" #. Description of setting with label #22079 "Default select action" #: system/settings/settings.xml msgctxt "#36177" -msgid "Toggle between [Choose], [Play] (default), [Resume] and [Show information].[CR][Choose] Will select an item, e.g. open a directory in files mode.[CR][Resume] Will automatically resume videos from the last position that you were viewing them, even after restarting the system." +msgid "Toggle between [Choose], [Play] (default), [Resume], [Show information] and [Queue item].[CR][Choose] will open a context menu to select an item, e.g. show information.[CR][Play] will automatically play videos from the beginning or if a resume point is present ask whether to play from beginning or to resume.[CR][Resume] Will automatically resume videos from the last position that you were viewing them.[CR][Show information] will open the video's information dialog.[CR][Queue item] will append the video to the video playlist." msgstr "" #. Description of setting with label #20433 "Extract thumbnails and video information" From 3575db26e3ebc851a026a9fe893b593fbaa32fef Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 30 Sep 2023 17:35:27 +0200 Subject: [PATCH 320/811] [utils] Introduce URIUtils::IsDiscImage and URIUtils::IsDiscImageStack to simplify some code. --- xbmc/FileItem.cpp | 2 +- xbmc/application/ApplicationStackHelper.cpp | 2 +- xbmc/utils/URIUtils.cpp | 10 ++++++++++ xbmc/utils/URIUtils.h | 2 ++ xbmc/video/VideoDatabase.cpp | 4 ++-- xbmc/video/guilib/VideoSelectActionProcessor.cpp | 1 - xbmc/video/windows/GUIWindowVideoBase.cpp | 5 ++--- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 2d033eea87323..e3d411838b381 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1178,7 +1178,7 @@ bool CFileItem::IsNFO() const bool CFileItem::IsDiscImage() const { - return URIUtils::HasExtension(GetDynPath(), ".img|.iso|.nrg|.udf"); + return URIUtils::IsDiscImage(GetDynPath()); } bool CFileItem::IsOpticalMediaFile() const diff --git a/xbmc/application/ApplicationStackHelper.cpp b/xbmc/application/ApplicationStackHelper.cpp index a1e8a0e8c8815..f8070c0d1ec53 100644 --- a/xbmc/application/ApplicationStackHelper.cpp +++ b/xbmc/application/ApplicationStackHelper.cpp @@ -75,7 +75,7 @@ bool CApplicationStackHelper::InitializeStack(const CFileItem & item) SetRegisteredStack(GetStackPartFileItem(i), stack); SetRegisteredStackPartNumber(GetStackPartFileItem(i), i); } - m_currentStackIsDiscImageStack = CFileItem(CStackDirectory::GetFirstStackedFile(item.GetPath()), false).IsDiscImage(); + m_currentStackIsDiscImageStack = URIUtils::IsDiscImageStack(item.GetDynPath()); return true; } diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp index 54feea3981db1..4a435e93d6144 100644 --- a/xbmc/utils/URIUtils.cpp +++ b/xbmc/utils/URIUtils.cpp @@ -846,6 +846,16 @@ bool URIUtils::IsArchive(const std::string& strFile) return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr"); } +bool URIUtils::IsDiscImage(const std::string& file) +{ + return HasExtension(file, ".img|.iso|.nrg|.udf"); +} + +bool URIUtils::IsDiscImageStack(const std::string& file) +{ + return IsStack(file) && IsDiscImage(CStackDirectory::GetFirstStackedFile(file)); +} + bool URIUtils::IsSpecial(const std::string& strFile) { if (IsStack(strFile)) diff --git a/xbmc/utils/URIUtils.h b/xbmc/utils/URIUtils.h index f682e3dfec8d0..06d1a712a0a22 100644 --- a/xbmc/utils/URIUtils.h +++ b/xbmc/utils/URIUtils.h @@ -175,6 +175,8 @@ class URIUtils static bool IsAPK(const std::string& strFile); static bool IsZIP(const std::string& strFile); static bool IsArchive(const std::string& strFile); + static bool IsDiscImage(const std::string& file); + static bool IsDiscImageStack(const std::string& file); static bool IsBluray(const std::string& strFile); static bool IsAndroidApp(const std::string& strFile); static bool IsLibraryFolder(const std::string& strFile); diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index b286bcadb9962..087232e4f2cdd 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -3216,7 +3216,7 @@ void CVideoDatabase::GetBookMarksForFile(const std::string& strFilenameAndPath, { try { - if (URIUtils::IsStack(strFilenameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(strFilenameAndPath),false).IsDiscImage()) + if (URIUtils::IsDiscImageStack(strFilenameAndPath)) { CStackDirectory dir; CFileItemList fileList; @@ -4024,7 +4024,7 @@ bool CVideoDatabase::GetResumePoint(CVideoInfoTag& tag) try { - if (URIUtils::IsStack(tag.m_strFileNameAndPath) && CFileItem(CStackDirectory::GetFirstStackedFile(tag.m_strFileNameAndPath),false).IsDiscImage()) + if (URIUtils::IsDiscImageStack(tag.m_strFileNameAndPath)) { CStackDirectory dir; CFileItemList fileList; diff --git a/xbmc/video/guilib/VideoSelectActionProcessor.cpp b/xbmc/video/guilib/VideoSelectActionProcessor.cpp index 7a75a6bad3241..1a8d274a4fdb4 100644 --- a/xbmc/video/guilib/VideoSelectActionProcessor.cpp +++ b/xbmc/video/guilib/VideoSelectActionProcessor.cpp @@ -13,7 +13,6 @@ #include "dialogs/GUIDialogContextMenu.h" #include "dialogs/GUIDialogSelect.h" #include "filesystem/Directory.h" -#include "filesystem/StackDirectory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index d9d70db760ba7..892d0cef88621 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -27,7 +27,6 @@ #include "dialogs/GUIDialogYesNo.h" #include "filesystem/Directory.h" #include "filesystem/MultiPathDirectory.h" -#include "filesystem/StackDirectory.h" #include "filesystem/VideoDatabaseDirectory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" @@ -764,7 +763,7 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but if (URIUtils::IsStack(path)) { std::vector times; - if (m_database.GetStackTimes(path,times) || CFileItem(CStackDirectory::GetFirstStackedFile(path),false).IsDiscImage()) + if (m_database.GetStackTimes(path, times) || URIUtils::IsDiscImageStack(path)) buttons.Add(CONTEXT_BUTTON_PLAY_PART, 20324); } } @@ -825,7 +824,7 @@ bool CGUIWindowVideoBase::OnPlayStackPart(int itemIndex, unsigned int partNumber if (!URIUtils::IsStack(path)) return false; - if (CFileItem(CStackDirectory::GetFirstStackedFile(path), false).IsDiscImage()) + if (URIUtils::IsDiscImageStack(path)) { // disc image stack CFileItemList parts; From 525cc516bd8db7d449bfd21365bd3f5225cbe368 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:08:02 +0200 Subject: [PATCH 321/811] [video] CGUIWindowVideoBase::ShowResumeMenu: Eliminate this function in favour to VIDEO::GUILIB::CVideoSelectActionProcessorBase::ChoosePlayOrResume. --- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 15 ++++++++++++--- xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 20 +++++++++++++++----- xbmc/video/windows/GUIWindowVideoBase.cpp | 13 ------------- xbmc/video/windows/GUIWindowVideoBase.h | 10 ---------- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index fcf04fb3a176e..52785cbac509f 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -36,7 +36,7 @@ #include "utils/log.h" #include "video/PlayerController.h" #include "video/VideoUtils.h" -#include "video/windows/GUIWindowVideoBase.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include @@ -502,10 +502,19 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) if (!item.m_bIsFolder && item.IsPlugin()) item.SetProperty("IsPlayable", true); - if (askToResume == true) + if (askToResume) { - if (CGUIWindowVideoBase::ShowResumeMenu(item) == false) + const VIDEO::GUILIB::SelectAction action = + VIDEO::GUILIB::CVideoSelectActionProcessorBase::ChoosePlayOrResume(item); + if (action == VIDEO::GUILIB::SELECT_ACTION_RESUME) + { + item.SetStartOffset(STARTOFFSET_RESUME); + } + else if (action != VIDEO::GUILIB::SELECT_ACTION_PLAY) + { + // The Resume dialog was closed without any choice return false; + } item.SetProperty("check_resume", false); } diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index cfc53c08f74c7..cc0b4965e2f48 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -55,6 +55,7 @@ #include "video/VideoLibraryQueue.h" #include "video/VideoThumbLoader.h" #include "video/VideoUtils.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include "video/windows/GUIWindowVideoNav.h" #include @@ -65,6 +66,7 @@ using namespace XFILE::VIDEODATABASEDIRECTORY; using namespace XFILE; using namespace KODI::MESSAGING; +using namespace VIDEO::GUILIB; #define CONTROL_IMAGE 3 #define CONTROL_TEXTAREA 4 @@ -748,12 +750,20 @@ void CGUIDialogVideoInfo::Play(bool resume) { m_movieItem->SetStartOffset(STARTOFFSET_RESUME); } - else if (!CGUIWindowVideoBase::ShowResumeMenu(*m_movieItem)) + else { - // The Resume dialog was closed without any choice - SetMovie(m_movieItem.get()); // restore cast list, which was cleared on GUI_MSG_WINDOW_DEINIT - Open(); - return; + const SelectAction action = CVideoSelectActionProcessorBase::ChoosePlayOrResume(*m_movieItem); + if (action == SELECT_ACTION_RESUME) + { + m_movieItem->SetStartOffset(STARTOFFSET_RESUME); + } + else if (action != SELECT_ACTION_PLAY) + { + // The Resume dialog was closed without any choice + SetMovie(m_movieItem.get()); // restore cast list, which was cleared on dialog close + Open(); + return; + } } m_movieItem->SetProperty("playlist_type_hint", PLAYLIST::TYPE_VIDEO); diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 892d0cef88621..2d92bb2086cdc 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -724,19 +724,6 @@ void CGUIWindowVideoBase::LoadVideoInfo(CFileItemList& items, } } -bool CGUIWindowVideoBase::ShowResumeMenu(CFileItem &item) -{ - if (!item.IsLiveTV()) - { - const SelectAction action = CVideoSelectActionProcessor::ChoosePlayOrResume(item); - if (action == SELECT_ACTION_RESUME) - item.SetStartOffset(STARTOFFSET_RESUME); - else if (action != SELECT_ACTION_PLAY) - return false; // don't do anything - } - return true; -} - bool CGUIWindowVideoBase::OnPlayOrResumeItem(int iItem, const std::string& player) { return OnFileAction(iItem, SELECT_ACTION_PLAY_OR_RESUME, player); diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index 5776de227a2e8..38a54a40a3d4b 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -37,16 +37,6 @@ class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObse */ bool OnItemInfo(const CFileItem& fileItem); - /*! \brief Show the resume menu for this item (if it has a resume bookmark) - If a resume bookmark is found, we set the item's m_lStartOffset to STARTOFFSET_RESUME. - Note that we do this in favour of setting the resume point, as we need additional - information from the database (in particular, the playerState) when resuming some items - (eg ISO/VIDEO_TS). - \param item item to check for a resume bookmark - \return true if an option was chosen, false if the resume menu was cancelled. - */ - static bool ShowResumeMenu(CFileItem &item); - /*! \brief Append a set of search items to a results list using a specific prepend label Sorts the search items first, then appends with the given prependLabel to the results list. Then empty the search item list so it can be refilled. From ac0c3d1bd8a7bdfcd7208d8e3bc832d7ecac1a63 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 30 Sep 2023 09:02:07 +0200 Subject: [PATCH 322/811] [PVR] CPVRGUIActionsPlayback::CheckResumeRecording: Eliminate custom resume context menu implementation in favour to VIDEO::GUILIB::CVideoSelectActionProcessorBase::ChoosePlayOrResume. --- xbmc/dialogs/GUIDialogContextMenu.h | 1 - xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 28 +++++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h index 3a3cdf9f6c431..8389449f94bb3 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.h +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -53,7 +53,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_PLAY_WITH, CONTEXT_BUTTON_PLAY_PARTYMODE, CONTEXT_BUTTON_PLAY_PART, - CONTEXT_BUTTON_RESUME_ITEM, CONTEXT_BUTTON_EDIT, CONTEXT_BUTTON_EDIT_SMART_PLAYLIST, CONTEXT_BUTTON_INFO, diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp index 9e8878eaf71c6..7c40898bdb917 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -12,7 +12,6 @@ #include "ServiceBroker.h" #include "application/ApplicationEnums.h" #include "cores/DataCacheCore.h" -#include "dialogs/GUIDialogContextMenu.h" #include "dialogs/GUIDialogKaiToast.h" #include "dialogs/GUIDialogYesNo.h" #include "guilib/GUIComponent.h" @@ -42,6 +41,7 @@ #include "utils/Variant.h" #include "utils/log.h" #include "video/VideoUtils.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include #include @@ -59,19 +59,23 @@ CPVRGUIActionsPlayback::CPVRGUIActionsPlayback() bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem& item) const { bool bPlayIt(true); - const std::string resumeString = VIDEO_UTILS::GetResumeString(item); - if (!resumeString.empty()) + + const VIDEO::GUILIB::SelectAction action = + VIDEO::GUILIB::CVideoSelectActionProcessorBase::ChoosePlayOrResume(item); + if (action == VIDEO::GUILIB::SELECT_ACTION_RESUME) { - CContextButtons choices; - choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString); - choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); // Play from beginning - int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices); - if (choice > 0) - const_cast(&item)->SetStartOffset( - choice == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0); - else - bPlayIt = false; // context menu cancelled + const_cast(&item)->SetStartOffset(STARTOFFSET_RESUME); + } + else if (action == VIDEO::GUILIB::SELECT_ACTION_PLAY) + { + const_cast(&item)->SetStartOffset(0); } + else + { + // The Resume dialog was closed without any choice + bPlayIt = false; + } + return bPlayIt; } From d65521d1a5106deb3f1582173bed96d41bb8ac1c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 27 Sep 2023 20:03:07 +0200 Subject: [PATCH 323/811] [builtins] Add support for non-folder items to 'QueueMedia' builtin. --- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index 52785cbac509f..ff842ee588694 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -518,7 +518,7 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) item.SetProperty("check_resume", false); } - if (item.m_bIsFolder || item.IsPlayList()) + if (!forcePlay /* queue */ || item.m_bIsFolder || item.IsPlayList()) { CFileItemList items; GetItemsForPlayList(std::make_shared(item), items); From ecdb124fe052f67a4d8f56e3d599de34257cc726 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:03:52 +0200 Subject: [PATCH 324/811] [builtins][video] Add support for video stacks to 'PlayMedia' and 'QueueMedia' builtins. --- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 35 +++++++++++------- xbmc/video/VideoUtils.cpp | 39 +++++++++++++++++++++ xbmc/video/VideoUtils.h | 8 +++++ 3 files changed, 70 insertions(+), 12 deletions(-) diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index ff842ee588694..bdb0490aa2f28 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -518,7 +518,17 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) item.SetProperty("check_resume", false); } - if (!forcePlay /* queue */ || item.m_bIsFolder || item.IsPlayList()) + if (item.IsStack()) + { + const VIDEO_UTILS::ResumeInformation resumeInfo = + VIDEO_UTILS::GetStackPartResumeInformation(item, playOffset + 1); + + if (item.GetStartOffset() == STARTOFFSET_RESUME) + item.SetStartOffset(resumeInfo.startOffset); + + item.m_lStartPartNumber = resumeInfo.partNumber; + } + else if (!forcePlay /* queue */ || item.m_bIsFolder || item.IsPlayList()) { CFileItemList items; GetItemsForPlayList(std::make_shared(item), items); @@ -783,17 +793,17 @@ static int SubtitleShiftDown(const std::vector& params) /// \table_row2_l{ /// `PlayMedia(media[\,isdir][\,1]\,[playoffset=xx])` /// , -/// 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. +/// Plays the given media. This can be a playlist\, music\, or video file\, directory\, +/// plugin\, disc image stack\, video file stack 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 or a disc image stack or a video file stack\, 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=" to start playback from a given position in a playlist (optional). +/// @param[in] playoffset Set "playoffset=" to start playback from a given position in a playlist or stack (optional). /// } /// \table_row2_l{ /// `PlayWith(core)` @@ -812,16 +822,17 @@ static int SubtitleShiftDown(const std::vector& params) /// `QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])` /// \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. +/// plugin\, disc image stack\, video file stack 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 or a disc image stack or a video file stack\, you can use +/// playoffset=xx where xx is the position to start playback from. +/// 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=" to start playback from a given position in a playlist (optional). +/// @param[in] playoffset Set "playoffset=" to start playback from a given position in a playlist or stack (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). ///


diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index ba3afaaad950a..b10c0f6a25a5c 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -796,4 +796,43 @@ std::string GetResumeString(const CFileItem& item) } return {}; } + +ResumeInformation GetStackPartResumeInformation(const CFileItem& item, unsigned int partNumber) +{ + ResumeInformation resumeInfo; + + if (item.IsStack()) + { + const std::string path = item.GetDynPath(); + if (URIUtils::IsDiscImageStack(path)) + { + // disc image stack + CFileItemList parts; + XFILE::CDirectory::GetDirectory(path, parts, "", XFILE::DIR_FLAG_DEFAULTS); + + resumeInfo = GetItemResumeInformation(*parts[partNumber - 1]); + resumeInfo.partNumber = partNumber; + } + else + { + // video file stack + CVideoDatabase db; + if (!db.Open()) + { + CLog::LogF(LOGERROR, "Cannot open VideoDatabase"); + return {}; + } + + std::vector times; + if (db.GetStackTimes(path, times)) + { + resumeInfo.startOffset = times[partNumber - 1]; + resumeInfo.isResumable = (resumeInfo.startOffset > 0); + } + resumeInfo.partNumber = partNumber; + } + } + return resumeInfo; +} + } // namespace VIDEO_UTILS diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h index b61ddafe2dea5..a55de50446b39 100644 --- a/xbmc/video/VideoUtils.h +++ b/xbmc/video/VideoUtils.h @@ -89,4 +89,12 @@ ResumeInformation GetItemResumeInformation(const CFileItem& item); */ std::string GetResumeString(const CFileItem& item); +/*! + \brief Get resume information for a part of a stack item. + \param item The stack item to retrieve information for + \param partNumber The number of the part + \return The resume information. + */ +ResumeInformation GetStackPartResumeInformation(const CFileItem& item, unsigned int partNumber); + } // namespace VIDEO_UTILS From 8a460ba23364193fea80fb2187375c3c03573a8a Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 29 Sep 2023 19:49:52 +0200 Subject: [PATCH 325/811] [listproviders] CDirectoryProvider: Add support for all possible video select actions (before only 'info' was supported). --- xbmc/FileItem.cpp | 6 +- xbmc/listproviders/DirectoryProvider.cpp | 93 +++++++++++++++++++++--- xbmc/listproviders/DirectoryProvider.h | 3 + 3 files changed, 87 insertions(+), 15 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index e3d411838b381..5222bdc6d8d9e 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -886,9 +886,9 @@ bool CFileItem::IsVideo() const if (HasPictureInfoTag()) return false; - // only tv recordings are videos... - if (IsPVRRecording()) - return !GetPVRRecordingInfoTag()->IsRadio(); + // TV recordings are videos... + if (!m_bIsFolder && URIUtils::IsPVRTVRecordingFileOrFolder(GetPath())) + return true; // ... all other PVR items are not. if (IsPVR()) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index aaf0418d7031a..d54a8c7fea0f6 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -36,9 +36,10 @@ #include "utils/Variant.h" #include "utils/XMLUtils.h" #include "utils/log.h" +#include "video/VideoInfoTag.h" #include "video/VideoThumbLoader.h" #include "video/dialogs/GUIDialogVideoInfo.h" -#include "video/windows/GUIWindowVideoBase.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include #include @@ -465,17 +466,77 @@ bool ExecuteAction(const std::string& execute) } return false; } -} // namespace -bool CDirectoryProvider::OnClick(const CGUIListItemPtr &item) +class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessorBase { - CFileItem fileItem(*std::static_pointer_cast(item)); +public: + CVideoSelectActionProcessor(CDirectoryProvider& provider, CFileItem& item) + : CVideoSelectActionProcessorBase(item), m_provider(provider) + { + } + +protected: + bool OnPlayPartSelected(unsigned int part) override + { + // part numbers are 1-based + BuildAndExecAction("PlayMedia", StringUtils::Format("playoffset={}", part - 1)); + return true; + } - if (fileItem.HasVideoInfoTag() && - CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( - CSettings::SETTING_MYVIDEOS_SELECTACTION) == VIDEO::GUILIB::SELECT_ACTION_INFO && - OnInfo(item)) + bool OnResumeSelected() override + { + BuildAndExecAction("PlayMedia", "resume"); + return true; + } + + bool OnPlaySelected() override + { + BuildAndExecAction("PlayMedia", "noresume"); return true; + } + + bool OnQueueSelected() override + { + BuildAndExecAction("QueueMedia", ""); + return true; + } + + bool OnInfoSelected() override + { + m_provider.OnInfo(std::make_shared(m_item)); + return true; + } + + bool OnMoreSelected() override + { + m_provider.OnContextMenu(std::make_shared(m_item)); + return true; + } + +private: + void BuildAndExecAction(const std::string& method, const std::string& param) + { + std::vector params{StringUtils::Paramify(m_item.GetPath())}; + if (!param.empty()) + params.emplace_back(param); + + const CExecString es{method, params}; + ExecuteAction(es.GetExecString()); + } + + CDirectoryProvider& m_provider; +}; +} // namespace + +bool CDirectoryProvider::OnClick(const CGUIListItemPtr& item) +{ + CFileItem fileItem(*std::static_pointer_cast(item)); + if (fileItem.HasVideoInfoTag()) + { + CVideoSelectActionProcessor proc{*this, fileItem}; + if (proc.Process()) + return true; + } if (fileItem.HasProperty("node.target_url")) fileItem.SetPath(fileItem.GetProperty("node.target_url").asString()); @@ -520,10 +581,8 @@ bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) return true; } -bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) +bool CDirectoryProvider::OnInfo(const std::shared_ptr& fileItem) { - auto fileItem = std::static_pointer_cast(item); - if (fileItem->HasAddonInfo()) { return CGUIDialogAddonInfo::ShowForItem(fileItem); @@ -552,10 +611,14 @@ bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) return false; } -bool CDirectoryProvider::OnContextMenu(const CGUIListItemPtr& item) +bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) { auto fileItem = std::static_pointer_cast(item); + return OnInfo(fileItem); +} +bool CDirectoryProvider::OnContextMenu(const std::shared_ptr& fileItem) +{ const std::string target = GetTarget(*fileItem); if (!target.empty()) fileItem->SetProperty("targetwindow", target); @@ -563,6 +626,12 @@ bool CDirectoryProvider::OnContextMenu(const CGUIListItemPtr& item) return CONTEXTMENU::ShowFor(fileItem); } +bool CDirectoryProvider::OnContextMenu(const CGUIListItemPtr& item) +{ + auto fileItem = std::static_pointer_cast(item); + return OnContextMenu(fileItem); +} + bool CDirectoryProvider::IsUpdating() const { std::unique_lock lock(m_section); diff --git a/xbmc/listproviders/DirectoryProvider.h b/xbmc/listproviders/DirectoryProvider.h index 9a17aaae7720a..9535842210f4a 100644 --- a/xbmc/listproviders/DirectoryProvider.h +++ b/xbmc/listproviders/DirectoryProvider.h @@ -20,6 +20,7 @@ #include #include +class CFileItem; class TiXmlElement; class CVariant; @@ -72,6 +73,8 @@ class CDirectoryProvider : void Reset() override; bool OnClick(const CGUIListItemPtr &item) override; bool OnPlay(const CGUIListItemPtr& item) override; + bool OnInfo(const std::shared_ptr& item); + bool OnContextMenu(const std::shared_ptr& item); bool OnInfo(const CGUIListItemPtr &item) override; bool OnContextMenu(const CGUIListItemPtr &item) override; bool IsUpdating() const override; From 224e42e68dbfba833e1fe80db91de30987c19231 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 30 Sep 2023 14:00:42 +0200 Subject: [PATCH 326/811] [PVR] CGUIWindowPVRRecordingsBase: Add support for all possible video select actions (before only some were supported). --- xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 94 ++++++++++++++------- 1 file changed, 64 insertions(+), 30 deletions(-) diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index 4a3f4d7fc36a0..c5f3ba1bb3b26 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -28,7 +28,8 @@ #include "settings/SettingsComponent.h" #include "utils/URIUtils.h" #include "video/VideoLibraryQueue.h" -#include "video/guilib/VideoSelectAction.h" +#include "video/VideoUtils.h" +#include "video/guilib/VideoSelectActionProcessor.h" #include "video/windows/GUIWindowVideoBase.h" #include @@ -42,8 +43,7 @@ CGUIWindowPVRRecordingsBase::CGUIWindowPVRRecordingsBase(bool bRadio, int id, const std::string& xmlFile) : CGUIWindowPVRBase(bRadio, id, xmlFile), - m_settings( - {CSettings::SETTING_PVRRECORD_GROUPRECORDINGS, CSettings::SETTING_MYVIDEOS_SELECTACTION}) + m_settings({CSettings::SETTING_PVRRECORD_GROUPRECORDINGS}) { } @@ -221,6 +221,62 @@ void CGUIWindowPVRRecordingsBase::UpdateButtons() bGroupRecordings && path.IsValid() ? path.GetUnescapedDirectoryPath() : ""); } +namespace +{ +class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessorBase +{ +public: + CVideoSelectActionProcessor(CGUIWindowPVRRecordingsBase& window, CFileItem& item, int itemIndex) + : CVideoSelectActionProcessorBase(item), m_window(window), m_itemIndex(itemIndex) + { + } + +protected: + bool OnPlayPartSelected(unsigned int part) override + { + //! @todo pvr recordings do not support video stacking (yet). + return false; + } + + bool OnResumeSelected() override + { + CServiceBroker::GetPVRManager().Get().ResumePlayRecording( + m_item, true /* fall back to play if no resume possible */); + return true; + } + + bool OnPlaySelected() override + { + CServiceBroker::GetPVRManager().Get().PlayRecording( + m_item, false /* no resume check */); + return true; + } + + bool OnQueueSelected() override + { + VIDEO_UTILS::QueueItem(std::make_shared(m_item), + VIDEO_UTILS::QueuePosition::POSITION_END); + return true; + } + + bool OnInfoSelected() override + { + CServiceBroker::GetPVRManager().Get().ShowRecordingInfo(m_item); + return true; + } + + bool OnMoreSelected() override + { + m_window.OnPopupMenu(m_itemIndex); + return true; + } + +private: + CGUIWindowPVRRecordingsBase& m_window; + const int m_itemIndex{-1}; +}; +} // namespace + bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) { bool bReturn = false; @@ -242,7 +298,7 @@ bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) const CPVRRecordingsPath path(m_vecItems->GetPath()); if (path.IsValid() && path.IsRecordingsRoot() && item->IsParentFolder()) { - // handle special 'go home' item. + // handle .. item, which is only visible if list of recordings is empty. CServiceBroker::GetGUI()->GetWindowManager().ActivateWindow(WINDOW_HOME); bReturn = true; break; @@ -256,9 +312,10 @@ bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) *item, true /* check resume */); } else + { CServiceBroker::GetPVRManager().Get().PlayRecording( *item, true /* check resume */); - + } bReturn = true; } else if (item->m_bIsFolder) @@ -268,31 +325,8 @@ bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) } else { - switch (m_settings.GetIntValue(CSettings::SETTING_MYVIDEOS_SELECTACTION)) - { - case SELECT_ACTION_CHOOSE: - OnPopupMenu(iItem); - bReturn = true; - break; - case SELECT_ACTION_PLAY_OR_RESUME: - CServiceBroker::GetPVRManager().Get().PlayRecording( - *item, true /* check resume */); - bReturn = true; - break; - case SELECT_ACTION_RESUME: - CServiceBroker::GetPVRManager().Get().ResumePlayRecording( - *item, true /* fall back to play if no resume possible */); - bReturn = true; - break; - case SELECT_ACTION_INFO: - CServiceBroker::GetPVRManager().Get().ShowRecordingInfo( - *item); - bReturn = true; - break; - default: - bReturn = false; - break; - } + CVideoSelectActionProcessor proc(*this, *item, iItem); + bReturn = proc.Process(); } break; } From 0aa253264477ba351fe671edb580f79d51b1b733 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 30 Sep 2023 13:27:20 +0200 Subject: [PATCH 327/811] [PVR] Fix default select action 'resume' not working for PVR recordings if triggered via Estuary home screen widget. --- xbmc/FileItem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 5222bdc6d8d9e..5bb70b577992b 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3741,14 +3741,14 @@ bool CFileItem::LoadDetails() return true; } - if (m_bIsFolder && URIUtils::IsPVRRecordingFileOrFolder(GetPath())) + if (URIUtils::IsPVRRecordingFileOrFolder(GetPath())) { if (HasProperty("watchedepisodes") || HasProperty("watched")) return true; const std::string parentPath = URIUtils::GetParentPath(GetPath()); - //! @todo optimize, find a way to set the details of the directory without loading its content. + //! @todo optimize, find a way to set the details of the item without loading parent directory. CFileItemList items; if (CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) { From 96ad2031b02ba90ae5450c0d8f73d9fb1ef08af6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 1 Oct 2023 16:13:37 +0200 Subject: [PATCH 328/811] [builtins] 'PlayMedia' and 'QueueMedia': Improve docs to use markdown inline code statements instead of the double quotes. --- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index bdb0490aa2f28..bbd66d72f0f00 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -794,16 +794,16 @@ static int SubtitleShiftDown(const std::vector& params) /// `PlayMedia(media[\,isdir][\,1]\,[playoffset=xx])` /// , /// Plays the given media. This can be a playlist\, music\, or video file\, directory\, -/// plugin\, disc image stack\, video file stack or an URL. The optional parameter "\,isdir" can -/// be used for playing a directory. "\,1" will start the media without switching to fullscreen. +/// plugin\, disc image stack\, video file stack 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 or a disc image stack or a video file stack\, 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] playoffset Set "playoffset=" to start playback from a given position in a playlist or stack (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] playoffset Set `playoffset=` to start playback from a given position in a playlist or stack (optional). /// } /// \table_row2_l{ /// `PlayWith(core)` @@ -822,18 +822,18 @@ static int SubtitleShiftDown(const std::vector& params) /// `QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])` /// \anchor Builtin_QueueMedia, /// Queues the given media. This can be a playlist\, music\, or video file\, directory\, -/// plugin\, disc image stack\, video file stack or an URL. The optional parameter "\,isdir" can -/// be used for playing a directory. "\,1" will start the media without switching to fullscreen. +/// plugin\, disc image stack\, video file stack 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 or a disc image stack or a video file stack\, you can use /// playoffset=xx where xx is the position to start playback from. /// 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] playoffset Set "playoffset=" to start playback from a given position in a playlist or stack (optional). -/// @param[in] playnext Set "playnext" to play the media right after the currently playing item, if player is currently +/// @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] playoffset Set `playoffset=` to start playback from a given position in a playlist or stack (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). ///


/// @skinning_v20 **[New builtin]** \link Builtin_QueueMedia `QueueMedia(media[\,isdir][\,1][\,playnext]\,[playoffset=xx])`\endlink From 66f35f8e025bb043de43ee47cb26f5cc9199f2d9 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 2 Oct 2023 17:57:12 +1000 Subject: [PATCH 329/811] [cmake] FindRapidJSON update to newer module standards --- CMakeLists.txt | 4 +- cmake/modules/FindRapidJSON.cmake | 86 +++++++++++++------ ...cmake-standardise_config_installpath.patch | 15 ++++ ...ch => 003-cmake-removedocs-examples.patch} | 0 ...03-win-arm64.patch => 004-win-arm64.patch} | 0 tools/depends/target/rapidjson/Makefile | 10 ++- 6 files changed, 83 insertions(+), 32 deletions(-) create mode 100644 tools/depends/target/rapidjson/002-cmake-standardise_config_installpath.patch rename tools/depends/target/rapidjson/{002-cmake-removedocs-examples.patch => 003-cmake-removedocs-examples.patch} (100%) rename tools/depends/target/rapidjson/{003-win-arm64.patch => 004-win-arm64.patch} (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7acd26853877..b5b9da0d9637d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,7 +73,6 @@ option(ENABLE_TESTING "Enable testing support?" ON) # These are required enabled for all CI platforms, and recommended for all builds option(ENABLE_INTERNAL_CROSSGUID "Enable internal crossguid?" ON) -option(ENABLE_INTERNAL_RapidJSON "Enable internal rapidjson?" ON) # use ffmpeg from depends or system option(ENABLE_INTERNAL_FFMPEG "Enable internal ffmpeg?" OFF) @@ -83,6 +82,7 @@ dependent_option(ENABLE_INTERNAL_FLATBUFFERS "Enable internal flatbuffers?") dependent_option(ENABLE_INTERNAL_FMT "Enable internal fmt?") dependent_option(ENABLE_INTERNAL_NFS "Enable internal libnfs?") dependent_option(ENABLE_INTERNAL_PCRE "Enable internal pcre?") +dependent_option(ENABLE_INTERNAL_RapidJSON "Enable internal rapidjson?") dependent_option(ENABLE_INTERNAL_SPDLOG "Enable internal spdlog?") dependent_option(ENABLE_INTERNAL_TAGLIB "Enable internal taglib?") if(KODI_DEPENDSBUILD OR WIN32 OR WINDOWS_STORE) @@ -195,7 +195,7 @@ set(required_deps ASS>=0.15.0 Lzo2 OpenSSL>=1.1.0 PCRE - RapidJSON + RapidJSON>=1.0.2 Spdlog Sqlite3 TagLib diff --git a/cmake/modules/FindRapidJSON.cmake b/cmake/modules/FindRapidJSON.cmake index 19405867bdde0..377273151f1cc 100644 --- a/cmake/modules/FindRapidJSON.cmake +++ b/cmake/modules/FindRapidJSON.cmake @@ -9,18 +9,15 @@ # if(NOT TARGET RapidJSON::RapidJSON) - if(ENABLE_INTERNAL_RapidJSON) - include(cmake/scripts/common/ModuleHelpers.cmake) - - set(MODULE_LC rapidjson) - - SETUP_BUILD_VARS() + include(cmake/scripts/common/ModuleHelpers.cmake) + macro(buildrapidjson) set(RapidJSON_VERSION ${${MODULE}_VER}) set(patches "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/001-remove_custom_cxx_flags.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/002-cmake-removedocs-examples.patch" - "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/003-win-arm64.patch") + "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/002-cmake-standardise_config_installpath.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/003-cmake-removedocs-examples.patch" + "${CORE_SOURCE_DIR}/tools/depends/target/rapidjson/004-win-arm64.patch") generate_patchcommand("${patches}") @@ -29,34 +26,54 @@ if(NOT TARGET RapidJSON::RapidJSON) -DRAPIDJSON_BUILD_TESTS=OFF -DRAPIDJSON_BUILD_THIRDPARTY_GTEST=OFF) - set(BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/${CORE_BUILD_DIR}/include/rapidjson/rapidjson.h) + set(BUILD_BYPRODUCTS ${DEPENDS_PATH}/include/rapidjson/rapidjson.h) BUILD_DEP_TARGET() set(RAPIDJSON_INCLUDE_DIRS ${${MODULE}_INCLUDE_DIR}) + endmacro() - else() - if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_RapidJSON RapidJSON>=1.0.2 QUIET) - endif() + set(MODULE_LC rapidjson) - if(CORE_SYSTEM_NAME STREQUAL windows OR CORE_SYSTEM_NAME STREQUAL windowsstore) - set(RapidJSON_VERSION 1.1.0) + SETUP_BUILD_VARS() + + if(RapidJSON_FIND_VERSION) + if(RapidJSON_FIND_VERSION_EXACT) + set(RapidJSON_FIND_SPEC "=${RapidJSON_FIND_VERSION_COMPLETE}") + set(RapidJSON_CONFIG_SPEC "${RapidJSON_FIND_VERSION_COMPLETE}" EXACT) else() - if(PC_RapidJSON_VERSION) + set(RapidJSON_FIND_SPEC ">=${RapidJSON_FIND_VERSION_COMPLETE}") + set(RapidJSON_CONFIG_SPEC "${RapidJSON_FIND_VERSION_COMPLETE}") + endif() + endif() + + find_package(RapidJSON CONFIG ${RapidJSON_CONFIG_SPEC} + HINTS ${DEPENDS_PATH}/lib/cmake + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) + + # Check for existing RAPIDJSON. If version >= RAPIDJSON-VERSION file version, dont build + # A corner case, but if a linux/freebsd user WANTS to build internal tinyxml2, build anyway + if((RAPIDJSON_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_RapidJSON) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_RapidJSON)) + # Build internal rapidjson + buildrapidjson() + else() + # If RAPIDJSON_INCLUDE_DIRS exists, then the find_package command found a config + # and suitable version. If its not, we fall back to a pkgconfig/manual search + if(NOT DEFINED RAPIDJSON_INCLUDE_DIRS) + find_package(PkgConfig) + # Fallback to pkg-config and individual lib/include file search + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) + pkg_check_modules(PC_RapidJSON RapidJSON${RapidJSON_FIND_SPEC} QUIET) set(RapidJSON_VERSION ${PC_RapidJSON_VERSION}) - else() - find_package(RapidJSON 1.1.0 CONFIG REQUIRED - QUIET - HINTS ${DEPENDS_PATH}/lib - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) endif() - endif() - find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h - HINTS ${DEPENDS_PATH}/include ${PC_RapidJSON_INCLUDEDIR} - ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} - NO_CACHE) + find_path(RAPIDJSON_INCLUDE_DIRS NAMES rapidjson/rapidjson.h + HINTS ${DEPENDS_PATH}/include ${PC_RapidJSON_INCLUDEDIR} + ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG} + NO_CACHE) + endif() endif() include(FindPackageHandleStandardArgs) @@ -71,6 +88,23 @@ if(NOT TARGET RapidJSON::RapidJSON) if(TARGET rapidjson) add_dependencies(RapidJSON::RapidJSON rapidjson) endif() + + # Add internal build target when a Multi Config Generator is used + # We cant add a dependency based off a generator expression for targeted build types, + # https://gitlab.kitware.com/cmake/cmake/-/issues/19467 + # therefore if the find heuristics only find the library, we add the internal build + # target to the project to allow user to manually trigger for any build type they need + # in case only a specific build type is actually available (eg Release found, Debug Required) + # This is mainly targeted for windows who required different runtime libs for different + # types, and they arent compatible + if(_multiconfig_generator) + if(NOT TARGET rapidjson) + buildrapidjson() + set_target_properties(rapidjson PROPERTIES EXCLUDE_FROM_ALL TRUE) + endif() + add_dependencies(build_internal_depends rapidjson) + endif() + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP RapidJSON::RapidJSON) endif() endif() diff --git a/tools/depends/target/rapidjson/002-cmake-standardise_config_installpath.patch b/tools/depends/target/rapidjson/002-cmake-standardise_config_installpath.patch new file mode 100644 index 0000000000000..937d19e8e367a --- /dev/null +++ b/tools/depends/target/rapidjson/002-cmake-standardise_config_installpath.patch @@ -0,0 +1,15 @@ +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -94,11 +94,7 @@ + SET(LIB_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE STRING "Directory where lib will install") + SET(DOC_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/share/doc/${PROJECT_NAME}" CACHE PATH "Path to the documentation") + +-IF(UNIX OR CYGWIN) +- SET(_CMAKE_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}") +-ELSEIF(WIN32) +- SET(_CMAKE_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/cmake") +-ENDIF() ++SET(_CMAKE_INSTALL_DIR "${LIB_INSTALL_DIR}/cmake/${PROJECT_NAME}") + SET(CMAKE_INSTALL_DIR "${_CMAKE_INSTALL_DIR}" CACHE PATH "The directory cmake fiels are installed in") + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/tools/depends/target/rapidjson/002-cmake-removedocs-examples.patch b/tools/depends/target/rapidjson/003-cmake-removedocs-examples.patch similarity index 100% rename from tools/depends/target/rapidjson/002-cmake-removedocs-examples.patch rename to tools/depends/target/rapidjson/003-cmake-removedocs-examples.patch diff --git a/tools/depends/target/rapidjson/003-win-arm64.patch b/tools/depends/target/rapidjson/004-win-arm64.patch similarity index 100% rename from tools/depends/target/rapidjson/003-win-arm64.patch rename to tools/depends/target/rapidjson/004-win-arm64.patch diff --git a/tools/depends/target/rapidjson/Makefile b/tools/depends/target/rapidjson/Makefile index 233d3a3571226..1e0a2d2e752b8 100644 --- a/tools/depends/target/rapidjson/Makefile +++ b/tools/depends/target/rapidjson/Makefile @@ -1,8 +1,9 @@ -include ../../Makefile.include include RAPIDJSON-VERSION DEPS = Makefile RAPIDJSON-VERSION 001-remove_custom_cxx_flags.patch ../../download-files.include \ - 002-cmake-removedocs-examples.patch \ - 003-win-arm64.patch + 002-cmake-standardise_config_installpath.patch \ + 003-cmake-removedocs-examples.patch \ + 004-win-arm64.patch CMAKE_OPTIONS=-DRAPIDJSON_HAS_STDSTRING=ON -DRAPIDJSON_BUILD_DOC=OFF -DRAPIDJSON_BUILD_EXAMPLES=OFF -DRAPIDJSON_BUILD_TESTS=OFF -DRAPIDJSON_BUILD_THIRDPARTY_GTEST=OFF @@ -42,8 +43,9 @@ endif cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) cd $(PLATFORM); rm -rf build; mkdir -p build cd $(PLATFORM); patch -p1 -i ../001-remove_custom_cxx_flags.patch - cd $(PLATFORM); patch -p1 -i ../002-cmake-removedocs-examples.patch - cd $(PLATFORM); patch -p1 -i ../003-win-arm64.patch + cd $(PLATFORM); patch -p1 -i ../002-cmake-standardise_config_installpath.patch + cd $(PLATFORM); patch -p1 -i ../003-cmake-removedocs-examples.patch + cd $(PLATFORM); patch -p1 -i ../004-win-arm64.patch cd $(PLATFORM)/build; $(CMAKE) $(CMAKE_OPTIONS) .. .installed-$(PLATFORM): $(PLATFORM) From 7b88bb989c417f14a08eeb4b96c34bdd5110fac9 Mon Sep 17 00:00:00 2001 From: throwaway96 <68320646+throwaway96@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:17:48 -0400 Subject: [PATCH 330/811] build curl without libpsl dependency --- tools/depends/target/curl/Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/depends/target/curl/Makefile b/tools/depends/target/curl/Makefile index 76ddb64e9730a..90a0cd83cadd1 100644 --- a/tools/depends/target/curl/Makefile +++ b/tools/depends/target/curl/Makefile @@ -5,7 +5,8 @@ DEPS = ../../Makefile.include Makefile CURL-VERSION ../../download-files.include CONFIGURE=cp -f $(CONFIG_SUB) $(CONFIG_GUESS) .; \ ./configure --prefix=$(PREFIX) --disable-shared --disable-ldap \ --without-libssh2 --disable-ntlm-wb --enable-ipv6 --without-librtmp \ - --without-libidn2 --with-ca-fallback --with-ssl=$(PREFIX) --with-nghttp2=$(PREFIX) + --without-libidn2 --with-ca-fallback --with-ssl=$(PREFIX) \ + --with-nghttp2=$(PREFIX) --without-libpsl LIBDYLIB=$(PLATFORM)/lib/.libs/$(BYPRODUCT) From 7ea86ed2d09ece261b53641f6ab9321d65cf5565 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 2 Oct 2023 09:43:14 +0100 Subject: [PATCH 331/811] Revert "[docs] Fix for-the-badge badges" This reverts commit ea625655cbb84873e5741ef267a10749908ca131. --- README.md | 10 +++++----- docs/CONTRIBUTING.md | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5ab9086e06a75..ab43f9563661a 100644 --- a/README.md +++ b/README.md @@ -76,8 +76,8 @@ Kodi couldn't exist without ## License Kodi is **[GPLv2 licensed](LICENSE.md)**. You may use, distribute and copy it under the license terms. - - - - - + + + + + diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8277a6f8fdd86..df7d2ffef506f 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -45,6 +45,6 @@ The *pull request* description should only contain information relevant to the c ## Updating your PR Making a *pull request* adhere to the standards above can be difficult. If the maintainers notice anything that they'd like changed, they'll ask you to edit your *pull request* before it gets merged. **There's no need to open a new *pull request*, just edit the existing one**. If you're not sure how to do that, our **[git guide](GIT-FU.md)** provides a step by step guide. If you're still not sure, ask us for guidance. We're all fairly proficient with *git* and happy to be of assistance. - - + + From d459ec039209817e29b44fedd5445447bcf9fe3a Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 2 Oct 2023 19:00:38 +1000 Subject: [PATCH 332/811] [cmake] windows remove superfluous installdata --- cmake/installdata/windows/addons.txt | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 cmake/installdata/windows/addons.txt diff --git a/cmake/installdata/windows/addons.txt b/cmake/installdata/windows/addons.txt deleted file mode 100644 index 66b583a23fe1c..0000000000000 --- a/cmake/installdata/windows/addons.txt +++ /dev/null @@ -1,2 +0,0 @@ -project/BuildDependencies/${ARCH}/addons/script.module.pil KEEP_DIR_STRUCTURE addons -project/BuildDependencies/${ARCH}/addons/script.module.pycryptodome KEEP_DIR_STRUCTURE addons From f44cd9b32ef17166957a0ca3aa5fc63e516f77a5 Mon Sep 17 00:00:00 2001 From: Graham Horner Date: Mon, 8 May 2023 15:10:00 +0100 Subject: [PATCH 333/811] Add infolabels for video width and height Request from skinner to be able to print width and height separately from resolution and aspect ratio. Co-authored-by: Miguel Borges de Freitas Co-authored-by: Miguel Borges de Freitas --- xbmc/GUIInfoManager.cpp | 16 ++++++++++++++++ xbmc/guilib/guiinfo/GUIInfoLabels.h | 2 ++ xbmc/guilib/guiinfo/VideoGUIInfo.cpp | 14 ++++++++++++++ 3 files changed, 32 insertions(+) diff --git a/xbmc/GUIInfoManager.cpp b/xbmc/GUIInfoManager.cpp index c74c5d12ec6e8..44704963cb6cb 100644 --- a/xbmc/GUIInfoManager.cpp +++ b/xbmc/GUIInfoManager.cpp @@ -6872,6 +6872,20 @@ const infomap container_str[] = {{ "property", CONTAINER_PROPERTY }, ///


/// @skinning_v21 **[New Infolabel]** \link ListItem_SongVideoURL `ListItem.SongVideoURL`\endlink /// } +/// \table_row3{ `ListItem.VideoWidth`, +/// \anchor ListItem_VideoWidth +/// _string_, +/// @return String containing width of video in pixels - empty if unknown. +///


+/// @skinning_v21 **[New Infolabel]** \link ListItem_VideoWidth `ListItem.VideoWidth`\endlink +/// } +/// \table_row3{ `ListItem.VideoHeight`, +/// \anchor ListItem_VideoHeight +/// _string_, +/// @return String containing height of video in pixels - empty if unknown. +///


+/// @skinning_v21 **[New Infolabel]** \link ListItem_VideoHeight `ListItem.VideoHeight`\endlink +/// } /// \table_end /// /// ----------------------------------------------------------------------------- @@ -6989,6 +7003,8 @@ const infomap listitem_labels[]= {{ "thumb", LISTITEM_THUMB }, { "setid", LISTITEM_SETID }, { "videocodec", LISTITEM_VIDEO_CODEC }, { "videoresolution", LISTITEM_VIDEO_RESOLUTION }, + { "videowidth", LISTITEM_VIDEO_WIDTH}, + { "videoheight", LISTITEM_VIDEO_HEIGHT}, { "videoaspect", LISTITEM_VIDEO_ASPECT }, { "audiocodec", LISTITEM_AUDIO_CODEC }, { "audiochannels", LISTITEM_AUDIO_CHANNELS }, diff --git a/xbmc/guilib/guiinfo/GUIInfoLabels.h b/xbmc/guilib/guiinfo/GUIInfoLabels.h index ea5b326ccabd8..428171c150e26 100644 --- a/xbmc/guilib/guiinfo/GUIInfoLabels.h +++ b/xbmc/guilib/guiinfo/GUIInfoLabels.h @@ -964,6 +964,8 @@ #define LISTITEM_VIDEO_HDR_TYPE (LISTITEM_START + 208) #define LISTITEM_SONG_VIDEO_URL (LISTITEM_START + 209) #define LISTITEM_PARENTAL_RATING_CODE (LISTITEM_START + 210) +#define LISTITEM_VIDEO_WIDTH (LISTITEM_START + 211) +#define LISTITEM_VIDEO_HEIGHT (LISTITEM_START + 212) #define LISTITEM_END (LISTITEM_START + 2500) diff --git a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp index 3a6b0daf5a621..0d23d63aa506f 100644 --- a/xbmc/guilib/guiinfo/VideoGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/VideoGUIInfo.cpp @@ -437,6 +437,20 @@ bool CVideoGUIInfo::GetLabel(std::string& value, const CFileItem *item, int cont case LISTITEM_VIDEO_ASPECT: value = CStreamDetails::VideoAspectToAspectDescription(tag->m_streamDetails.GetVideoAspect()); return true; + case LISTITEM_VIDEO_WIDTH: + { + const int val = tag->m_streamDetails.GetVideoWidth(); + if (val > 0) + value = std::to_string(val); + return true; + } + case LISTITEM_VIDEO_HEIGHT: + { + const int val = tag->m_streamDetails.GetVideoHeight(); + if (val > 0) + value = std::to_string(val); + return true; + } case LISTITEM_AUDIO_CODEC: value = tag->m_streamDetails.GetAudioCodec(); return true; From f2ce6d0201235a029f9695a247037b9cc2735fb4 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Thu, 28 Sep 2023 22:44:19 -0700 Subject: [PATCH 334/811] CGBMUtils: return a reference to CGBMDevice Signed-off-by: Lukas Rusak --- xbmc/utils/GBMBufferObject.cpp | 2 +- xbmc/windowing/gbm/GBMUtils.h | 2 +- xbmc/windowing/gbm/WinSystemGbm.cpp | 4 ++-- xbmc/windowing/gbm/WinSystemGbm.h | 2 +- xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp | 10 +++++----- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/xbmc/utils/GBMBufferObject.cpp b/xbmc/utils/GBMBufferObject.cpp index 90c4017c3dca9..c3b21ecea98d1 100644 --- a/xbmc/utils/GBMBufferObject.cpp +++ b/xbmc/utils/GBMBufferObject.cpp @@ -30,7 +30,7 @@ void CGBMBufferObject::Register() CGBMBufferObject::CGBMBufferObject() { m_device = - static_cast(CServiceBroker::GetWinSystem())->GetGBMDevice()->Get(); + static_cast(CServiceBroker::GetWinSystem())->GetGBMDevice().Get(); } CGBMBufferObject::~CGBMBufferObject() diff --git a/xbmc/windowing/gbm/GBMUtils.h b/xbmc/windowing/gbm/GBMUtils.h index 291a93a32d7dc..876cc3114831c 100644 --- a/xbmc/windowing/gbm/GBMUtils.h +++ b/xbmc/windowing/gbm/GBMUtils.h @@ -158,7 +158,7 @@ class CGBMUtils * * @return CGBMDevice* A pointer to the CGBMDevice object */ - CGBMUtils::CGBMDevice* GetDevice() const { return m_device.get(); } + CGBMUtils::CGBMDevice& GetDevice() const { return *m_device; } private: struct CGBMDeviceDeleter diff --git a/xbmc/windowing/gbm/WinSystemGbm.cpp b/xbmc/windowing/gbm/WinSystemGbm.cpp index 34c8c16fe4655..2509f288d6bbd 100644 --- a/xbmc/windowing/gbm/WinSystemGbm.cpp +++ b/xbmc/windowing/gbm/WinSystemGbm.cpp @@ -242,7 +242,7 @@ bool CWinSystemGbm::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool bl if (!std::dynamic_pointer_cast(m_DRM)) { - bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface()->LockFrontBuffer()->Get(); } auto result = m_DRM->SetVideoMode(res, bo); @@ -290,7 +290,7 @@ void CWinSystemGbm::FlipPage(bool rendered, bool videoLayer) if (rendered) { - bo = m_GBM->GetDevice()->GetSurface()->LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface()->LockFrontBuffer()->Get(); } m_DRM->FlipPage(bo, rendered, videoLayer); diff --git a/xbmc/windowing/gbm/WinSystemGbm.h b/xbmc/windowing/gbm/WinSystemGbm.h index a800acef6b1a6..8993b6d278652 100644 --- a/xbmc/windowing/gbm/WinSystemGbm.h +++ b/xbmc/windowing/gbm/WinSystemGbm.h @@ -71,7 +71,7 @@ class CWinSystemGbm : public CWinSystemBase m_videoLayerBridge = std::move(bridge); }; - CGBMUtils::CGBMDevice* GetGBMDevice() const { return m_GBM->GetDevice(); } + CGBMUtils::CGBMDevice& GetGBMDevice() const { return m_GBM->GetDevice(); } std::shared_ptr GetDrm() const { return m_DRM; } std::vector GetConnectedOutputs() override; diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp index 83a59413f79f2..a80026fc329f7 100644 --- a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp +++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp @@ -23,7 +23,7 @@ bool CWinSystemGbmEGLContext::InitWindowSystemEGL(EGLint renderableType, EGLint return false; } - if (!m_eglContext.CreatePlatformDisplay(m_GBM->GetDevice()->Get(), m_GBM->GetDevice()->Get())) + if (!m_eglContext.CreatePlatformDisplay(m_GBM->GetDevice().Get(), m_GBM->GetDevice().Get())) { return false; } @@ -87,8 +87,8 @@ bool CWinSystemGbmEGLContext::CreateNewWindow(const std::string& name, if (plane) modifiers = plane->GetModifiersForFormat(format); - if (!m_GBM->GetDevice()->CreateSurface(res.iWidth, res.iHeight, format, modifiers.data(), - modifiers.size())) + if (!m_GBM->GetDevice().CreateSurface(res.iWidth, res.iHeight, format, modifiers.data(), + modifiers.size())) { CLog::Log(LOGERROR, "CWinSystemGbmEGLContext::{} - failed to initialize GBM", __FUNCTION__); return false; @@ -98,8 +98,8 @@ bool CWinSystemGbmEGLContext::CreateNewWindow(const std::string& name, static_assert(sizeof(EGLNativeWindowType) == sizeof(gbm_surface*), "Declaration specifier differs in size"); if (!m_eglContext.CreatePlatformSurface( - m_GBM->GetDevice()->GetSurface()->Get(), - reinterpret_cast(m_GBM->GetDevice()->GetSurface()->Get()))) + m_GBM->GetDevice().GetSurface()->Get(), + reinterpret_cast(m_GBM->GetDevice().GetSurface()->Get()))) { return false; } From e0c7d9b54b552fb3e676ae78bbbace2c27e18c98 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Thu, 28 Sep 2023 22:46:46 -0700 Subject: [PATCH 335/811] CGBMDevice: return a reference to CGBMSurface Signed-off-by: Lukas Rusak --- xbmc/windowing/gbm/GBMUtils.h | 2 +- xbmc/windowing/gbm/WinSystemGbm.cpp | 4 ++-- xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xbmc/windowing/gbm/GBMUtils.h b/xbmc/windowing/gbm/GBMUtils.h index 876cc3114831c..259a2870c28d7 100644 --- a/xbmc/windowing/gbm/GBMUtils.h +++ b/xbmc/windowing/gbm/GBMUtils.h @@ -137,7 +137,7 @@ class CGBMUtils * * @return CGBMSurface* A pointer to the CGBMSurface object */ - CGBMDevice::CGBMSurface* GetSurface() const { return m_surface.get(); } + CGBMDevice::CGBMSurface& GetSurface() const { return *m_surface; } private: gbm_device* m_device{nullptr}; diff --git a/xbmc/windowing/gbm/WinSystemGbm.cpp b/xbmc/windowing/gbm/WinSystemGbm.cpp index 2509f288d6bbd..2b087d8f81bc9 100644 --- a/xbmc/windowing/gbm/WinSystemGbm.cpp +++ b/xbmc/windowing/gbm/WinSystemGbm.cpp @@ -242,7 +242,7 @@ bool CWinSystemGbm::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool bl if (!std::dynamic_pointer_cast(m_DRM)) { - bo = m_GBM->GetDevice().GetSurface()->LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer()->Get(); } auto result = m_DRM->SetVideoMode(res, bo); @@ -290,7 +290,7 @@ void CWinSystemGbm::FlipPage(bool rendered, bool videoLayer) if (rendered) { - bo = m_GBM->GetDevice().GetSurface()->LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer()->Get(); } m_DRM->FlipPage(bo, rendered, videoLayer); diff --git a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp index a80026fc329f7..2fb742efed35f 100644 --- a/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp +++ b/xbmc/windowing/gbm/WinSystemGbmEGLContext.cpp @@ -98,8 +98,8 @@ bool CWinSystemGbmEGLContext::CreateNewWindow(const std::string& name, static_assert(sizeof(EGLNativeWindowType) == sizeof(gbm_surface*), "Declaration specifier differs in size"); if (!m_eglContext.CreatePlatformSurface( - m_GBM->GetDevice().GetSurface()->Get(), - reinterpret_cast(m_GBM->GetDevice().GetSurface()->Get()))) + m_GBM->GetDevice().GetSurface().Get(), + reinterpret_cast(m_GBM->GetDevice().GetSurface().Get()))) { return false; } From 237c3cd09d8a1b5a3d2f157ab7f900cd8a8b6319 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Thu, 28 Sep 2023 22:48:26 -0700 Subject: [PATCH 336/811] CGBMSurface: return a reference to CGBMSurfaceBuffer Signed-off-by: Lukas Rusak --- xbmc/windowing/gbm/GBMUtils.cpp | 4 ++-- xbmc/windowing/gbm/GBMUtils.h | 2 +- xbmc/windowing/gbm/WinSystemGbm.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xbmc/windowing/gbm/GBMUtils.cpp b/xbmc/windowing/gbm/GBMUtils.cpp index 5267c93c8f3d7..0ac2b854baeef 100644 --- a/xbmc/windowing/gbm/GBMUtils.cpp +++ b/xbmc/windowing/gbm/GBMUtils.cpp @@ -74,7 +74,7 @@ CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurface(gbm_surface* surface) : m_surfac { } -CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer* CGBMUtils::CGBMDevice::CGBMSurface:: +CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer& CGBMUtils::CGBMDevice::CGBMSurface:: LockFrontBuffer() { m_buffers.emplace(std::make_unique(m_surface)); @@ -92,7 +92,7 @@ CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer* CGBMUtils::CGBMDevice::CG m_buffers.pop(); } - return m_buffers.back().get(); + return *m_buffers.back(); } CGBMUtils::CGBMDevice::CGBMSurface::CGBMSurfaceBuffer::CGBMSurfaceBuffer(gbm_surface* surface) diff --git a/xbmc/windowing/gbm/GBMUtils.h b/xbmc/windowing/gbm/GBMUtils.h index 259a2870c28d7..031283461992e 100644 --- a/xbmc/windowing/gbm/GBMUtils.h +++ b/xbmc/windowing/gbm/GBMUtils.h @@ -125,7 +125,7 @@ class CGBMUtils * * @return CGBMSurfaceBuffer* A pointer to a CGBMSurfaceBuffer object */ - CGBMSurfaceBuffer* LockFrontBuffer(); + CGBMSurfaceBuffer& LockFrontBuffer(); private: gbm_surface* m_surface{nullptr}; diff --git a/xbmc/windowing/gbm/WinSystemGbm.cpp b/xbmc/windowing/gbm/WinSystemGbm.cpp index 2b087d8f81bc9..da5609490a149 100644 --- a/xbmc/windowing/gbm/WinSystemGbm.cpp +++ b/xbmc/windowing/gbm/WinSystemGbm.cpp @@ -242,7 +242,7 @@ bool CWinSystemGbm::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool bl if (!std::dynamic_pointer_cast(m_DRM)) { - bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer().Get(); } auto result = m_DRM->SetVideoMode(res, bo); @@ -290,7 +290,7 @@ void CWinSystemGbm::FlipPage(bool rendered, bool videoLayer) if (rendered) { - bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer()->Get(); + bo = m_GBM->GetDevice().GetSurface().LockFrontBuffer().Get(); } m_DRM->FlipPage(bo, rendered, videoLayer); From 55d3a5e4ac7a6693c1d7a7d952824b9f10a095c0 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Mon, 2 Oct 2023 23:54:39 +0200 Subject: [PATCH 337/811] Remove GLES3/gl3ext.h include This file an empty header that is only available in mesa. It's not part of the original khronos GLES3 headers --- xbmc/addons/kodi-dev-kit/include/kodi/gui/gl/GL.h | 1 - 1 file changed, 1 deletion(-) diff --git a/xbmc/addons/kodi-dev-kit/include/kodi/gui/gl/GL.h b/xbmc/addons/kodi-dev-kit/include/kodi/gui/gl/GL.h index 16d43e37b4cca..761c5cc511310 100644 --- a/xbmc/addons/kodi-dev-kit/include/kodi/gui/gl/GL.h +++ b/xbmc/addons/kodi-dev-kit/include/kodi/gui/gl/GL.h @@ -95,7 +95,6 @@ #else #if HAS_GLES == 3 #include -#include #else #include #include From a6612f3a869e96203d676c78a9e46ea0cb79c6a3 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 2 Oct 2023 09:26:46 +0200 Subject: [PATCH 338/811] [listproviders] CDirectoryProvider: Favourites lists: Add support for video select actions. --- xbmc/FileItem.cpp | 16 ++++++++++++++ xbmc/listproviders/DirectoryProvider.cpp | 27 +++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 5bb70b577992b..99c9aaaa7fa5a 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3766,6 +3766,22 @@ bool CFileItem::LoadDetails() return false; } + if (IsVideo()) + { + if (HasVideoInfoTag()) + return true; + + CVideoDatabase db; + if (!db.Open()) + return false; + + if (db.LoadVideoInfo(GetDynPath(), *GetVideoInfoTag())) + return true; + + CLog::LogF(LOGERROR, "Error filling item details (path={})", GetPath()); + return false; + } + //! @todo add support for other types on demand. CLog::LogF(LOGDEBUG, "Unsupported item type (path={})", GetPath()); return false; diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index d54a8c7fea0f6..a4713c2827693 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -38,6 +38,7 @@ #include "utils/log.h" #include "video/VideoInfoTag.h" #include "video/VideoThumbLoader.h" +#include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" #include "video/guilib/VideoSelectActionProcessor.h" @@ -517,6 +518,9 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc void BuildAndExecAction(const std::string& method, const std::string& param) { std::vector params{StringUtils::Paramify(m_item.GetPath())}; + if (m_item.m_bIsFolder) + params.emplace_back("isdir"); + if (!param.empty()) params.emplace_back(param); @@ -531,7 +535,28 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc bool CDirectoryProvider::OnClick(const CGUIListItemPtr& item) { CFileItem fileItem(*std::static_pointer_cast(item)); - if (fileItem.HasVideoInfoTag()) + + if (fileItem.IsFavourite()) + { + // Resolve the favourite + const CFavouritesURL url{fileItem.GetPath()}; + if (!url.IsValid()) + return false; + + if (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) + { + CFileItem targetItem{url.GetTarget(), url.IsDir()}; + targetItem.LoadDetails(); + if (targetItem.IsVideo() || + (targetItem.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(targetItem))) + { + CVideoSelectActionProcessor proc{*this, targetItem}; + if (proc.Process()) + return true; + } + } + } + else if (fileItem.HasVideoInfoTag()) { CVideoSelectActionProcessor proc{*this, fileItem}; if (proc.Process()) From 36a3447c7cde848ebf0a67b56b63f07ee06a2bbd Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 2 Oct 2023 11:27:44 +0200 Subject: [PATCH 339/811] [favourites] CGUIWindowFavourites: Add support for video select actions. --- xbmc/favourites/GUIWindowFavourites.cpp | 83 ++++++++++++++++++++++++- xbmc/utils/ExecString.cpp | 19 ++++++ xbmc/utils/ExecString.h | 1 + 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index 0d43b59562d03..dbe7dffc2fbc3 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -8,6 +8,7 @@ #include "GUIWindowFavourites.h" +#include "ContextMenuManager.h" #include "FileItem.h" #include "ServiceBroker.h" #include "favourites/FavouritesURL.h" @@ -18,8 +19,13 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" #include "messaging/ApplicationMessenger.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsUtils.h" #include "utils/PlayerUtils.h" #include "utils/StringUtils.h" +#include "video/VideoUtils.h" +#include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/guilib/VideoSelectActionProcessor.h" CGUIWindowFavourites::CGUIWindowFavourites() : CGUIMediaWindow(WINDOW_FAVOURITES, "MyFavourites.xml") @@ -53,6 +59,64 @@ bool ExecuteAction(const std::string& execute) } return false; } + +bool ExecuteAction(const CExecString& execute) +{ + return ExecuteAction(execute.GetExecString()); +} + +class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessorBase +{ +public: + explicit CVideoSelectActionProcessor(CFileItem& item) : CVideoSelectActionProcessorBase(item) {} + +protected: + bool OnPlayPartSelected(unsigned int part) override + { + // part numbers are 1-based + ExecuteAction({"PlayMedia", m_item, StringUtils::Format("playoffset={}", part - 1)}); + return true; + } + + bool OnResumeSelected() override + { + ExecuteAction({"PlayMedia", m_item, "resume"}); + return true; + } + + bool OnPlaySelected() override + { + ExecuteAction({"PlayMedia", m_item, "noresume"}); + return true; + } + + bool OnQueueSelected() override + { + ExecuteAction({"QueueMedia", m_item, ""}); + return true; + } + + bool OnInfoSelected() override + { + if (m_item.IsPVR()) + { + CServiceBroker::GetPVRManager().Get().OnInfo(m_item); + return true; + } + else if (m_item.HasVideoInfoTag()) + { + CGUIDialogVideoInfo::ShowFor(m_item); + return true; + } + return false; + } + + bool OnMoreSelected() override + { + CONTEXTMENU::ShowFor(std::make_shared(m_item)); + return true; + } +}; } // namespace bool CGUIWindowFavourites::OnSelect(int item) @@ -60,7 +124,24 @@ bool CGUIWindowFavourites::OnSelect(int item) if (item < 0 || item >= m_vecItems->Size()) return false; - return ExecuteAction(CFavouritesURL(*(*m_vecItems)[item], GetID()).GetExecString()); + const CFavouritesURL favURL{*(*m_vecItems)[item], GetID()}; + if (!favURL.IsValid()) + return false; + + if (favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) + { + // Resolve the favourite + CFileItem targetItem{favURL.GetTarget(), favURL.IsDir()}; + targetItem.LoadDetails(); + if (targetItem.IsVideo() || (targetItem.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(targetItem))) + { + CVideoSelectActionProcessor proc{targetItem}; + if (proc.Process()) + return true; + } + } + + return ExecuteAction(favURL.GetExecString()); } bool CGUIWindowFavourites::OnAction(const CAction& action) diff --git a/xbmc/utils/ExecString.cpp b/xbmc/utils/ExecString.cpp index 7b55af0183487..43d986e018e80 100644 --- a/xbmc/utils/ExecString.cpp +++ b/xbmc/utils/ExecString.cpp @@ -33,6 +33,25 @@ CExecString::CExecString(const std::string& function, const std::vector& params); + CExecString(const std::string& function, const CFileItem& target, const std::string& param); CExecString(const CFileItem& item, const std::string& contextWindow); virtual ~CExecString() = default; From a8637b61d41b7e9db5db9a880fcbef4c3b08d092 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 3 Oct 2023 10:44:23 +0100 Subject: [PATCH 340/811] [macos][input] Support NSEnterCharacter for XBMCK_RETURN --- xbmc/windowing/osx/WinEventsOSXImpl.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/windowing/osx/WinEventsOSXImpl.mm b/xbmc/windowing/osx/WinEventsOSXImpl.mm index 27b609da989c4..4a35ba1c20593 100644 --- a/xbmc/windowing/osx/WinEventsOSXImpl.mm +++ b/xbmc/windowing/osx/WinEventsOSXImpl.mm @@ -108,6 +108,7 @@ - (unichar)OsxKey2XbmcKey:(unichar)character case NSDeleteCharacter: return XBMCK_BACKSPACE; case NSCarriageReturnCharacter: + case NSEnterCharacter: return XBMCK_RETURN; default: return character; From 2d71fee39560e6b34e4eb7ae11846c71919624e0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:10:57 +0200 Subject: [PATCH 341/811] [fileitem] CFileItem::LoadDetails: Log db open errors, remove unneeded db close calls (will be done automatically in db dtor). --- xbmc/FileItem.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 99c9aaaa7fa5a..049f7d3229b32 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3706,7 +3706,10 @@ bool CFileItem::LoadDetails() CVideoDatabase db; if (!db.Open()) + { + CLog::LogF(LOGERROR, "Error opening video database"); return false; + } VIDEODATABASEDIRECTORY::CQueryParams params; VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(GetPath(), params); @@ -3733,11 +3736,8 @@ bool CFileItem::LoadDetails() this); } else - { - db.Close(); return false; - } - db.Close(); + return true; } @@ -3773,7 +3773,10 @@ bool CFileItem::LoadDetails() CVideoDatabase db; if (!db.Open()) + { + CLog::LogF(LOGERROR, "Error opening video database"); return false; + } if (db.LoadVideoInfo(GetDynPath(), *GetVideoInfoTag())) return true; From cf1b5898ec9e30a3a29b97c8f35a3ba9643299a4 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Mon, 2 Oct 2023 22:32:45 +0200 Subject: [PATCH 342/811] [webOS] Pause video on minimize --- .../wayland/ShellSurfaceWebOSShell.cpp | 1 + xbmc/windowing/wayland/WinSystemWayland.h | 8 ++--- .../wayland/WinSystemWaylandWebOS.cpp | 36 +++++++++++++++++++ .../windowing/wayland/WinSystemWaylandWebOS.h | 3 ++ 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp index a4b9ab869d3ca..2bc66065879a7 100644 --- a/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp +++ b/xbmc/windowing/wayland/ShellSurfaceWebOSShell.cpp @@ -61,6 +61,7 @@ CShellSurfaceWebOSShell::CShellSurfaceWebOSShell(IShellSurfaceHandler& handler, case webos_shell_surface_state::minimized: CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to minimized"); m_surfaceState.reset(); + m_handler.OnConfigure(0, m_windowSize, m_surfaceState); break; case webos_shell_surface_state::_default: CLog::Log(LOGDEBUG, "CShellSurfaceWebOSShell: State changed to default (windowed)"); diff --git a/xbmc/windowing/wayland/WinSystemWayland.h b/xbmc/windowing/wayland/WinSystemWayland.h index 62720c5e188ff..be65b976a4d52 100644 --- a/xbmc/windowing/wayland/WinSystemWayland.h +++ b/xbmc/windowing/wayland/WinSystemWayland.h @@ -118,6 +118,10 @@ class CWinSystemWayland : public CWinSystemBase, virtual void SetContextSize(CSizeInt size) = 0; virtual IShellSurface* CreateShellSurface(const std::string& name); + // IShellSurfaceHandler + void OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state) override; + void OnClose() override; + private: // IInputHandler void OnEnter(InputType type) override; @@ -133,10 +137,6 @@ class CWinSystemWayland : public CWinSystemBase, void OnWindowMaximize() override; void OnWindowMinimize() override; - // IShellSurfaceHandler - void OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state) override; - void OnClose() override; - // Registry handlers void OnSeatAdded(std::uint32_t name, wayland::proxy_t&& seat); void OnSeatRemoved(std::uint32_t name); diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp index ec1567b2a2518..da0ac6dd2820d 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.cpp @@ -12,9 +12,14 @@ #include "OSScreenSaverWebOS.h" #include "Registry.h" #include "ShellSurfaceWebOSShell.h" +#include "application/ApplicationComponents.h" +#include "application/ApplicationPlayer.h" #include "cores/AudioEngine/Sinks/AESinkStarfish.h" #include "cores/VideoPlayer/DVDCodecs/Video/DVDVideoCodecStarfish.h" #include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" +#include "messaging/ApplicationMessenger.h" #include "utils/JSONVariantParser.h" #include "utils/log.h" @@ -144,6 +149,37 @@ bool CWinSystemWaylandWebOS::OnAppLifecycleEventWrapper(LSHandle* sh, LSMessage* return static_cast(context->userdata)->OnAppLifecycleEvent(sh, reply); } +void CWinSystemWaylandWebOS::OnConfigure(std::uint32_t serial, + CSizeInt size, + IShellSurface::StateBitset state) +{ + const auto& components = CServiceBroker::GetAppComponents(); + const auto player = components.GetComponent(); + + // intercept minimized event, passing the minimized event causes a weird animation + if (state.none()) + { + m_resumePlayback = false; + + if (player->IsPlaying() && player->HasVideo() && !player->IsPaused()) + { + CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast(new CAction(ACTION_PAUSE))); + m_resumePlayback = true; + } + } + else + { + if (m_resumePlayback && player->IsPlaying() && player->HasVideo() && player->IsPaused()) + { + CServiceBroker::GetAppMessenger()->SendMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast(new CAction(ACTION_PLAYER_PLAY))); + m_resumePlayback = false; + } + CWinSystemWayland::OnConfigure(serial, size, state); + } +} + bool CWinSystemWaylandWebOS::OnAppLifecycleEvent(LSHandle* sh, LSMessage* reply) { const char* msg = HLunaServiceMessage(reply); diff --git a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h index 5584d5eb32790..7863c041c893e 100644 --- a/xbmc/windowing/wayland/WinSystemWaylandWebOS.h +++ b/xbmc/windowing/wayland/WinSystemWaylandWebOS.h @@ -44,6 +44,7 @@ class CWinSystemWaylandWebOS : public CWinSystemWayland bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; ~CWinSystemWaylandWebOS() noexcept override; bool HasCursor() override; + void OnConfigure(std::uint32_t serial, CSizeInt size, IShellSurface::StateBitset state) override; protected: std::unique_ptr GetOSScreenSaverImpl() override; @@ -62,6 +63,8 @@ class CWinSystemWaylandWebOS : public CWinSystemWayland std::unique_ptr m_requestContext{new HContext(), HUnregisterServiceCallback}; + + bool m_resumePlayback{false}; }; } // namespace KODI::WINDOWING::WAYLAND From eb9fa0dd03790682ae69944e8d038bad393ff074 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:02:31 +0200 Subject: [PATCH 343/811] [Estouchy] Retire Favourites dialog, use Favourites window instead. --- .../skin.estouchy/media/DefaultFavourites.png | Bin 0 -> 2107 bytes .../media/icon_breadcrumb_favourites.png | Bin 0 -> 828 bytes addons/skin.estouchy/xml/DialogFavourites.xml | 148 ------------------ addons/skin.estouchy/xml/FileManager.xml | 2 +- addons/skin.estouchy/xml/Includes.xml | 3 +- addons/skin.estouchy/xml/MyFavourites.xml | 46 ++++++ addons/skin.estouchy/xml/MyWeather.xml | 2 +- addons/skin.estouchy/xml/Settings.xml | 2 +- addons/skin.estouchy/xml/SettingsCategory.xml | 2 +- addons/skin.estouchy/xml/SettingsProfile.xml | 2 +- addons/skin.estouchy/xml/SkinSettings.xml | 2 +- addons/skin.estouchy/xml/ViewsList.xml | 4 +- addons/skin.estouchy/xml/ViewsThumbnail.xml | 74 ++++++++- 13 files changed, 128 insertions(+), 159 deletions(-) create mode 100644 addons/skin.estouchy/media/DefaultFavourites.png create mode 100644 addons/skin.estouchy/media/icon_breadcrumb_favourites.png delete mode 100644 addons/skin.estouchy/xml/DialogFavourites.xml create mode 100644 addons/skin.estouchy/xml/MyFavourites.xml diff --git a/addons/skin.estouchy/media/DefaultFavourites.png b/addons/skin.estouchy/media/DefaultFavourites.png new file mode 100644 index 0000000000000000000000000000000000000000..62c3dd5d742fa03c7926906ccee8aab6297c265c GIT binary patch literal 2107 zcmcJR`#;o)9>za2t|1g6GA1v}B*s0WDM}=ZQk3hEOLB?he%ayMYNZ=al4#wVp_$y7 zFwv}b8#In$gmGz3){YE`hH>ez{)F>-o!9y4dEU=I@P2Neb9LM;qb>sgyPeM1y91zh zDhi~V$O`RJ>+5Ts; zBR#b*+l6|0-!!a@58dA-48LN2_d!5swt5+@uJupPfn&V zM8^i=x&}nslW$XweV`24!x>k{&tJV%k)T7qe+|U}?6YcQVOj@tz9~15CUzk@!t2%WNDN!FGgqCTMCGwB zFjr0XLhmt&NG&&DuFRRD*CfG-ATe=*dr<}o8P0z9m*C6?KW0wQewe5|$6$!RD@Q{c zm%ME10Ldbf;ozSa5Jx3#*Dzu~K zNazY6wSHvW8w+S?q{xxSuI*(t#1R z$p)A;3j#R|+46$16=Uv%M8oZ!JklY{!-tN%emsjuj`atnGt>&|h*FPh2<+HuL7rYm zx@N$(Avd~6U_R}<+qJKNd6G~^Vz1c+)l+CbO?Zi19+i1xDKkrF$=}w^!$kDpQH@o* zWu|WOXxG@xQuTGGjY;NEEB?0=o073MR^*fY9zJNJHP7O&9pM+gI1~58uG6D}ZsD)m z^;BrqtK2TyDzGk9vHOhq^myNI+O9*axVUaB~GEQO~YQ9wa@u`DAFQc$-x*g$_ zR2n@m-EvFVtB`e*#?UwHJU2mQeQIYdPE`dS@yAV7-c2z#=-6=h`ZNRY*qZt+GN45z zjPirRBlCzm1fTx#6$1e-$}D}uGD6Kw>T3O7QkYO^WVv{y-UrFNGyfQICXg&>|<#_IbjgnQPR8Zyk%-F9-P6$U}E^O4*gNEv^-2 z7R}sNC^X1T*idDypOb>s6PFbE4O_#pt76GIo}dERjNOgs8Zq0KB}$vQOjG!MEWc6g zu^3`iFTr8S2hb)Ll^Zpf>khus>bsCO)1ZOI(~uo;J9|G(Ed4?f{Qf>H=(>a-3b9Ik zuLjPLw<**5Rt++^o0TbPDBmbfDk4DT3H&W(m_E=G~+>6P`iDIS?D(~%- zi9B-?dSi4hh>J<9-xQ$-lnnG65ZMZ-GWRL8@=ln%T*WFSoswc=^#{ww(2ks$XlfJ?HjGT5YaL z!}|~8{8Gd)6+1{#d5STulnV{e%RVg>g91q4Z4DG&2vZ{ z?1!+qdj71x<~3B(Uh1dFLuF0(>zS!LNMTxL(u-S4L)D;oT7zN5X2gdWe2lO!Tr^xC z`%Vz=FWYLu%P(7(92id$ROK?(5>-*y$T%|$uP~@f$T<-$7nQV+n?^2yqU^tfNjKf?#g(WkYk zqdmltBnpWwbcUOsY3AFs9Fx^?d6zhrB2;*f{yV0wf3T!$_KRBLPYuEH(9IE*HzP+p zLYG{L7iS`8exe4*#Vl{}-1I bZK`IR4QHG)RCU`q1>khr)xPdzz|VgJ322HZ literal 0 HcmV?d00001 diff --git a/addons/skin.estouchy/media/icon_breadcrumb_favourites.png b/addons/skin.estouchy/media/icon_breadcrumb_favourites.png new file mode 100644 index 0000000000000000000000000000000000000000..eb183e8f9b15f593ea83d12b6edf1d96f3a873d5 GIT binary patch literal 828 zcmV-C1H=4@P)4vLaU3Uc!`v-$eU27x_R3$%M$Y9MU23< zi%_>%2@jg6AhdN7rA=Ac)ZKkw2W@$~`__Hy?t4x!-@DE*-<$c(Z+IB4z~kvj@N)4+V@u z@)_qJo%q0lUQ6!FFHCqXSTU3i9*u_qQ;0|3@ z41JDEG^YF}h$IL`m@v@uB4;xb{LC(D$}btId+zcnGr{S&^pR~Mcrsn$mjq1H3oyp{ z%{RlW&8+HeGEOD-Y}U)|cn?bYOxeRrd)8}iY&aQYHN(YL zS!EEXN#`^QwT$9FqNIwSSJawZDdM1o_nPwFC_&!QVo?D>F}LyR9B;|^!xuU@X>A2q z$~{&L=`C~Dw5U5&GLKP1g0IOto*Jf}bwh&d)E|dlE^dS8bQm|e)(h6~-S8&yQDgl* z_-Pv1Q01A$_hc&gkyaM`NrkoZ_n1(U0!qI)3-QFlIi!pQ-G%hgNhdzt#s9_`>ymVz z&iqA~r4a>%MrL*HC?WbRO4gjht26v0<@*BIC}&dV7rd6d8e-!n+i^p0Gek*Ds}hEE zTHUf>4k=_hZs0X~DM)BnKrd@?Ek96bzF>3AK2=$uO@Du)(8fZ{h6!-h{AZ#!nhg+Q zhPvc83+&V}qlv}{VaQ{MWhGE>ja{pkE{FpwB|y1fb*h(2#d zc#y@roX#`b>_Ox$(Q16LaL~%qK{S3f@mY>${6H8F4e2EeY&75@4E*Mrd4e|x5acBE>-1BaBauiX5{X125rx04wzw&r`n`4l0000 - - 450 - 16x9_xPos_Relocation - Window_OpenClose_Animation_Zoom - - 80 - 90 - - - BehindDialogFadeOut - - 0 - 0 - 1120 - 60 - dialog_header.png - - - 0 - 60 - 1120 - 720 - dialog_back.png - - - header label - 20 - 0 - WindowTitleCommons - 1000 - - - - 1050 - 0 - DialogCloseButtonCommons - - - 0 - 60 - 1070 - 720 - panel.png - - - 1 - 60 - 1068 - 720 - 450 - 450 - 450 - 450 - 60 - 200 - - - 10 - 10 - 247 - 340 - black.png - - - 10 - 10 - 247 - 277 - keep - $INFO[ListItem.Icon] - - - 10 - 287 - 247 - 63 - black.png - - - 10 - 287 - 247 - 63 - font20 - center - center - selected - - - - - - 10 - 10 - 247 - 340 - list_focus.png - - - 10 - 10 - 247 - 277 - keep - $INFO[ListItem.Icon] - - - 10 - 287 - 247 - 63 - black.png - - - 10 - 287 - 247 - 63 - font20 - center - center - selected - - - - - - 1082 - 90 - 26 - 660 - white.png - white.png - white.png - blank.png - blank.png - 3 - 3 - 60 - 60 - false - vertical - Visible - Hidden - - - diff --git a/addons/skin.estouchy/xml/FileManager.xml b/addons/skin.estouchy/xml/FileManager.xml index 815e747b4e438..b33f5c9b65648 100644 --- a/addons/skin.estouchy/xml/FileManager.xml +++ b/addons/skin.estouchy/xml/FileManager.xml @@ -281,7 +281,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/Includes.xml b/addons/skin.estouchy/xml/Includes.xml index bb168e958a87f..edd607f544f63 100644 --- a/addons/skin.estouchy/xml/Includes.xml +++ b/addons/skin.estouchy/xml/Includes.xml @@ -64,6 +64,7 @@ icon_breadcrumb_pictures.png icon_breadcrumb_addons.png icon_breadcrumb_tv.png + icon_breadcrumb_favourites.png icon_breadcrumb_settings.png @@ -948,7 +949,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_menu_favourites.png !Skin.HasSetting(HideHomeButtonFavourites) diff --git a/addons/skin.estouchy/xml/MyFavourites.xml b/addons/skin.estouchy/xml/MyFavourites.xml new file mode 100644 index 0000000000000..80fa3b8546a63 --- /dev/null +++ b/addons/skin.estouchy/xml/MyFavourites.xml @@ -0,0 +1,46 @@ + + + 50 + 50,500 + ClearProperty(PopupMenuVisible,Home) + + CommonBackground + SideMenu + Header + CommonNowPlaying + Footer + + Window label + MediaWindowTitleCommons + + + + Window_OpenClose_Animation_Zoom + + + + + + + + + + + + + + + + + + + + ScrollBarCommons + BottomMenu + + 400 + 160 + + ScrollOffsetLabel + + diff --git a/addons/skin.estouchy/xml/MyWeather.xml b/addons/skin.estouchy/xml/MyWeather.xml index f097c8236130a..17b2e607cdec8 100644 --- a/addons/skin.estouchy/xml/MyWeather.xml +++ b/addons/skin.estouchy/xml/MyWeather.xml @@ -519,7 +519,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/Settings.xml b/addons/skin.estouchy/xml/Settings.xml index c67ca45bf59ac..870e1aa9d1a71 100644 --- a/addons/skin.estouchy/xml/Settings.xml +++ b/addons/skin.estouchy/xml/Settings.xml @@ -45,7 +45,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/SettingsCategory.xml b/addons/skin.estouchy/xml/SettingsCategory.xml index beb3f47279fe0..12cea16900157 100644 --- a/addons/skin.estouchy/xml/SettingsCategory.xml +++ b/addons/skin.estouchy/xml/SettingsCategory.xml @@ -153,7 +153,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/SettingsProfile.xml b/addons/skin.estouchy/xml/SettingsProfile.xml index 70a461b210f5b..c85b453797583 100644 --- a/addons/skin.estouchy/xml/SettingsProfile.xml +++ b/addons/skin.estouchy/xml/SettingsProfile.xml @@ -85,7 +85,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/SkinSettings.xml b/addons/skin.estouchy/xml/SkinSettings.xml index 446a892a71bc0..060304cb48b5e 100644 --- a/addons/skin.estouchy/xml/SkinSettings.xml +++ b/addons/skin.estouchy/xml/SkinSettings.xml @@ -99,7 +99,7 @@ - ActivateWindow(Favourites) + ActivateWindow(FavouritesBrowser) icon_button_favourites.png diff --git a/addons/skin.estouchy/xml/ViewsList.xml b/addons/skin.estouchy/xml/ViewsList.xml index fbeedd24da6b3..0aa95a4b33669 100644 --- a/addons/skin.estouchy/xml/ViewsList.xml +++ b/addons/skin.estouchy/xml/ViewsList.xml @@ -51,7 +51,7 @@ 62 $INFO[Listitem.Icon] keep - [Container.Content() + !Window.IsVisible(Pictures)] | Container.Content(Files) | Container.Content(Games) | Container.Content(Genres) | Container.Content(Years) | Container.Content(Directors) | Container.Content(Studios) | Container.Content(Countries) | Container.Content(Tags) | [Container.Content(Addons) + ListItem.IsFolder] | [Window.IsVisible(Pictures) + String.IsEmpty(Container.FolderPath)] + [Container.Content() + !Window.IsVisible(Pictures)] | Container.Content(Files) | Container.Content(Games) | Container.Content(Genres) | Container.Content(Years) | Container.Content(Directors) | Container.Content(Studios) | Container.Content(Countries) | Container.Content(Tags) | Container.Content(Favourites) | [Container.Content(Addons) + ListItem.IsFolder] | [Window.IsVisible(Pictures) + String.IsEmpty(Container.FolderPath)] 75 @@ -193,7 +193,7 @@ 62 $INFO[Listitem.Icon] keep - [Container.Content() + !Window.IsVisible(Pictures)] | Container.Content(Files) | Container.Content(Games) | Container.Content(Genres) | Container.Content(Years) | Container.Content(Directors) | Container.Content(Studios) | Container.Content(Countries) | Container.Content(Tags) | [Container.Content(Addons) + ListItem.IsFolder] | [Window.IsVisible(Pictures) + String.IsEmpty(Container.FolderPath)] + [Container.Content() + !Window.IsVisible(Pictures)] | Container.Content(Files) | Container.Content(Games) | Container.Content(Genres) | Container.Content(Years) | Container.Content(Directors) | Container.Content(Studios) | Container.Content(Countries) | Container.Content(Tags) | Container.Content(Favourites) | [Container.Content(Addons) + ListItem.IsFolder] | [Window.IsVisible(Pictures) + String.IsEmpty(Container.FolderPath)] 75 diff --git a/addons/skin.estouchy/xml/ViewsThumbnail.xml b/addons/skin.estouchy/xml/ViewsThumbnail.xml index 99a40208d45df..708ec6c799279 100644 --- a/addons/skin.estouchy/xml/ViewsThumbnail.xml +++ b/addons/skin.estouchy/xml/ViewsThumbnail.xml @@ -17,7 +17,7 @@ Conditional Conditional Conditional - + 5 0 @@ -89,7 +89,7 @@ $INFO[ListItem.Overlay] - + 5 0 @@ -422,6 +422,76 @@ !ListItem.IsResumable + + + 10 + 10 + 247 + 340 + black.png + + + 10 + 10 + 247 + 277 + keep + $INFO[ListItem.Icon] + + + 10 + 287 + 247 + 63 + black.png + + + 10 + 287 + 247 + 63 + font20 + center + center + selected + + + + + + 10 + 10 + 247 + 340 + list_focus.png + + + 10 + 10 + 247 + 277 + keep + $INFO[ListItem.Icon] + + + 10 + 287 + 247 + 63 + black.png + + + 10 + 287 + 247 + 63 + font20 + center + center + selected + + +
From febdad3beeb11723e03cd7c3a1937bda330a5f17 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:05:40 +0200 Subject: [PATCH 344/811] [Estuary] Retire Favourites dialog, use Favourites window instead. --- addons/skin.estuary/xml/DialogFavourites.xml | 76 -------------------- addons/skin.estuary/xml/Includes.xml | 2 +- 2 files changed, 1 insertion(+), 77 deletions(-) delete mode 100644 addons/skin.estuary/xml/DialogFavourites.xml diff --git a/addons/skin.estuary/xml/DialogFavourites.xml b/addons/skin.estuary/xml/DialogFavourites.xml deleted file mode 100644 index 73b6309421493..0000000000000 --- a/addons/skin.estuary/xml/DialogFavourites.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - 450 - Animation_DialogPopupOpenClose - - - 50% - 50% - 1540 - 858 - - - - - - - - - - - - - - 0 - 70 - 12 - 0 - 450 - 450 - vertical - - - 10 - 70 - 1600 - 0 - 60 - 60 - 450 - 450 - 2 - 60 - 200 - vertical - - - 10 - - - - - - - - 10 - - - - - - - - - 300 - 90 - 900 - 0 - center - center - - font45_title - Integer.IsEqual(Container(450).NumItems,0) - - - - diff --git a/addons/skin.estuary/xml/Includes.xml b/addons/skin.estuary/xml/Includes.xml index 4e1452ddcc47b..d1bb0bb0fea66 100644 --- a/addons/skin.estuary/xml/Includes.xml +++ b/addons/skin.estuary/xml/Includes.xml @@ -1439,7 +1439,7 @@ - + From 3507ca00e787e86cdcde2050384a45edd9fd06ff Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:06:14 +0200 Subject: [PATCH 345/811] [favourites] Remove CGUIDialogFavourites. --- xbmc/favourites/CMakeLists.txt | 2 - xbmc/favourites/GUIDialogFavourites.cpp | 213 ------------------------ xbmc/favourites/GUIDialogFavourites.h | 43 ----- xbmc/guilib/GUIWindowManager.cpp | 3 - xbmc/guilib/WindowIDs.dox | 6 +- xbmc/guilib/WindowIDs.h | 1 - xbmc/input/WindowTranslator.cpp | 1 - 7 files changed, 5 insertions(+), 264 deletions(-) delete mode 100644 xbmc/favourites/GUIDialogFavourites.cpp delete mode 100644 xbmc/favourites/GUIDialogFavourites.h diff --git a/xbmc/favourites/CMakeLists.txt b/xbmc/favourites/CMakeLists.txt index beee8bd679184..4397a1db798a5 100644 --- a/xbmc/favourites/CMakeLists.txt +++ b/xbmc/favourites/CMakeLists.txt @@ -1,5 +1,4 @@ set(SOURCES ContextMenus.cpp - GUIDialogFavourites.cpp GUIViewStateFavourites.cpp GUIWindowFavourites.cpp FavouritesService.cpp @@ -7,7 +6,6 @@ set(SOURCES ContextMenus.cpp FavouritesUtils.cpp) set(HEADERS ContextMenus.h - GUIDialogFavourites.h GUIViewStateFavourites.h GUIWindowFavourites.h FavouritesService.h diff --git a/xbmc/favourites/GUIDialogFavourites.cpp b/xbmc/favourites/GUIDialogFavourites.cpp deleted file mode 100644 index 7af6d29578870..0000000000000 --- a/xbmc/favourites/GUIDialogFavourites.cpp +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 "GUIDialogFavourites.h" - -#include "ContextMenuManager.h" -#include "ServiceBroker.h" -#include "dialogs/GUIDialogContextMenu.h" -#include "favourites/FavouritesURL.h" -#include "favourites/FavouritesUtils.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIMessage.h" -#include "guilib/GUIWindowManager.h" -#include "input/actions/ActionIDs.h" - -#define FAVOURITES_LIST 450 - -CGUIDialogFavourites::CGUIDialogFavourites() : - CGUIDialog(WINDOW_DIALOG_FAVOURITES, "DialogFavourites.xml"), - m_favouritesService(CServiceBroker::GetFavouritesService()) -{ - m_favourites = new CFileItemList; - m_loadType = KEEP_IN_MEMORY; -} - -CGUIDialogFavourites::~CGUIDialogFavourites(void) -{ - delete m_favourites; -} - -bool CGUIDialogFavourites::OnMessage(CGUIMessage &message) -{ - if (message.GetMessage() == GUI_MSG_CLICKED) - { - if (message.GetSenderId() == FAVOURITES_LIST) - { - int item = GetSelectedItem(); - int action = message.GetParam1(); - if (action == ACTION_SELECT_ITEM || action == ACTION_MOUSE_LEFT_CLICK) - OnClick(item); - else if (action == ACTION_MOVE_ITEM_UP) - OnMoveItem(item, -1); - else if (action == ACTION_MOVE_ITEM_DOWN) - OnMoveItem(item, 1); - else if (action == ACTION_CONTEXT_MENU || action == ACTION_MOUSE_RIGHT_CLICK) - OnPopupMenu(item); - else if (action == ACTION_DELETE_ITEM) - OnDelete(item); - else - return false; - return true; - } - } - else if (message.GetMessage() == GUI_MSG_WINDOW_DEINIT) - { - CGUIDialog::OnMessage(message); - // clear our favourites - CGUIMessage message(GUI_MSG_LABEL_RESET, GetID(), FAVOURITES_LIST); - OnMessage(message); - m_favourites->Clear(); - return true; - } - return CGUIDialog::OnMessage(message); -} - -void CGUIDialogFavourites::OnInitWindow() -{ - m_favouritesService.GetAll(*m_favourites); - UpdateList(); - CGUIWindow::OnInitWindow(); -} - -int CGUIDialogFavourites::GetSelectedItem() -{ - CGUIMessage message(GUI_MSG_ITEM_SELECTED, GetID(), FAVOURITES_LIST); - OnMessage(message); - return message.GetParam1(); -} - -void CGUIDialogFavourites::OnClick(int item) -{ - if (item < 0 || item >= m_favourites->Size()) - return; - - CGUIMessage message(GUI_MSG_EXECUTE, 0, GetID()); - message.SetStringParam(CFavouritesURL(*(*m_favourites)[item], GetID()).GetExecString()); - - Close(); - - CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); -} - -void CGUIDialogFavourites::OnPopupMenu(int item) -{ - if (item < 0 || item >= m_favourites->Size()) - return; - - // highlight the item - (*m_favourites)[item]->Select(true); - - CContextButtons choices; - if (m_favourites->Size() > 1) - { - choices.Add(1, 13332); // Move up - choices.Add(2, 13333); // Move down - } - choices.Add(3, 20019); // Choose thumbnail - choices.Add(4, 118); // Rename - choices.Add(5, 15015); // Remove - - CFileItemPtr itemPtr = m_favourites->Get(item); - - //temporary workaround until the context menu ids are removed - const int addonItemOffset = 10000; - - auto addonItems = CServiceBroker::GetContextMenuManager().GetAddonItems(*itemPtr); - - for (size_t i = 0; i < addonItems.size(); ++i) - choices.Add(addonItemOffset + i, addonItems[i]->GetLabel(*itemPtr)); - - int button = CGUIDialogContextMenu::ShowAndGetChoice(choices); - - // unhighlight the item - (*m_favourites)[item]->Select(false); - - if (button == 1) - OnMoveItem(item, -1); - else if (button == 2) - OnMoveItem(item, +1); - else if (button == 3) - OnSetThumb(item); - else if (button == 4) - OnRename(item); - else if (button == 5) - OnDelete(item); - else if (button >= addonItemOffset) - CONTEXTMENU::LoopFrom(*addonItems.at(button - addonItemOffset), itemPtr); -} - -void CGUIDialogFavourites::OnMoveItem(int item, int amount) -{ - if (item < 0 || item >= m_favourites->Size() || m_favourites->Size() <= 1 || 0 == amount) return; - - int nextItem = (item + amount) % m_favourites->Size(); - if (nextItem < 0) nextItem += m_favourites->Size(); - - m_favourites->Swap(item, nextItem); - m_favouritesService.Save(*m_favourites); - - CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), FAVOURITES_LIST, nextItem); - OnMessage(message); - - UpdateList(); -} - -void CGUIDialogFavourites::OnDelete(int item) -{ - if (item < 0 || item >= m_favourites->Size()) - return; - m_favourites->Remove(item); - m_favouritesService.Save(*m_favourites); - - CGUIMessage message(GUI_MSG_ITEM_SELECT, GetID(), FAVOURITES_LIST, item < m_favourites->Size() ? item : item - 1); - OnMessage(message); - - UpdateList(); -} - -void CGUIDialogFavourites::OnRename(int item) -{ - if (item < 0 || item >= m_favourites->Size()) - return; - - if (FAVOURITES_UTILS::ChooseAndSetNewName(*(*m_favourites)[item])) - { - m_favouritesService.Save(*m_favourites); - UpdateList(); - } -} - -void CGUIDialogFavourites::OnSetThumb(int item) -{ - if (item < 0 || item >= m_favourites->Size()) - return; - - if (FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*(*m_favourites)[item])) - { - m_favouritesService.Save(*m_favourites); - UpdateList(); - } -} - -void CGUIDialogFavourites::UpdateList() -{ - int currentItem = GetSelectedItem(); - CGUIMessage message(GUI_MSG_LABEL_BIND, GetID(), FAVOURITES_LIST, currentItem >= 0 ? currentItem : 0, 0, m_favourites); - OnMessage(message); -} - -CFileItemPtr CGUIDialogFavourites::GetCurrentListItem(int offset) -{ - int currentItem = GetSelectedItem(); - if (currentItem < 0 || !m_favourites->Size()) return CFileItemPtr(); - - int item = (currentItem + offset) % m_favourites->Size(); - if (item < 0) item += m_favourites->Size(); - return (*m_favourites)[item]; -} diff --git a/xbmc/favourites/GUIDialogFavourites.h b/xbmc/favourites/GUIDialogFavourites.h deleted file mode 100644 index 72d26bd659c6a..0000000000000 --- a/xbmc/favourites/GUIDialogFavourites.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 "favourites/FavouritesService.h" -#include "guilib/GUIDialog.h" - -class CFileItem; -class CFileItemList; - -class CGUIDialogFavourites : - public CGUIDialog -{ -public: - CGUIDialogFavourites(void); - ~CGUIDialogFavourites(void) override; - bool OnMessage(CGUIMessage &message) override; - void OnInitWindow() override; - - CFileItemPtr GetCurrentListItem(int offset = 0) override; - - bool HasListItems() const override { return true; } - -protected: - int GetSelectedItem(); - void OnClick(int item); - void OnPopupMenu(int item); - void OnMoveItem(int item, int amount); - void OnDelete(int item); - void OnRename(int item); - void OnSetThumb(int item); - void UpdateList(); - -private: - CFileItemList* m_favourites; - CFavouritesService& m_favouritesService; -}; diff --git a/xbmc/guilib/GUIWindowManager.cpp b/xbmc/guilib/GUIWindowManager.cpp index a8da102614c5f..72e35badb1287 100644 --- a/xbmc/guilib/GUIWindowManager.cpp +++ b/xbmc/guilib/GUIWindowManager.cpp @@ -22,7 +22,6 @@ #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "events/windows/GUIWindowEventLog.h" -#include "favourites/GUIDialogFavourites.h" #include "favourites/GUIWindowFavourites.h" #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" @@ -229,7 +228,6 @@ void CGUIWindowManager::CreateWindows() Add(new CGUIDialogNetworkSetup); Add(new CGUIDialogMediaSource); Add(new CGUIDialogProfileSettings); - Add(new CGUIDialogFavourites); Add(new CGUIDialogSongInfo); Add(new CGUIDialogSmartPlaylistEditor); Add(new CGUIDialogSmartPlaylistRule); @@ -364,7 +362,6 @@ bool CGUIWindowManager::DestroyWindows() DestroyWindow(WINDOW_DIALOG_CONTENT_SETTINGS); DestroyWindow(WINDOW_DIALOG_INFOPROVIDER_SETTINGS); DestroyWindow(WINDOW_DIALOG_LIBEXPORT_SETTINGS); - DestroyWindow(WINDOW_DIALOG_FAVOURITES); DestroyWindow(WINDOW_DIALOG_SONG_INFO); DestroyWindow(WINDOW_DIALOG_SMART_PLAYLIST_EDITOR); DestroyWindow(WINDOW_DIALOG_SMART_PLAYLIST_RULE); diff --git a/xbmc/guilib/WindowIDs.dox b/xbmc/guilib/WindowIDs.dox index 68fdc7dc0893a..21b158ca1a32d 100644 --- a/xbmc/guilib/WindowIDs.dox +++ b/xbmc/guilib/WindowIDs.dox @@ -56,7 +56,6 @@ This page shows the window names, the window definition, the window ID and the s | LockSettings | WINDOW_DIALOG_LOCK_SETTINGS | 10131 | DialogSettings.xml | | | ContentSettings | WINDOW_DIALOG_CONTENT_SETTINGS | 10132 | DialogSettings.xml | | | LibexportSettings | WINDOW_DIALOG_LIBEXPORT_SETTINGS | 10133 | DialogSettings.xml | | -| Favourites | WINDOW_DIALOG_FAVOURITES | 10134 | DialogFavourites.xml | @deprecated Dialog **Favourites** is deprecated and is scheduled for removal in v21.

@skinning_v20 Deprecated. Please use **FavouritesBrowser** window instead.

| | SongInformation | WINDOW_DIALOG_SONG_INFO | 10135 | DialogMusicInfo.xml | | | SmartPlaylistEditor | WINDOW_DIALOG_SMART_PLAYLIST_EDITOR | 10136 | SmartPlaylistEditor.xml | | | SmartPlaylistRule | WINDOW_DIALOG_SMART_PLAYLIST_RULE | 10137 | SmartPlaylistRule.xml | | @@ -141,5 +140,10 @@ This page shows the window names, the window definition, the window ID and the s | Startup | WINDOW_STARTUP_ANIM | 12999 | Startup.xml | | | FavouritesBrowser | WINDOW_FAVOURITES | 10060 | MyFavourites.xml | @skinning_v20 **New window** FavouritesBrowser, replaces the old Favourites dialog.

| +\section window_ids_rm Additional revision history + +\subsection modules_rm_windowids_v21 Kodi v21 (Omega) +@skinning_v21 **[Removed Windows]** The following windows have been removed: +- **Favourites** - Window Favourites (DialogFavourites.xml with id 10134) has been removed. Please use **FavouritesBrowser** instead. */ diff --git a/xbmc/guilib/WindowIDs.h b/xbmc/guilib/WindowIDs.h index d08354d11280c..716a341e81ee1 100644 --- a/xbmc/guilib/WindowIDs.h +++ b/xbmc/guilib/WindowIDs.h @@ -72,7 +72,6 @@ #define WINDOW_DIALOG_LOCK_SETTINGS 10131 #define WINDOW_DIALOG_CONTENT_SETTINGS 10132 #define WINDOW_DIALOG_LIBEXPORT_SETTINGS 10133 -#define WINDOW_DIALOG_FAVOURITES 10134 #define WINDOW_DIALOG_SONG_INFO 10135 #define WINDOW_DIALOG_SMART_PLAYLIST_EDITOR 10136 #define WINDOW_DIALOG_SMART_PLAYLIST_RULE 10137 diff --git a/xbmc/input/WindowTranslator.cpp b/xbmc/input/WindowTranslator.cpp index a6b7b3b86dd1f..f23cb5dff8f8e 100644 --- a/xbmc/input/WindowTranslator.cpp +++ b/xbmc/input/WindowTranslator.cpp @@ -78,7 +78,6 @@ const CWindowTranslator::WindowMapByName CWindowTranslator::WindowMappingByName {"virtualkeyboard", WINDOW_DIALOG_KEYBOARD}, {"volumebar", WINDOW_DIALOG_VOLUME_BAR}, {"submenu", WINDOW_DIALOG_SUB_MENU}, - {"favourites", WINDOW_DIALOG_FAVOURITES}, {"contextmenu", WINDOW_DIALOG_CONTEXT_MENU}, {"notification", WINDOW_DIALOG_KAI_TOAST}, {"numericinput", WINDOW_DIALOG_NUMERIC}, From 6e4793f903c174cc7cde1a8045afca54d888a1cd Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Tue, 3 Oct 2023 17:08:14 +0200 Subject: [PATCH 346/811] [PosixTimezone][webOS] Use realpath to resolve localtime path On webOS /etc/localtime is a symlink to /var/luna/preferences/localtime which in turn is another symlink to the real timezone file. In this case the timezone file must be resolved twice which realpath does --- xbmc/platform/posix/PosixTimezone.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/xbmc/platform/posix/PosixTimezone.cpp b/xbmc/platform/posix/PosixTimezone.cpp index 823394b4168d8..e76fd722e90a9 100644 --- a/xbmc/platform/posix/PosixTimezone.cpp +++ b/xbmc/platform/posix/PosixTimezone.cpp @@ -216,12 +216,9 @@ std::string CPosixTimezone::GetOSConfiguredTimezone() std::string CPosixTimezone::ReadFromLocaltime(const std::string_view filename) { char path[PATH_MAX]; - ssize_t len = readlink(filename.data(), path, sizeof(path) - 1); - - if (len == -1) + if(realpath(filename.data(), path) == nullptr) return ""; - path[len] = '\0'; // Read the timezone starting from the second last occurrence of / std::string str = path; size_t pos = str.rfind('/'); From eca4a4bc440fec65c561c6d979975655d2c9bcb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Tue, 3 Oct 2023 22:14:36 +0200 Subject: [PATCH 347/811] Revert "[AddonSettings] Simplify code by removing member strings" This reverts commit e02c6f09b904d90749b3a7bf7453b5d456f99616. --- xbmc/addons/settings/AddonSettings.cpp | 28 ++++++++++++-------------- xbmc/addons/settings/AddonSettings.h | 6 +++++- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/xbmc/addons/settings/AddonSettings.cpp b/xbmc/addons/settings/AddonSettings.cpp index cdee1b2df84c1..be61dd8b29760 100644 --- a/xbmc/addons/settings/AddonSettings.cpp +++ b/xbmc/addons/settings/AddonSettings.cpp @@ -154,11 +154,14 @@ namespace ADDON CAddonSettings::CAddonSettings(const std::shared_ptr& addon, AddonInstanceId instanceId) : CSettingsBase(), + m_addonId(addon->ID()), + m_addonPath(addon->Path()), + m_addonProfile(addon->Profile()), m_instanceId(instanceId), m_addon{addon}, m_unknownSettingLabelId(UnknownSettingLabelIdStart), m_logger(CServiceBroker::GetLogging().GetLogger( - StringUtils::Format("CAddonSettings[{}@{}]", m_instanceId, addon->ID()))) + StringUtils::Format("CAddonSettings[{}@{}]", m_instanceId, m_addonId))) { } @@ -173,11 +176,6 @@ std::shared_ptr CAddonSettings::CreateSetting( return CSettingCreator::CreateSetting(settingType, settingId, settingsManager); } -std::string CAddonSettings::GetAddonId() const -{ - return m_addon->ID(); -} - void CAddonSettings::OnSettingAction(const std::shared_ptr& setting) { std::string actionData; @@ -191,9 +189,9 @@ void CAddonSettings::OnSettingAction(const std::shared_ptr& sett { actionData = settingAction->GetData(); // replace $CWD with the url of the add-on - StringUtils::Replace(actionData, "$CWD", m_addon->Path()); + StringUtils::Replace(actionData, "$CWD", m_addonPath); // replace $ID with the id of the add-on - StringUtils::Replace(actionData, "$ID", m_addon->ID()); + StringUtils::Replace(actionData, "$ID", m_addonId); } } @@ -229,7 +227,7 @@ bool CAddonSettings::AddInstanceSettings() CLog::Log( LOGDEBUG, "CAddonSettings::{} - Add-on {} using instance setting values byself, Kodi's add ignored", - __func__, m_addon->ID()); + __func__, m_addonId); return true; } @@ -812,7 +810,7 @@ bool CAddonSettings::InitializeFromOldSettingDefinitions(const CXBMCTinyXML& doc return false; std::shared_ptr section = - std::make_shared(m_addon->ID(), GetSettingsManager()); + std::make_shared(m_addonId, GetSettingsManager()); std::shared_ptr category; uint32_t categoryId = 0; @@ -845,9 +843,9 @@ SettingPtr CAddonSettings::InitializeFromOldSettingAction(const std::string& set // parse the action attribute std::string action = XMLUtils::GetAttribute(settingElement, "action"); // replace $CWD with the url of the add-on - StringUtils::Replace(action, "$CWD", m_addon->Path()); + StringUtils::Replace(action, "$CWD", m_addonPath); // replace $ID with the id of the add-on - StringUtils::Replace(action, "$ID", m_addon->ID()); + StringUtils::Replace(action, "$ID", m_addonId); // prepare the setting's control auto control = std::make_shared(); @@ -1314,7 +1312,7 @@ SettingPtr CAddonSettings::InitializeFromOldSettingEnums( for (uint32_t i = 0; i < values.size(); ++i) { int label = static_cast(strtol(values[i].c_str(), nullptr, 0)); - std::string value = g_localizeStrings.GetAddonString(m_addon->ID(), label); + std::string value = g_localizeStrings.GetAddonString(m_addonId, label); if (settingEntries.size() > i) value = settingEntries[i]; @@ -1464,9 +1462,9 @@ SettingPtr CAddonSettings::InitializeFromOldSettingFileWithSource( setting->SetDefault(defaultValue); if (source.find("$PROFILE") != std::string::npos) - StringUtils::Replace(source, "$PROFILE", m_addon->Profile()); + StringUtils::Replace(source, "$PROFILE", m_addonProfile); else - source = URIUtils::AddFileToFolder(m_addon->Path(), source); + source = URIUtils::AddFileToFolder(m_addonPath, source); setting->SetSources({source}); diff --git a/xbmc/addons/settings/AddonSettings.h b/xbmc/addons/settings/AddonSettings.h index 1e23174e485f2..510f14a71f97e 100644 --- a/xbmc/addons/settings/AddonSettings.h +++ b/xbmc/addons/settings/AddonSettings.h @@ -60,7 +60,7 @@ class CAddonSettings : public CSettingsBase, // implementation of ISettingCallback void OnSettingAction(const std::shared_ptr& setting) override; - std::string GetAddonId() const; + const std::string& GetAddonId() const { return m_addonId; } bool Initialize(const CXBMCTinyXML& doc, bool allowEmpty = false); bool Load(const CXBMCTinyXML& doc); @@ -187,6 +187,10 @@ class CAddonSettings : public CSettingsBase, std::string& current, void* data); + // store these values so that we don't always have to access the weak pointer + const std::string m_addonId; + const std::string m_addonPath; + const std::string m_addonProfile; const AddonInstanceId m_instanceId{ADDON_SETTINGS_ID}; std::shared_ptr m_addon; From d07f989bbf6dd5ac1b698eaaf3b25936619922e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20H=C3=A4rer?= Date: Tue, 3 Oct 2023 22:22:06 +0200 Subject: [PATCH 348/811] CAddonSettings: Prevent reference cycle with CAddon CAddon::m_settings contains CSettingsData objects which contain CAddonSettings. The shared_ptr CAddonSettings::m_addon creates a cycle that prevents the addon from getting freed. Fixes #23786. --- xbmc/addons/settings/AddonSettings.cpp | 8 ++++++-- xbmc/addons/settings/AddonSettings.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/xbmc/addons/settings/AddonSettings.cpp b/xbmc/addons/settings/AddonSettings.cpp index be61dd8b29760..1d4b808b316f7 100644 --- a/xbmc/addons/settings/AddonSettings.cpp +++ b/xbmc/addons/settings/AddonSettings.cpp @@ -436,8 +436,12 @@ bool CAddonSettings::HasSettings() const bool CAddonSettings::Save() { - assert(m_addon); - return m_addon->SaveSettings(); + std::shared_ptr addon = m_addon.lock(); + assert(addon); + if (addon) + return addon->SaveSettings(); + else + return false; } std::string CAddonSettings::GetSettingLabel(int label) const diff --git a/xbmc/addons/settings/AddonSettings.h b/xbmc/addons/settings/AddonSettings.h index 510f14a71f97e..5661a1f1a8e54 100644 --- a/xbmc/addons/settings/AddonSettings.h +++ b/xbmc/addons/settings/AddonSettings.h @@ -192,7 +192,7 @@ class CAddonSettings : public CSettingsBase, const std::string m_addonPath; const std::string m_addonProfile; const AddonInstanceId m_instanceId{ADDON_SETTINGS_ID}; - std::shared_ptr m_addon; + std::weak_ptr m_addon; uint32_t m_unidentifiedSettingId = 0; int m_unknownSettingLabelId; From 988bae69e7da9cfdc3e31c691f6c92dda5a9808d Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 3 Oct 2023 20:11:12 -0700 Subject: [PATCH 349/811] CNetworkLinux: implement ping using sockets instead of system call to ping Signed-off-by: Lukas Rusak --- xbmc/platform/linux/network/NetworkLinux.cpp | 151 +++++++++++++++++-- 1 file changed, 136 insertions(+), 15 deletions(-) diff --git a/xbmc/platform/linux/network/NetworkLinux.cpp b/xbmc/platform/linux/network/NetworkLinux.cpp index 4e9eda0d8d310..7afeb9de610cc 100644 --- a/xbmc/platform/linux/network/NetworkLinux.cpp +++ b/xbmc/platform/linux/network/NetworkLinux.cpp @@ -8,17 +8,63 @@ #include "NetworkLinux.h" +#include "utils/FileHandle.h" #include "utils/StringUtils.h" #include "utils/log.h" +#include #include #include #include #include #include +#include #include +#include #include +#include +#include + +using namespace KODI::UTILS::POSIX; + +namespace +{ + +constexpr unsigned int ICMP_PACKET_SIZE{64}; +constexpr unsigned int TTL{64}; + +struct IcmpPacket +{ + icmphdr header; + uint8_t data[ICMP_PACKET_SIZE - sizeof(icmphdr)]; + + uint16_t Checksum() + { + auto data = reinterpret_cast(&header); + unsigned int length = sizeof(header) + sizeof(data); + + unsigned int sum; + + for (sum = 0; length > 1; length -= 2) + { + sum += *data++; + } + + if (length == 1) + { + sum += *data; + } + + sum = (sum >> 16) + (sum & 0xFFFF); + + sum += (sum >> 16); + + return ~sum; + } +}; + +} // namespace CNetworkInterfaceLinux::CNetworkInterfaceLinux(CNetworkPosix* network, std::string interfaceName, @@ -199,25 +245,100 @@ std::vector CNetworkLinux::GetNameServers() bool CNetworkLinux::PingHost(unsigned long remote_ip, unsigned int timeout_ms) { - char cmd_line[64]; + CFileHandle fd(socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_ICMP)); + if (!fd) + { + CLog::Log(LOGERROR, "socket failed: {} ({})", strerror(errno), errno); + return false; + } + + int ret = setsockopt(fd, SOL_IP, IP_TTL, &TTL, sizeof(TTL)); + if (ret != 0) + { + CLog::Log(LOGERROR, "setsockopt failed: {} ({})", strerror(errno), errno); + return false; + } + + CFileHandle epfd(epoll_create1(EPOLL_CLOEXEC)); + if (!epfd) + { + CLog::Log(LOGERROR, "epoll_create1 failed: {} ({})", strerror(errno), errno); + return false; + } + + epoll_event event; + event.events = EPOLLIN; + event.data.fd = fd; + ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event); + if (ret < 0) + { + CLog::Log(LOGERROR, "epoll_ctl failed: {} ({})", strerror(errno), errno); + return false; + } + + IcmpPacket packet = {}; + + packet.header.type = ICMP_ECHO; + packet.header.un.echo.id = getpid(); + + packet.header.un.echo.sequence = 0; + packet.header.checksum = packet.Checksum(); - struct in_addr host_ip; - host_ip.s_addr = remote_ip; + sockaddr_in addr = {}; + addr.sin_addr.s_addr = remote_ip; + addr.sin_family = AF_INET; + + const auto start = std::chrono::steady_clock::now(); + + ret = sendto(fd, &packet, sizeof(packet), 0, reinterpret_cast(&addr), sizeof(addr)); + if (ret < 1) + { + CLog::Log(LOGERROR, "sendto failed: {} ({})", strerror(errno), errno); + return false; + } + + event = {}; + ret = epoll_wait(epfd, &event, 1, timeout_ms); + if (ret < 1) + { + if (ret == 0) + { + CLog::Log(LOGERROR, "timed out while waiting to receive ({} ms)", timeout_ms); + } + else + { + CLog::Log(LOGERROR, "epoll_wait failed: {} ({})", strerror(errno), errno); + } - snprintf(cmd_line, sizeof(cmd_line), "ping -c 1 -w %d %s", - timeout_ms / 1000 + (timeout_ms % 1000) != 0, inet_ntoa(host_ip)); + return false; + } - int status = -1; - status = system(cmd_line); - int result = WIFEXITED(status) ? WEXITSTATUS(status) : -1; + if (event.events & EPOLLIN) + { + socklen_t length = sizeof(addr); + ret = recvfrom(fd, &packet, sizeof(packet), 0, reinterpret_cast(&addr), &length); + if (ret < 1) + { + CLog::Log(LOGERROR, "recvfrom failed: {} ({})", strerror(errno), errno); + return false; + } + } - // http://linux.about.com/od/commands/l/blcmdl8_ping.htm ; - // 0 reply - // 1 no reply - // else some error + const auto end = std::chrono::steady_clock::now(); - if (result < 0 || result > 1) - CLog::Log(LOGERROR, "Ping fail : status = {}, errno = {} : '{}'", status, errno, cmd_line); + if (!(packet.header.type == ICMP_ECHOREPLY && packet.header.code == 0)) + { + CLog::Log(LOGERROR, "unexpected ping reply: type={} code={}", packet.header.type, + packet.header.code); + return false; + } + else + { + const auto duration = + std::chrono::duration_cast>(end - start); - return result == 0; + CLog::Log(LOGDEBUG, "PING {}: icmp_seq={} ttl={} time={:0.3f} ms", inet_ntoa(addr.sin_addr), + packet.header.un.echo.sequence, TTL, duration.count()); + return true; + } } From 149e11b99e0ee62d042fa49ff41d8018c41cb584 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Wed, 4 Oct 2023 14:59:15 +0200 Subject: [PATCH 350/811] [Android] Clang -march flag is not needed --- tools/depends/configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index add494f478a8d..4dc38e6ce1812 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -323,7 +323,7 @@ case $host in use_cpu="armeabi-v7a" fi if test "x$use_cpu" = "xarmeabi-v7a"; then - platform_cflags="${platform_cflags} -march=armv7-a -mtune=cortex-a9 -mfloat-abi=softfp" + platform_cflags="${platform_cflags} -mtune=cortex-a9 -mfloat-abi=softfp" fi platform_ldflags="${platform_ldflags} -Wl,--exclude-libs,libunwind.a" meson_cpu="arm" @@ -333,7 +333,7 @@ case $host in use_cpu="arm64-v8a" fi if test "x$use_cpu" = "xarm64-v8a"; then - platform_cflags="${platform_cflags} -march=armv8-a -mtune=cortex-a53" + platform_cflags="${platform_cflags} -mtune=cortex-a53" fi meson_cpu="aarch64" ;; From a3f644705c96f1ff81f008c9784b01615fe714e7 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 1 Oct 2023 14:52:46 +0200 Subject: [PATCH 351/811] [video][settings] Add default play action setting. --- .../resources/strings.po | 27 ++++++- system/settings/settings.xml | 11 +++ xbmc/settings/Settings.h | 1 + xbmc/video/guilib/CMakeLists.txt | 7 +- xbmc/video/guilib/VideoPlayAction.h | 24 +++++++ .../video/guilib/VideoPlayActionProcessor.cpp | 72 +++++++++++++++++++ xbmc/video/guilib/VideoPlayActionProcessor.h | 41 +++++++++++ 7 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 xbmc/video/guilib/VideoPlayAction.h create mode 100644 xbmc/video/guilib/VideoPlayActionProcessor.cpp create mode 100644 xbmc/video/guilib/VideoPlayActionProcessor.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index cb5a0b9cfd31d..5db113c3fde3a 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -5725,6 +5725,7 @@ msgstr "" #: xbmc/pvr/PVRGUIActionsPlayback.cpp #: xbmc/video/ContextMenus.cpp #: xbmc/video/guilib/VideoSelectActionProcessor.cpp +#: xbmc/video/guilib/VideoPlayActionProcessor.cpp msgctxt "#12021" msgid "Play from beginning" msgstr "" @@ -15309,7 +15310,25 @@ msgctxt "#22033" msgid "Charset" msgstr "" -#empty strings from id 22034 to 22078 +#empty strings from id 22034 to 22075 + +#. Label for setting to choose the default action for "play" +#: system/settings/settings.xml +msgctxt "#22076" +msgid "Default play action" +msgstr "" + +#. Label for value for default play action setting +#: system/settings/settings.xml +msgctxt "#22077" +msgid "Ask if resumable" +msgstr "" + +#. Label for value for default play action setting +#: system/settings/settings.xml +msgctxt "#22078" +msgid "Resume" +msgstr "" #: system/settings/settings.xml msgctxt "#22079" @@ -19638,7 +19657,11 @@ msgctxt "#36203" msgid "Enables the \"Personal Video Recorder\" (PVR) features. This requires that at least one PVR add-on is installed." msgstr "" -#empty string with id 36204 +#. Description of setting with label #22076 "Default play action" +#: system/settings/settings.xml +msgctxt "#36204" +msgid "Toggle between [Ask if resumable] (default) and [Resume].[CR][Ask if resumable] will ask whether to play from beginning or to resume (if a resume point is present).[CR][Resume] will automatically resume videos from the last position that you were viewing them.[CR]If no resume point is set, playback will automatically start from the beginning." +msgstr "" #: system/settings/settings.xml msgctxt "#36205" diff --git a/system/settings/settings.xml b/system/settings/settings.xml index d9218c470dc03..7154234eee724 100755 --- a/system/settings/settings.xml +++ b/system/settings/settings.xml @@ -1015,6 +1015,17 @@ + + 0 + 1 + + + + + + + + 2 false diff --git a/xbmc/settings/Settings.h b/xbmc/settings/Settings.h index 349a153065852..8a3dead5bbfd7 100644 --- a/xbmc/settings/Settings.h +++ b/xbmc/settings/Settings.h @@ -131,6 +131,7 @@ class CSettings : public CSettingsBase, public CSettingCreator, public CSettingC static constexpr auto SETTING_VIDEOPLAYER_SUPPORTMVC = "videoplayer.supportmvc"; static constexpr auto SETTING_VIDEOPLAYER_CONVERTDOVI = "videoplayer.convertdovi"; static constexpr auto SETTING_MYVIDEOS_SELECTACTION = "myvideos.selectaction"; + static constexpr auto SETTING_MYVIDEOS_PLAYACTION = "myvideos.playaction"; static constexpr auto SETTING_MYVIDEOS_USETAGS = "myvideos.usetags"; static constexpr auto SETTING_MYVIDEOS_EXTRACTFLAGS = "myvideos.extractflags"; static constexpr auto SETTING_MYVIDEOS_EXTRACTCHAPTERTHUMBS = "myvideos.extractchapterthumbs"; diff --git a/xbmc/video/guilib/CMakeLists.txt b/xbmc/video/guilib/CMakeLists.txt index fd16dee4cbef9..b0a93d5234a02 100644 --- a/xbmc/video/guilib/CMakeLists.txt +++ b/xbmc/video/guilib/CMakeLists.txt @@ -1,6 +1,9 @@ -set(SOURCES VideoSelectActionProcessor.cpp) +set(SOURCES VideoPlayActionProcessor.cpp + VideoSelectActionProcessor.cpp) -set(HEADERS VideoSelectAction.h +set(HEADERS VideoPlayAction.h + VideoPlayActionProcessor.h + VideoSelectAction.h VideoSelectActionProcessor.h) core_add_library(video_guilib) diff --git a/xbmc/video/guilib/VideoPlayAction.h b/xbmc/video/guilib/VideoPlayAction.h new file mode 100644 index 0000000000000..a5668756028b1 --- /dev/null +++ b/xbmc/video/guilib/VideoPlayAction.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 VIDEO +{ +namespace GUILIB +{ +// Note: Do not change the numerical values of the elements. Some of them are used as values for +// the integer setting SETTING_MYVIDEOS_PLAYACTION. +enum PlayAction +{ + PLAY_ACTION_PLAY_OR_RESUME = 1, // if resume is possible, ask user. play from beginning otherwise + PLAY_ACTION_RESUME = 2, // resume if possibly, play from beginning otherwise + PLAY_ACTION_PLAY_FROM_BEGINNING = 5, // play from beginning, also if resume would be possible +}; +} // namespace GUILIB +} // namespace VIDEO diff --git a/xbmc/video/guilib/VideoPlayActionProcessor.cpp b/xbmc/video/guilib/VideoPlayActionProcessor.cpp new file mode 100644 index 0000000000000..69f4919f3ecbb --- /dev/null +++ b/xbmc/video/guilib/VideoPlayActionProcessor.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "VideoPlayActionProcessor.h" + +#include "ServiceBroker.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "guilib/LocalizeStrings.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "video/VideoUtils.h" + +using namespace VIDEO::GUILIB; + +PlayAction CVideoPlayActionProcessorBase::GetDefaultPlayAction() +{ + return static_cast(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_MYVIDEOS_PLAYACTION)); +} + +bool CVideoPlayActionProcessorBase::Process() +{ + return Process(GetDefaultPlayAction()); +} + +bool CVideoPlayActionProcessorBase::Process(PlayAction PlayAction) +{ + switch (PlayAction) + { + case PLAY_ACTION_PLAY_OR_RESUME: + { + const VIDEO::GUILIB::PlayAction action = ChoosePlayOrResume(); + if (action < 0) + return true; // User cancelled the select menu. We're done. + + return Process(action); + } + + case PLAY_ACTION_RESUME: + return OnResumeSelected(); + + case PLAY_ACTION_PLAY_FROM_BEGINNING: + return OnPlaySelected(); + + default: + break; + } + return false; // We did not handle the action. +} + +PlayAction CVideoPlayActionProcessorBase::ChoosePlayOrResume() +{ + PlayAction action = PLAY_ACTION_PLAY_FROM_BEGINNING; + + const std::string resumeString = VIDEO_UTILS::GetResumeString(m_item); + if (!resumeString.empty()) + { + CContextButtons choices; + + choices.Add(PLAY_ACTION_RESUME, resumeString); + choices.Add(PLAY_ACTION_PLAY_FROM_BEGINNING, 12021); // Play from beginning + + action = static_cast(CGUIDialogContextMenu::ShowAndGetChoice(choices)); + } + + return action; +} diff --git a/xbmc/video/guilib/VideoPlayActionProcessor.h b/xbmc/video/guilib/VideoPlayActionProcessor.h new file mode 100644 index 0000000000000..9c7076d2f0e7e --- /dev/null +++ b/xbmc/video/guilib/VideoPlayActionProcessor.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "video/guilib/VideoPlayAction.h" + +class CFileItem; + +namespace VIDEO +{ +namespace GUILIB +{ +class CVideoPlayActionProcessorBase +{ +public: + explicit CVideoPlayActionProcessorBase(CFileItem& item) : m_item(item) {} + virtual ~CVideoPlayActionProcessorBase() = default; + + static PlayAction GetDefaultPlayAction(); + + bool Process(); + bool Process(PlayAction playAction); + +protected: + virtual bool OnResumeSelected() = 0; + virtual bool OnPlaySelected() = 0; + + CFileItem& m_item; + +private: + CVideoPlayActionProcessorBase() = delete; + PlayAction ChoosePlayOrResume(); +}; +} // namespace GUILIB +} // namespace VIDEO From 9f536cd3fc288bfb7064ccac87f4bf871010da91 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 1 Oct 2023 17:34:30 +0200 Subject: [PATCH 352/811] [video] CGUIWindowVideoBase: Add support for default play action setting. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 43 ++++++++++++++++++++++- xbmc/video/windows/GUIWindowVideoBase.h | 2 ++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 2d92bb2086cdc..2a81f0e48bb8e 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -56,6 +56,7 @@ #include "video/VideoLibraryQueue.h" #include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" #include "view/GUIViewState.h" @@ -724,9 +725,49 @@ void CGUIWindowVideoBase::LoadVideoInfo(CFileItemList& items, } } +namespace +{ +class CVideoPlayActionProcessor : public CVideoPlayActionProcessorBase +{ +public: + CVideoPlayActionProcessor(CGUIWindowVideoBase& window, + CFileItem& item, + int itemIndex, + const std::string& player) + : CVideoPlayActionProcessorBase(item), + m_window(window), + m_itemIndex(itemIndex), + m_player(player) + { + } + +protected: + bool OnResumeSelected() override + { + m_item.SetStartOffset(STARTOFFSET_RESUME); + return m_window.OnFileAction(m_itemIndex, SELECT_ACTION_RESUME, m_player); + } + + bool OnPlaySelected() override + { + m_item.SetStartOffset(0); + return m_window.OnFileAction(m_itemIndex, SELECT_ACTION_PLAY, m_player); + } + +private: + CGUIWindowVideoBase& m_window; + const int m_itemIndex{-1}; + const std::string m_player; +}; +} // namespace + bool CGUIWindowVideoBase::OnPlayOrResumeItem(int iItem, const std::string& player) { - return OnFileAction(iItem, SELECT_ACTION_PLAY_OR_RESUME, player); + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; + + CVideoPlayActionProcessor proc{*this, *m_vecItems->Get(iItem), iItem, player}; + return proc.Process(); } void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &buttons) diff --git a/xbmc/video/windows/GUIWindowVideoBase.h b/xbmc/video/windows/GUIWindowVideoBase.h index 38a54a40a3d4b..be3f5946205b3 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.h +++ b/xbmc/video/windows/GUIWindowVideoBase.h @@ -17,11 +17,13 @@ namespace { class CVideoSelectActionProcessor; +class CVideoPlayActionProcessor; } // unnamed namespace class CGUIWindowVideoBase : public CGUIMediaWindow, public IBackgroundLoaderObserver { friend class ::CVideoSelectActionProcessor; + friend class ::CVideoPlayActionProcessor; public: CGUIWindowVideoBase(int id, const std::string &xmlFile); From dc8a6e8478263b77d32f0c336cd27a940e549a33 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:24:40 +0200 Subject: [PATCH 353/811] [PVR] CGUIWindowPVRRecordingsBase: Add support for default play action setting. --- xbmc/pvr/windows/GUIWindowPVRRecordings.cpp | 53 ++++++++++++++++----- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp index c5f3ba1bb3b26..d939d741b5eb5 100644 --- a/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRRecordings.cpp @@ -29,6 +29,7 @@ #include "utils/URIUtils.h" #include "video/VideoLibraryQueue.h" #include "video/VideoUtils.h" +#include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" #include "video/windows/GUIWindowVideoBase.h" @@ -275,6 +276,45 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc CGUIWindowPVRRecordingsBase& m_window; const int m_itemIndex{-1}; }; + +class CVideoPlayActionProcessor : public CVideoPlayActionProcessorBase +{ +public: + explicit CVideoPlayActionProcessor(CFileItem& item) : CVideoPlayActionProcessorBase(item) {} + +protected: + bool OnResumeSelected() override + { + if (m_item.m_bIsFolder) + { + m_item.SetStartOffset(STARTOFFSET_RESUME); + CServiceBroker::GetPVRManager().Get().PlayRecordingFolder( + m_item, false /* no resume check */); + } + else + { + CServiceBroker::GetPVRManager().Get().ResumePlayRecording( + m_item, true /* fall back to play if no resume possible */); + } + return true; + } + + bool OnPlaySelected() override + { + if (m_item.m_bIsFolder) + { + m_item.SetStartOffset(0); + CServiceBroker::GetPVRManager().Get().PlayRecordingFolder( + m_item, false /* no resume check */); + } + else + { + CServiceBroker::GetPVRManager().Get().PlayRecording( + m_item, false /* no resume check */); + } + return true; + } +}; } // namespace bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) @@ -306,17 +346,8 @@ bool CGUIWindowPVRRecordingsBase::OnMessage(CGUIMessage& message) if (!item->IsParentFolder() && message.GetParam1() == ACTION_PLAYER_PLAY) { - if (item->m_bIsFolder) - { - CServiceBroker::GetPVRManager().Get().PlayRecordingFolder( - *item, true /* check resume */); - } - else - { - CServiceBroker::GetPVRManager().Get().PlayRecording( - *item, true /* check resume */); - } - bReturn = true; + CVideoPlayActionProcessor proc{*item}; + bReturn = proc.Process(); } else if (item->m_bIsFolder) { From 5087071af7700150383e78f5771c123e30428e1d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 2 Oct 2023 12:57:57 +0200 Subject: [PATCH 354/811] [favourites] CGUIWindowFavourites: Add support for default play action setting. --- xbmc/favourites/GUIWindowFavourites.cpp | 74 ++++++++++++++++--------- 1 file changed, 48 insertions(+), 26 deletions(-) diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index dbe7dffc2fbc3..2715f32bd2f1a 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -25,6 +25,7 @@ #include "utils/StringUtils.h" #include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" CGUIWindowFavourites::CGUIWindowFavourites() @@ -117,6 +118,25 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc return true; } }; + +class CVideoPlayActionProcessor : public VIDEO::GUILIB::CVideoPlayActionProcessorBase +{ +public: + explicit CVideoPlayActionProcessor(CFileItem& item) : CVideoPlayActionProcessorBase(item) {} + +protected: + bool OnResumeSelected() override + { + ExecuteAction({"PlayMedia", m_item, "resume"}); + return true; + } + + bool OnPlaySelected() override + { + ExecuteAction({"PlayMedia", m_item, "noresume"}); + return true; + } +}; } // namespace bool CGUIWindowFavourites::OnSelect(int item) @@ -128,19 +148,20 @@ bool CGUIWindowFavourites::OnSelect(int item) if (!favURL.IsValid()) return false; - if (favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) + CFileItem targetItem{favURL.GetTarget(), favURL.IsDir()}; + targetItem.LoadDetails(); + + const bool isPlayMedia{favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA}; + + // video select action setting is for files only, except exec func is playmedia... + if (targetItem.HasVideoInfoTag() && (!targetItem.m_bIsFolder || isPlayMedia)) { - // Resolve the favourite - CFileItem targetItem{favURL.GetTarget(), favURL.IsDir()}; - targetItem.LoadDetails(); - if (targetItem.IsVideo() || (targetItem.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(targetItem))) - { - CVideoSelectActionProcessor proc{targetItem}; - if (proc.Process()) - return true; - } + CVideoSelectActionProcessor proc{targetItem}; + if (proc.Process()) + return true; } + // exec the execute string for the original (!) item return ExecuteAction(favURL.GetExecString()); } @@ -156,26 +177,27 @@ bool CGUIWindowFavourites::OnAction(const CAction& action) if (!favURL.IsValid()) return false; - // If action is playmedia, just play it - if (favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) - return ExecuteAction(favURL.GetExecString()); + CFileItem item{favURL.GetTarget(), favURL.IsDir()}; + item.LoadDetails(); - // Resolve and check the target - const auto item = std::make_shared(favURL.GetTarget(), favURL.IsDir()); - if (CPlayerUtils::IsItemPlayable(*item)) + // video play action setting is for files and folders... + if (item.HasVideoInfoTag() || (item.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(item))) { - CFavouritesURL target(*item, {}); - if (target.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) - { - return ExecuteAction(target.GetExecString()); - } - else + CVideoPlayActionProcessor proc{item}; + if (proc.Process()) + return true; + } + + if (CPlayerUtils::IsItemPlayable(item)) + { + CFavouritesURL target{item, {}}; + if (target.GetAction() != CFavouritesURL::Action::PLAY_MEDIA) { - // build and execute a playmedia execute string - target = CFavouritesURL(CFavouritesURL::Action::PLAY_MEDIA, - {StringUtils::Paramify(item->GetPath())}); - return ExecuteAction(target.GetExecString()); + // build a playmedia execute string for given target + target = CFavouritesURL{CFavouritesURL::Action::PLAY_MEDIA, + {StringUtils::Paramify(item.GetPath())}}; } + return ExecuteAction(target.GetExecString()); } return false; } From dd4f5d6190498710f116f7bcecc7e8d6da767144 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 1 Oct 2023 18:14:31 +0200 Subject: [PATCH 355/811] [listproviders] CDirectoryProvider: Add support for default play action setting. --- xbmc/listproviders/DirectoryProvider.cpp | 120 +++++++++++++---------- 1 file changed, 70 insertions(+), 50 deletions(-) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index a4713c2827693..3f96e13cbc8b8 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -40,6 +40,7 @@ #include "video/VideoThumbLoader.h" #include "video/VideoUtils.h" #include "video/dialogs/GUIDialogVideoInfo.h" +#include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" #include @@ -468,6 +469,11 @@ bool ExecuteAction(const std::string& execute) return false; } +bool ExecuteAction(const CExecString& execute) +{ + return ExecuteAction(execute.GetExecString()); +} + class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessorBase { public: @@ -480,25 +486,25 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc bool OnPlayPartSelected(unsigned int part) override { // part numbers are 1-based - BuildAndExecAction("PlayMedia", StringUtils::Format("playoffset={}", part - 1)); + ExecuteAction({"PlayMedia", m_item, StringUtils::Format("playoffset={}", part - 1)}); return true; } bool OnResumeSelected() override { - BuildAndExecAction("PlayMedia", "resume"); + ExecuteAction({"PlayMedia", m_item, "resume"}); return true; } bool OnPlaySelected() override { - BuildAndExecAction("PlayMedia", "noresume"); + ExecuteAction({"PlayMedia", m_item, "noresume"}); return true; } bool OnQueueSelected() override { - BuildAndExecAction("QueueMedia", ""); + ExecuteAction({"QueueMedia", m_item, ""}); return true; } @@ -515,92 +521,106 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc } private: - void BuildAndExecAction(const std::string& method, const std::string& param) - { - std::vector params{StringUtils::Paramify(m_item.GetPath())}; - if (m_item.m_bIsFolder) - params.emplace_back("isdir"); + CDirectoryProvider& m_provider; +}; - if (!param.empty()) - params.emplace_back(param); +class CVideoPlayActionProcessor : public VIDEO::GUILIB::CVideoPlayActionProcessorBase +{ +public: + explicit CVideoPlayActionProcessor(CFileItem& item) : CVideoPlayActionProcessorBase(item) {} - const CExecString es{method, params}; - ExecuteAction(es.GetExecString()); +protected: + bool OnResumeSelected() override + { + ExecuteAction({"PlayMedia", m_item, "resume"}); + return true; } - CDirectoryProvider& m_provider; + bool OnPlaySelected() override + { + ExecuteAction({"PlayMedia", m_item, "noresume"}); + return true; + } }; } // namespace bool CDirectoryProvider::OnClick(const CGUIListItemPtr& item) { - CFileItem fileItem(*std::static_pointer_cast(item)); + CFileItem targetItem{*std::static_pointer_cast(item)}; + + bool isPlayMedia{false}; - if (fileItem.IsFavourite()) + if (targetItem.IsFavourite()) { // Resolve the favourite - const CFavouritesURL url{fileItem.GetPath()}; + const CFavouritesURL url{targetItem.GetPath()}; if (!url.IsValid()) return false; - if (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) - { - CFileItem targetItem{url.GetTarget(), url.IsDir()}; - targetItem.LoadDetails(); - if (targetItem.IsVideo() || - (targetItem.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(targetItem))) - { - CVideoSelectActionProcessor proc{*this, targetItem}; - if (proc.Process()) - return true; - } - } + targetItem = {url.GetTarget(), url.IsDir()}; + targetItem.LoadDetails(); + + isPlayMedia = (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA); } - else if (fileItem.HasVideoInfoTag()) + else { - CVideoSelectActionProcessor proc{*this, fileItem}; + const CExecString exec{targetItem, GetTarget(targetItem)}; + isPlayMedia = (exec.GetFunction() == "playmedia"); + } + + // video select action setting is for files only, except exec func is playmedia... + if (targetItem.HasVideoInfoTag() && (!targetItem.m_bIsFolder || isPlayMedia)) + { + CVideoSelectActionProcessor proc{*this, targetItem}; if (proc.Process()) return true; } + // exec the execute string for the original (!) item + CFileItem fileItem{*std::static_pointer_cast(item)}; + if (fileItem.HasProperty("node.target_url")) fileItem.SetPath(fileItem.GetProperty("node.target_url").asString()); - // grab and execute the execute string - return ExecuteAction(CExecString(fileItem, GetTarget(fileItem)).GetExecString()); + return ExecuteAction({fileItem, GetTarget(fileItem)}); } bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) { - CFileItem fileItem(*std::static_pointer_cast(item)); + CFileItem targetItem{*std::static_pointer_cast(item)}; - if (fileItem.IsFavourite()) + if (targetItem.IsFavourite()) { // Resolve the favourite - const CFavouritesURL url(fileItem.GetPath()); - if (url.IsValid()) - { - // If action is playmedia, just play it - if (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA) - return ExecuteAction(url.GetExecString()); + const CFavouritesURL url(targetItem.GetPath()); + if (!url.IsValid()) + return false; - CFileItem targetItem(url.GetTarget(), url.IsDir()); - fileItem = targetItem; - } + targetItem = {url.GetTarget(), url.IsDir()}; + targetItem.LoadDetails(); + } + + // video play action setting is for files and folders... + if (targetItem.HasVideoInfoTag() || + (targetItem.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(targetItem))) + { + CVideoPlayActionProcessor proc{targetItem}; + if (proc.Process()) + return true; } - if (CPlayerUtils::IsItemPlayable(fileItem)) + if (CPlayerUtils::IsItemPlayable(targetItem)) { - CExecString exec(fileItem, {}); + const CExecString exec{targetItem, GetTarget(targetItem)}; if (exec.GetFunction() == "playmedia") { - return ExecuteAction(exec.GetExecString()); + // exec as is + return ExecuteAction(exec); } else { - // build and execute a playmedia execute string - exec = CExecString("PlayMedia", {StringUtils::Paramify(fileItem.GetPath())}); - return ExecuteAction(exec.GetExecString()); + // build a playmedia execute string for given target and exec this + return ExecuteAction({"PlayMedia", targetItem, ""}); } } return true; From 829f0f3151a9f9e6448f6472d99280cd099f7a0e Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 5 Oct 2023 14:28:51 +1000 Subject: [PATCH 356/811] [cmake] Refactor and simplify FindFmt.cmake --- CMakeLists.txt | 11 +- cmake/modules/FindFmt.cmake | 190 ++++++++++------------- cmake/scripts/common/ModuleHelpers.cmake | 7 + 3 files changed, 102 insertions(+), 106 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b5b9da0d9637d..a821c6df6b4d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,8 +83,17 @@ dependent_option(ENABLE_INTERNAL_FMT "Enable internal fmt?") dependent_option(ENABLE_INTERNAL_NFS "Enable internal libnfs?") dependent_option(ENABLE_INTERNAL_PCRE "Enable internal pcre?") dependent_option(ENABLE_INTERNAL_RapidJSON "Enable internal rapidjson?") -dependent_option(ENABLE_INTERNAL_SPDLOG "Enable internal spdlog?") + +# If ENABLE_INTERNAL_FMT is ON, we force ENABLE_INTERNAL_SPDLOG ON as it has a hard +# dependency on fmt +if(ENABLE_INTERNAL_FMT) + option(ENABLE_INTERNAL_SPDLOG "Enable internal spdlog Forced" ON) +else() + dependent_option(ENABLE_INTERNAL_SPDLOG "Enable internal spdlog?") +endif() + dependent_option(ENABLE_INTERNAL_TAGLIB "Enable internal taglib?") + if(KODI_DEPENDSBUILD OR WIN32 OR WINDOWS_STORE) dependent_option(ENABLE_INTERNAL_TINYXML2 "Enable internal TinyXML2?") endif() diff --git a/cmake/modules/FindFmt.cmake b/cmake/modules/FindFmt.cmake index 0c263982198d4..cf78e6d24e4e4 100644 --- a/cmake/modules/FindFmt.cmake +++ b/cmake/modules/FindFmt.cmake @@ -6,44 +6,38 @@ # # fmt::fmt - The Fmt library -macro(buildFmt) - if(APPLE) - set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") - endif() - - set(FMT_VERSION ${${MODULE}_VER}) - # fmt debug uses postfix d for all platforms - set(FMT_DEBUG_POSTFIX d) - - if(WIN32 OR WINDOWS_STORE) - set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") - generate_patchcommand("${patches}") - endif() +if(NOT TARGET fmt::fmt) - set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} - -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} - -DFMT_DOC=OFF - -DFMT_TEST=OFF - -DFMT_INSTALL=ON - "${EXTRA_ARGS}") + include(cmake/scripts/common/ModuleHelpers.cmake) - BUILD_DEP_TARGET() -endmacro() + # Macro for building INTERNAL_FMT + macro(buildFmt) + if(APPLE) + set(EXTRA_ARGS "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + endif() -define_property(TARGET PROPERTY LIB_BUILD - BRIEF_DOCS "This target will be compiling the library" - FULL_DOCS "This target will be compiling the library") + set(FMT_VERSION ${${MODULE}_VER}) + # fmt debug uses postfix d for all platforms + set(FMT_DEBUG_POSTFIX d) -set(FORCE_BUILD OFF) + if(WIN32 OR WINDOWS_STORE) + set(patches "${CMAKE_SOURCE_DIR}/tools/depends/target/${MODULE_LC}/001-windows-pdb-symbol-gen.patch") + generate_patchcommand("${patches}") + endif() -# If target exists, no need to rerun find -# Allows a module that may be a dependency for multiple libraries to just be executed -# once to populate all required variables/targets -if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) + set(CMAKE_ARGS -DCMAKE_CXX_EXTENSIONS=${CMAKE_CXX_EXTENSIONS} + -DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} + -DFMT_DOC=OFF + -DFMT_TEST=OFF + -DFMT_INSTALL=ON + "${EXTRA_ARGS}") - include(cmake/scripts/common/ModuleHelpers.cmake) + BUILD_DEP_TARGET() + endmacro() set(MODULE_LC fmt) + # Default state + set(FORCE_BUILD OFF) SETUP_BUILD_VARS() @@ -52,39 +46,17 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) HINTS ${DEPENDS_PATH}/lib/cmake ${${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG}) - # Build if ENABLE_INTERNAL_FMT, or if required version in find_package call is greater - # than already found FMT_VERSION from a previous find_package call - if((Fmt_FIND_REQUIRED AND FMT_VERSION VERSION_LESS Fmt_FIND_VERSION AND ENABLE_INTERNAL_FMT) OR - (FMT_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_FMT) OR - ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_FMT)) - - if(Fmt_FIND_VERSION) - if(FMT_VERSION VERSION_LESS ${Fmt_FIND_VERSION}) - set(FORCE_BUILD ON) - endif() - endif() - - if(${FORCE_BUILD} OR FMT_VERSION VERSION_LESS ${${MODULE}_VER}) - - # Set FORCE_BUILD to enable fmt::fmt property that build will occur - set(FORCE_BUILD ON) - buildFmt() + if((FMT_VERSION VERSION_LESS ${${MODULE}_VER} AND ENABLE_INTERNAL_FMT) OR + ((CORE_SYSTEM_NAME STREQUAL linux OR CORE_SYSTEM_NAME STREQUAL freebsd) AND ENABLE_INTERNAL_FMT)) - else() - if(NOT TARGET fmt::fmt) - set(FMT_PKGCONFIG_CHECK ON) - endif() - endif() + # Set FORCE_BUILD to enable fmt::fmt property that build will occur + set(FORCE_BUILD ON) + buildFmt() else() if(NOT TARGET fmt::fmt) - set(FMT_PKGCONFIG_CHECK ON) - endif() - endif() - - if(NOT TARGET fmt::fmt) - if(FMT_PKGCONFIG_CHECK) - if(PKG_CONFIG_FOUND) + # Do not use pkgconfig on windows + if(PKG_CONFIG_FOUND AND NOT WIN32) pkg_check_modules(PC_FMT libfmt QUIET) set(FMT_VERSION ${PC_FMT_VERSION}) endif() @@ -103,23 +75,66 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) ${${CORE_PLATFORM_LC}_SEARCH_CONFIG} NO_CACHE) endif() + endif() - add_library(fmt::fmt UNKNOWN IMPORTED) - if(FMT_LIBRARY_RELEASE) - set_target_properties(fmt::fmt PROPERTIES - IMPORTED_CONFIGURATIONS RELEASE - IMPORTED_LOCATION_RELEASE "${FMT_LIBRARY_RELEASE}") - endif() - if(FMT_LIBRARY_DEBUG) + # fmt::fmt target exists and is of suitable versioning. the INTERNAL_FMT build + # is not created. + # We create variables based off TARGET data for use with FPHSA + if(TARGET fmt::fmt AND NOT TARGET fmt) + # This is for the case where a distro provides a non standard (Debug/Release) config type + # eg Debian's config file is fmtConfigTargets-none.cmake + # convert this back to either DEBUG/RELEASE or just RELEASE + # we only do this because we use find_package_handle_standard_args for config time output + # and it isnt capable of handling TARGETS, so we have to extract the info + get_target_property(_FMT_CONFIGURATIONS fmt::fmt IMPORTED_CONFIGURATIONS) + foreach(_fmt_config IN LISTS _FMT_CONFIGURATIONS) + # Some non standard config (eg None on Debian) + # Just set to RELEASE var so select_library_configurations can continue to work its magic + string(TOUPPER ${_fmt_config} _fmt_config_UPPER) + if((NOT ${_fmt_config_UPPER} STREQUAL "RELEASE") AND + (NOT ${_fmt_config_UPPER} STREQUAL "DEBUG")) + get_target_property(FMT_LIBRARY_RELEASE fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) + else() + get_target_property(FMT_LIBRARY_${_fmt_config_UPPER} fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) + endif() + endforeach() + + get_target_property(FMT_INCLUDE_DIR fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) + endif() + + include(SelectLibraryConfigurations) + select_library_configurations(FMT) + unset(FMT_LIBRARIES) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(Fmt + REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR + VERSION_VAR FMT_VERSION) + if(Fmt_FOUND) + if(TARGET fmt OR NOT TARGET fmt::fmt) + if(NOT TARGET fmt::fmt) + add_library(fmt::fmt UNKNOWN IMPORTED) + endif() + if(FMT_LIBRARY_RELEASE) + set_target_properties(fmt::fmt PROPERTIES + IMPORTED_CONFIGURATIONS RELEASE + IMPORTED_LOCATION_RELEASE "${FMT_LIBRARY_RELEASE}") + endif() + if(FMT_LIBRARY_DEBUG) + set_target_properties(fmt::fmt PROPERTIES + IMPORTED_CONFIGURATIONS DEBUG + IMPORTED_LOCATION_DEBUG "${FMT_LIBRARY_DEBUG}") + endif() set_target_properties(fmt::fmt PROPERTIES - IMPORTED_CONFIGURATIONS DEBUG - IMPORTED_LOCATION_DEBUG "${FMT_LIBRARY_DEBUG}") + INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") endif() - set_target_properties(fmt::fmt PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${FMT_INCLUDE_DIR}") if(TARGET fmt) add_dependencies(fmt::fmt fmt) + # If a force build is done, let any calling packages know they may want to rebuild + if(FORCE_BUILD) + set_target_properties(fmt::fmt PROPERTIES LIB_BUILD ON) + endif() endif() # Add internal build target when a Multi Config Generator is used @@ -139,40 +154,6 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) endif() endif() - # If a force build is done, let any calling packages know they may want to rebuild - if(FORCE_BUILD) - set_target_properties(fmt::fmt PROPERTIES LIB_BUILD ON) - endif() - - # This is for the case where a distro provides a non standard (Debug/Release) config type - # eg Debian's config file is fmtConfigTargets-none.cmake - # convert this back to either DEBUG/RELEASE or just RELEASE - # we only do this because we use find_package_handle_standard_args for config time output - # and it isnt capable of handling TARGETS, so we have to extract the info - get_target_property(_FMT_CONFIGURATIONS fmt::fmt IMPORTED_CONFIGURATIONS) - foreach(_fmt_config IN LISTS _FMT_CONFIGURATIONS) - # Some non standard config (eg None on Debian) - # Just set to RELEASE var so select_library_configurations can continue to work its magic - string(TOUPPER ${_fmt_config} _fmt_config_UPPER) - if((NOT ${_fmt_config_UPPER} STREQUAL "RELEASE") AND - (NOT ${_fmt_config_UPPER} STREQUAL "DEBUG")) - get_target_property(FMT_LIBRARY_RELEASE fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) - else() - get_target_property(FMT_LIBRARY_${_fmt_config_UPPER} fmt::fmt IMPORTED_LOCATION_${_fmt_config_UPPER}) - endif() - endforeach() - - get_target_property(FMT_INCLUDE_DIR fmt::fmt INTERFACE_INCLUDE_DIRECTORIES) - - include(SelectLibraryConfigurations) - select_library_configurations(FMT) - unset(FMT_LIBRARIES) - - include(FindPackageHandleStandardArgs) - find_package_handle_standard_args(Fmt - REQUIRED_VARS FMT_LIBRARY FMT_INCLUDE_DIR - VERSION_VAR FMT_VERSION) - # Check whether we already have fmt::fmt target added to dep property list get_property(CHECK_INTERNAL_DEPS GLOBAL PROPERTY INTERNAL_DEPS_PROP) list(FIND CHECK_INTERNAL_DEPS "fmt::fmt" FMT_PROP_FOUND) @@ -181,5 +162,4 @@ if(NOT TARGET fmt::fmt OR Fmt_FIND_REQUIRED) if(FMT_PROP_FOUND STREQUAL "-1") set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP fmt::fmt) endif() - endif() diff --git a/cmake/scripts/common/ModuleHelpers.cmake b/cmake/scripts/common/ModuleHelpers.cmake index d38e5afe7cdf4..7bd5847974b8b 100644 --- a/cmake/scripts/common/ModuleHelpers.cmake +++ b/cmake/scripts/common/ModuleHelpers.cmake @@ -425,3 +425,10 @@ macro(PATCH_LF_CHECK patch) endif() unset(patch_content_hex) endmacro() + +# Custom property that we can track to allow us to notify to dependency find modules +# that a dependency of that find module is being built, and therefore that higher level +# dependency should also be built regardless of success in lib searches +define_property(TARGET PROPERTY LIB_BUILD + BRIEF_DOCS "This target will be compiling the library" + FULL_DOCS "This target will be compiling the library") From 4acbf36fb0b3a977264fdc0660b955b9d1a89a95 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 15 Mar 2023 16:54:13 +0000 Subject: [PATCH 357/811] [macOS] Drop legacy SDL windowing --- cmake/modules/FindSdl.cmake | 29 - cmake/platform/osx/osx.cmake | 14 +- cmake/treedata/osx/subdirs.txt | 2 - docs/README.macOS.md | 15 - tools/buildsteps/osx-arm64/configure-xbmc | 2 +- tools/depends/Makefile.include.in | 1 - tools/depends/configure.ac | 17 - tools/depends/target/Makefile | 5 - tools/depends/target/cmakebuildsys/Makefile | 10 +- .../target/libsdl/01-SDL_SetWidthHeight.patch | 28 - .../libsdl/02-OSX_interpretKeyEvents.patch | 15 - .../target/libsdl/03-mavericks-compile.patch | 12 - .../libsdl/04-fix_external_screen_crash.patch | 11 - tools/depends/target/libsdl/Makefile | 45 - xbmc/application/Application.cpp | 10 - .../HwDecRender/RendererVTBGL.cpp | 4 - xbmc/platform/darwin/osx/CocoaInterface.mm | 9 - xbmc/platform/darwin/osx/SDL/CMakeLists.txt | 9 - .../darwin/osx/SDL/OSXTextInputResponder.h | 19 - .../darwin/osx/SDL/OSXTextInputResponder.mm | 185 -- xbmc/platform/darwin/osx/SDL/SDLMain.mm | 580 ------ xbmc/platform/posix/main.cpp | 7 - xbmc/windowing/osx/CMakeLists.txt | 19 +- xbmc/windowing/osx/OpenGL/CMakeLists.txt | 15 +- xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h | 6 +- xbmc/windowing/osx/SDL/CMakeLists.txt | 10 - xbmc/windowing/osx/SDL/WinEventsSDL.cpp | 241 --- xbmc/windowing/osx/SDL/WinEventsSDL.h | 24 - xbmc/windowing/osx/SDL/WinSystemOSXSDL.h | 111 - xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm | 1848 ----------------- 30 files changed, 19 insertions(+), 3284 deletions(-) delete mode 100644 cmake/modules/FindSdl.cmake delete mode 100644 tools/depends/target/libsdl/01-SDL_SetWidthHeight.patch delete mode 100644 tools/depends/target/libsdl/02-OSX_interpretKeyEvents.patch delete mode 100644 tools/depends/target/libsdl/03-mavericks-compile.patch delete mode 100644 tools/depends/target/libsdl/04-fix_external_screen_crash.patch delete mode 100644 tools/depends/target/libsdl/Makefile delete mode 100644 xbmc/platform/darwin/osx/SDL/CMakeLists.txt delete mode 100644 xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.h delete mode 100644 xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.mm delete mode 100644 xbmc/platform/darwin/osx/SDL/SDLMain.mm delete mode 100644 xbmc/windowing/osx/SDL/CMakeLists.txt delete mode 100644 xbmc/windowing/osx/SDL/WinEventsSDL.cpp delete mode 100644 xbmc/windowing/osx/SDL/WinEventsSDL.h delete mode 100644 xbmc/windowing/osx/SDL/WinSystemOSXSDL.h delete mode 100644 xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm diff --git a/cmake/modules/FindSdl.cmake b/cmake/modules/FindSdl.cmake deleted file mode 100644 index 60959cb06ca72..0000000000000 --- a/cmake/modules/FindSdl.cmake +++ /dev/null @@ -1,29 +0,0 @@ -#.rst: -# FindSDL -# ------- -# Finds the SDL library -# -# This will define the following variables:: -# -# SDL_FOUND - system has SDL -# SDL_INCLUDE_DIRS - the SDL include directory -# SDL_LIBRARIES - the SDL libraries -# SDL_DEFINITIONS - the SDL compile definitions - -if(PKG_CONFIG_FOUND) - pkg_check_modules(PC_SDL sdl QUIET) -endif() - -find_path(SDL_INCLUDE_DIR SDL/SDL.h PATHS ${PC_SDL_INCLUDE_DIR}) -find_library(SDL_LIBRARY NAMES SDL PATHS ${PC_SDL_LIBDIR}) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Sdl REQUIRED_VARS SDL_LIBRARY SDL_INCLUDE_DIR) - -if(SDL_FOUND) - set(SDL_LIBRARIES ${SDL_LIBRARY}) - set(SDL_INCLUDE_DIRS ${SDL_INCLUDE_DIR}) - set(SDL_DEFINITIONS -DHAVE_SDL=1) -endif() - -mark_as_advanced(SDL_LIBRARY SDL_INCLUDE_DIR) diff --git a/cmake/platform/osx/osx.cmake b/cmake/platform/osx/osx.cmake index 50cea0abb7374..8b581a8c1d5f5 100644 --- a/cmake/platform/osx/osx.cmake +++ b/cmake/platform/osx/osx.cmake @@ -1,3 +1,5 @@ +list(APPEND CORE_MAIN_SOURCE ${CMAKE_SOURCE_DIR}/xbmc/platform/darwin/osx/XBMCApplication.mm) + if(NOT APP_RENDER_SYSTEM OR APP_RENDER_SYSTEM STREQUAL "gl") list(APPEND PLATFORM_REQUIRED_DEPS OpenGl) set(APP_RENDER_SYSTEM gl) @@ -7,17 +9,5 @@ else() message(SEND_ERROR "Currently only OpenGL rendering is supported. Please set APP_RENDER_SYSTEM to \"gl\"") endif() -if(NOT APP_WINDOW_SYSTEM OR APP_WINDOW_SYSTEM STREQUAL sdl) - list(APPEND SYSTEM_DEFINES -DHAS_SDL) - list(APPEND PLATFORM_REQUIRED_DEPS Sdl) - list(APPEND CORE_MAIN_SOURCE ${CMAKE_SOURCE_DIR}/xbmc/platform/darwin/osx/SDL/SDLMain.mm - ${CMAKE_SOURCE_DIR}/xbmc/platform/posix/main.cpp) -elseif(APP_WINDOW_SYSTEM STREQUAL native) - # native windowing and input - list(APPEND CORE_MAIN_SOURCE ${CMAKE_SOURCE_DIR}/xbmc/platform/darwin/osx/XBMCApplication.mm) -else() - message(SEND_ERROR "Only SDL or native windowing options are supported.") -endif() - set(${CORE_PLATFORM_NAME_LC}_SEARCH_CONFIG NO_DEFAULT_PATH CACHE STRING "") list(APPEND PLATFORM_REQUIRED_DEPS Smctemp) diff --git a/cmake/treedata/osx/subdirs.txt b/cmake/treedata/osx/subdirs.txt index b56f268cae80a..f524d22dd377f 100644 --- a/cmake/treedata/osx/subdirs.txt +++ b/cmake/treedata/osx/subdirs.txt @@ -7,7 +7,6 @@ xbmc/platform/darwin/osx platform/osx xbmc/platform/darwin/osx/network platform/darwin/osx/network xbmc/platform/darwin/osx/peripherals platform/osx/peripherals xbmc/platform/darwin/osx/powermanagement platform/darwin/osx/powermanagement -xbmc/platform/darwin/osx/SDL platform/osx/SDL xbmc/platform/darwin/osx/storage platform/osx/storage xbmc/platform/darwin/peripherals platform/darwin/peripherals xbmc/platform/darwin/utils platform/darwin/utils @@ -19,4 +18,3 @@ xbmc/platform/posix/threads platform/posix/threads xbmc/platform/posix/utils platform/posix/utils xbmc/windowing/osx windowing/osx xbmc/windowing/osx/OpenGL windowing/osx/OpenGL -xbmc/windowing/osx/SDL windowing/osx/SDL diff --git a/docs/README.macOS.md b/docs/README.macOS.md index 9dc2522ca8fc4..fc6db1844d1ad 100644 --- a/docs/README.macOS.md +++ b/docs/README.macOS.md @@ -114,11 +114,6 @@ make -j$(getconf _NPROCESSORS_ONLN) ./configure --host=x86_64-apple-darwin --with-platform=macos --with-sdk=10.14 ``` -Developers can also select the legacy SDL windowing/input handling with the following -``` -./configure --host=x86_64-apple-darwin --with-platform=macos --with-windowsystem=sdl -``` - ### 4.1. Advanced Configure Options @@ -186,11 +181,6 @@ Developers can also select the legacy SDL windowing/input handling with the foll **Apple Specific:** -``` ---with-windowsystem= -``` - Windowing system to use (default is native windowing when not provided). arm64 MacOS does not support SDL windowing. - ``` --with-sdk= ``` @@ -241,11 +231,6 @@ Generate Xcode project as per configure command in **[Configure and build tools make -C tools/depends/target/cmakebuildsys BUILD_DIR=$HOME/kodi-build GEN=Xcode ``` -To explicitly select the windowing/input system to use do the following (default is to use native if not provided) -``` -make -C tools/depends/target/cmakebuildsys BUILD_DIR=$HOME/kodi-build GEN=Xcode APP_WINDOW_SYSTEM=sdl -``` - **TIP:** BUILD_DIR can be omitted, and project will be created in $HOME/kodi/build Change all relevant paths onwards if omitted. diff --git a/tools/buildsteps/osx-arm64/configure-xbmc b/tools/buildsteps/osx-arm64/configure-xbmc index 2fed17071c7a1..fb83a37f31b28 100755 --- a/tools/buildsteps/osx-arm64/configure-xbmc +++ b/tools/buildsteps/osx-arm64/configure-xbmc @@ -2,4 +2,4 @@ WORKSPACE=${WORKSPACE:-$( cd $(dirname $0)/../../.. ; pwd -P )} XBMC_PLATFORM_DIR=osx-arm64 . $WORKSPACE/tools/buildsteps/defaultenv -make -C $WORKSPACE/tools/depends/target/cmakebuildsys APP_WINDOW_SYSTEM=native CMAKE_EXTRA_ARGUMENTS="-D CODE_SIGN_IDENTITY='$CODE_SIGN_IDENTITY' -D DEV_ACCOUNT='$DEV_ACCOUNT' -D DEV_ACCOUNT_PASSWORD='$DEV_ACCOUNT_PASSWORD' -D DEV_TEAM='$DEV_TEAM'" +make -C $WORKSPACE/tools/depends/target/cmakebuildsys CMAKE_EXTRA_ARGUMENTS="-D CODE_SIGN_IDENTITY='$CODE_SIGN_IDENTITY' -D DEV_ACCOUNT='$DEV_ACCOUNT' -D DEV_ACCOUNT_PASSWORD='$DEV_ACCOUNT_PASSWORD' -D DEV_TEAM='$DEV_TEAM'" diff --git a/tools/depends/Makefile.include.in b/tools/depends/Makefile.include.in index e91bf152f4da6..1903fecd8564d 100644 --- a/tools/depends/Makefile.include.in +++ b/tools/depends/Makefile.include.in @@ -26,7 +26,6 @@ ARCH_DEFINES=@ARCH_DEFINES@ NATIVE_ARCH_DEFINES=@NATIVE_ARCH_DEFINES@ TARGET_PLATFORM=@target_platform@ RENDER_SYSTEM=@app_rendersystem@ -WINDOW_SYSTEM=@app_winsystem@ SHA512SUM=@SHA512SUM@ SHA256SUM=@SHA256SUM@ SHASUM=@SHASUM@ diff --git a/tools/depends/configure.ac b/tools/depends/configure.ac index add494f478a8d..a6da5ff399fb3 100644 --- a/tools/depends/configure.ac +++ b/tools/depends/configure.ac @@ -102,11 +102,6 @@ AC_ARG_WITH([rendersystem], [render system to use])], [app_rendersystem=$withval]) -AC_ARG_WITH([windowsystem], - [AS_HELP_STRING([--with-windowsystem], - [Windowing system to use])], - [app_winsystem=$withval]) - AC_ARG_WITH([target-cflags], [AS_HELP_STRING([--with-target-cflags], [C compiler flags (target)])], @@ -462,16 +457,6 @@ case $host in fi target_minver="10.14" - - # check provided window system is valid_sdk - # if no window system supplied, default to native. - if test -n "$app_winsystem"; then - if test "$app_winsystem" != "native" && test "$app_winsystem" != "sdl"; then - AC_MSG_ERROR(Window system must be native or sdl) - fi - else - app_winsystem=native - fi ;; aarch64-apple-darwin*) MC_CHECK_NOT_CPU([$use_cpu], "*86") @@ -482,7 +467,6 @@ case $host in ;; osx) target_minver="11.0" - app_winsystem=native ;; *) AC_MSG_ERROR(invalid platform for architecture ($host)) @@ -734,7 +718,6 @@ AC_SUBST(host_includes) AC_SUBST(host_sysroot) AC_SUBST(host_cxxflags) AC_SUBST(app_rendersystem) -AC_SUBST(app_winsystem) AC_SUBST(ffmpeg_options) [ diff --git a/tools/depends/target/Makefile b/tools/depends/target/Makefile index 9c3f8ca488a8c..b2ea61e733727 100644 --- a/tools/depends/target/Makefile +++ b/tools/depends/target/Makefile @@ -68,11 +68,6 @@ endif ifeq ($(OS),osx) EXCLUDED_DEPENDS = libusb DEPENDS += smctemp - ifneq ($(CPU),arm64) - ifeq ($(WINDOW_SYSTEM),sdl) - DEPENDS += libsdl - endif - endif endif ifeq ($(OS),android) diff --git a/tools/depends/target/cmakebuildsys/Makefile b/tools/depends/target/cmakebuildsys/Makefile index 446d25218de09..52941fa014739 100644 --- a/tools/depends/target/cmakebuildsys/Makefile +++ b/tools/depends/target/cmakebuildsys/Makefile @@ -28,21 +28,13 @@ else endif endif -ifeq ($(OS),osx) - ifeq ($(APP_WINDOW_SYSTEM),sdl) - WINDOWSYSTEM=-DAPP_WINDOW_SYSTEM=sdl - else - WINDOWSYSTEM=-DAPP_WINDOW_SYSTEM=native - endif -endif - ifeq ($(BUILD_DIR),) BUILD_DIR=$(CMAKE_SOURCE_DIR)/build endif all: mkdir -p $(BUILD_DIR) - cd $(BUILD_DIR); $(CMAKE) $(CMAKE_BUILD_ARGUMENTS) $(WINDOWSYSTEM) -DENABLE_INTERNAL_CROSSGUID=ON -DENABLE_INTERNAL_FFMPEG=OFF $(CMAKE_EXTRA_ARGUMENTS) $(CMAKE_SOURCE_DIR) + cd $(BUILD_DIR); $(CMAKE) $(CMAKE_BUILD_ARGUMENTS) -DENABLE_INTERNAL_CROSSGUID=ON -DENABLE_INTERNAL_FFMPEG=OFF $(CMAKE_EXTRA_ARGUMENTS) $(CMAKE_SOURCE_DIR) kodi: $(MAKE) -C $(BUILD_DIR) diff --git a/tools/depends/target/libsdl/01-SDL_SetWidthHeight.patch b/tools/depends/target/libsdl/01-SDL_SetWidthHeight.patch deleted file mode 100644 index 1ecb5a397394c..0000000000000 --- a/tools/depends/target/libsdl/01-SDL_SetWidthHeight.patch +++ /dev/null @@ -1,28 +0,0 @@ ---- a/include/SDL_video.h -+++ b/include/SDL_video.h -@@ -323,6 +323,11 @@ - */ - extern DECLSPEC SDL_Rect ** SDLCALL SDL_ListModes(SDL_PixelFormat *format, Uint32 flags); - -+/** -+* Alter the width and height of the current surface to the given sizes. -+*/ -+extern DECLSPEC void SDLCALL SDL_SetWidthHeight(int width, int height); -+ - /** - * Set up a video mode with the specified width, height and bits-per-pixel. - * ---- a/src/video/SDL_video.c -+++ b/src/video/SDL_video.c -@@ -1976,3 +1976,11 @@ - return(0); - } - } -+ -+void SDL_SetWidthHeight(int width, int height) -+{ -+ if (current_video != NULL && current_video->screen != NULL) { -+ current_video->screen->w = width; -+ current_video->screen->h = height; -+ } -+} diff --git a/tools/depends/target/libsdl/02-OSX_interpretKeyEvents.patch b/tools/depends/target/libsdl/02-OSX_interpretKeyEvents.patch deleted file mode 100644 index 0a6ef088b5bb2..0000000000000 --- a/tools/depends/target/libsdl/02-OSX_interpretKeyEvents.patch +++ /dev/null @@ -1,15 +0,0 @@ ---- a/src/video/quartz/SDL_QuartzEvents.m -+++ b/src/video/quartz/SDL_QuartzEvents.m -@@ -345,7 +345,11 @@ - the scancode/keysym. - */ - if (SDL_TranslateUNICODE && state == SDL_PRESSED) { -- [field_edit interpretKeyEvents:[NSArray arrayWithObject:event]]; -+ NSResponder *firstResponder = [[NSApp keyWindow] firstResponder]; -+ if ([NSStringFromClass([firstResponder class]) isEqual:@"OSXTextInputResponder"]) -+ [firstResponder interpretKeyEvents:[NSArray arrayWithObject:event]]; -+ else -+ [field_edit interpretKeyEvents:[NSArray arrayWithObject:event]]; - chars = [ event characters ]; - numChars = [ chars length ]; - if (numChars > 0) diff --git a/tools/depends/target/libsdl/03-mavericks-compile.patch b/tools/depends/target/libsdl/03-mavericks-compile.patch deleted file mode 100644 index 1c00140a7ff9e..0000000000000 --- a/tools/depends/target/libsdl/03-mavericks-compile.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- a/src/video/quartz/SDL_QuartzVideo.h -+++ b/src/video/quartz/SDL_QuartzVideo.h -@@ -91,7 +91,9 @@ - CGDirectDisplayID display; /* 0 == main display (only support single display) */ - const void *mode; /* current mode of the display */ - const void *save_mode; /* original mode of the display */ -+#ifdef CGDirectPaletteRef - CGDirectPaletteRef palette; /* palette of an 8-bit display */ -+#endif - NSOpenGLContext *gl_context; /* OpenGL rendering context */ - NSGraphicsContext *nsgfx_context; /* Cocoa graphics context */ - Uint32 width, height, bpp; /* frequently used data about the display */ diff --git a/tools/depends/target/libsdl/04-fix_external_screen_crash.patch b/tools/depends/target/libsdl/04-fix_external_screen_crash.patch deleted file mode 100644 index 82bb364b7044a..0000000000000 --- a/tools/depends/target/libsdl/04-fix_external_screen_crash.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/src/video/quartz/SDL_QuartzWindow.m -+++ b/src/video/quartz/SDL_QuartzWindow.m -@@ -87,7 +87,7 @@ - SDL_VideoDevice *this = (SDL_VideoDevice*)current_video; - - /* make sure pixels are fully opaque */ -- if (! ( SDL_VideoSurface->flags & SDL_OPENGL ) ) -+ if ( SDL_VideoSurface && ! ( SDL_VideoSurface->flags & SDL_OPENGL ) ) - QZ_SetPortAlphaOpaque (); - - /* save current visible SDL surface */ diff --git a/tools/depends/target/libsdl/Makefile b/tools/depends/target/libsdl/Makefile deleted file mode 100644 index d776b8937035c..0000000000000 --- a/tools/depends/target/libsdl/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -include ../../Makefile.include -DEPS = ../../Makefile.include Makefile 01-SDL_SetWidthHeight.patch 02-OSX_interpretKeyEvents.patch 03-mavericks-compile.patch 04-fix_external_screen_crash.patch ../../download-files.include - -# lib name, version -LIBNAME=SDL -VERSION=1.2.15 -SOURCE=$(LIBNAME)-$(VERSION) -ARCHIVE=$(SOURCE).tar.gz -SHA512=ac392d916e6953b0925a7cbb0f232affea33339ef69b47a0a7898492afb9784b93138986df53d6da6d3e2ad79af1e9482df565ecca30f89428be0ae6851b1adc -include ../../download-files.include - -# configuration settings -CONFIGURE=./configure --prefix=$(PREFIX) --disable-video-directfb -ifneq ($(OS),linux) -CONFIGURE += --without-x --disable-video-x11 -endif - -LIBDYLIB=$(PLATFORM)/build/.libs/lib$(LIBNAME).a - -all: .installed-$(PLATFORM) - - -$(PLATFORM): $(DEPS) | $(TARBALLS_LOCATION)/$(ARCHIVE).$(HASH_TYPE) - rm -rf $(PLATFORM); mkdir -p $(PLATFORM) - cd $(PLATFORM); $(ARCHIVE_TOOL) $(ARCHIVE_TOOL_FLAGS) $(TARBALLS_LOCATION)/$(ARCHIVE) - cd $(PLATFORM); patch -p1 -i ../01-SDL_SetWidthHeight.patch - cd $(PLATFORM); patch -p1 -i ../02-OSX_interpretKeyEvents.patch - cd $(PLATFORM); patch -p1 -i ../03-mavericks-compile.patch - cd $(PLATFORM); patch -p1 -i ../04-fix_external_screen_crash.patch - cd $(PLATFORM); ./autogen.sh - cd $(PLATFORM); $(CONFIGURE) - -$(LIBDYLIB): $(PLATFORM) - $(MAKE) -j 1 -C $(PLATFORM) - -.installed-$(PLATFORM): $(LIBDYLIB) - $(MAKE) -C $(PLATFORM) install - touch $@ - -clean: - $(MAKE) -C $(PLATFORM) clean - rm -f .installed-$(PLATFORM) - -distclean:: - rm -rf $(PLATFORM) .installed-$(PLATFORM) diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index 54c2c8ad7fa41..c0408a965c098 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -3127,16 +3127,6 @@ void CApplication::ProcessSlow() CServiceBroker::GetPowerManager().ProcessEvents(); -#if defined(TARGET_DARWIN_OSX) && defined(SDL_FOUND) - // There is an issue on OS X that several system services ask the cursor to become visible - // during their startup routines. Given that we can't control this, we hack it in by - // forcing the - if (CServiceBroker::GetWinSystem()->IsFullScreen()) - { // SDL thinks it's hidden - Cocoa_HideMouse(); - } -#endif - // Temporarily pause pausable jobs when viewing video/picture int currentWindow = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); if (CurrentFileItem().IsVideo() || diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp index 0069425afbfe6..d567c4e7f0cba 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.cpp @@ -14,11 +14,7 @@ #include "cores/VideoPlayer/DVDCodecs/Video/VTB.h" #include "utils/log.h" #include "windowing/WinSystem.h" -#if defined(HAS_SDL) -#include "windowing/osx/SDL/WinSystemOSXSDL.h" -#else #include "windowing/osx/WinSystemOSX.h" -#endif #include "platform/darwin/osx/CocoaInterface.h" diff --git a/xbmc/platform/darwin/osx/CocoaInterface.mm b/xbmc/platform/darwin/osx/CocoaInterface.mm index a7129482a87f8..a1c52943bf930 100644 --- a/xbmc/platform/darwin/osx/CocoaInterface.mm +++ b/xbmc/platform/darwin/osx/CocoaInterface.mm @@ -12,11 +12,7 @@ #include "ServiceBroker.h" #include "utils/StringUtils.h" #include "utils/log.h" -#if defined(HAS_SDL) -#include "windowing/osx/SDL/WinSystemOSXSDL.h" -#else #include "windowing/osx/WinSystemOSX.h" -#endif #import #import @@ -36,12 +32,7 @@ NSOpenGLContext* Cocoa_GL_GetCurrentContext(void) { -#if defined(HAS_SDL) - CWinSystemOSX *winSystem = dynamic_cast(CServiceBroker::GetWinSystem()); - return winSystem->GetNSOpenGLContext(); -#else return [NSOpenGLContext currentContext]; -#endif } uint32_t Cocoa_GL_GetCurrentDisplayID(void) diff --git a/xbmc/platform/darwin/osx/SDL/CMakeLists.txt b/xbmc/platform/darwin/osx/SDL/CMakeLists.txt deleted file mode 100644 index a9badcf8c6aa9..0000000000000 --- a/xbmc/platform/darwin/osx/SDL/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -if(SDL_FOUND) - set(SOURCES OSXTextInputResponder.mm) - - set(HEADERS OSXTextInputResponder.h) -endif() - -if(SOURCES) - core_add_library(platform_osx_SDL) -endif() diff --git a/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.h b/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.h deleted file mode 100644 index 0448bdbd09cc1..0000000000000 --- a/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.h +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 - -@interface OSXTextInputResponder : NSView -{ - NSString *_markedText; - NSRange _markedRange; - NSRange _selectedRange; - NSRect _inputRect; -} -- (void) setInputRect:(NSRect) rect; -@end diff --git a/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.mm b/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.mm deleted file mode 100644 index 0f87beb96afe6..0000000000000 --- a/xbmc/platform/darwin/osx/SDL/OSXTextInputResponder.mm +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 "OSXTextInputResponder.h" - -#include "GUIUserMessages.h" -#include "ServiceBroker.h" -#include "guilib/GUIWindowManager.h" -#include "input/Key.h" -#include "messaging/ApplicationMessenger.h" -#include "utils/log.h" - -void SendKeyboardText(const char *text) -{ - // CLog::Log(LOGDEBUG, "SendKeyboardText({})", text); - - /* Don't post text events for unprintable characters */ - if ((unsigned char)*text < ' ' || *text == 127) - return; - - CAction *action = new CAction(ACTION_INPUT_TEXT); - action->SetText(text); - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, - static_cast(action)); -} - -void SendEditingText(const char *text, unsigned int location, unsigned int length) -{ - //@todo fix this hack - // CLog::Log(LOGDEBUG, "SendEditingText({}, {}, {})", text, location, length); - // CGUIMessage msg(GUI_MSG_INPUT_TEXT_EDIT, 0, 0, location, length); - // msg.SetLabel(text); - // CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg, CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindowOrDialog()); -} - -@implementation OSXTextInputResponder - -- (id)initWithFrame:(NSRect)frame -{ - self = [super initWithFrame:frame]; - if (self) { - // Initialization code here. - _selectedRange = NSMakeRange(0, 0); - _markedRange = NSMakeRange(NSNotFound, 0); - _inputRect = NSZeroRect; - } - - return self; -} - -- (void) setInputRect:(NSRect) rect -{ - _inputRect = rect; -} - -- (void) insertText:(id) aString replacementRange:(NSRange)replacementRange -{ - const char *str; - - if (replacementRange.location == NSNotFound) { - if (_markedRange.location != NSNotFound) { - replacementRange = _markedRange; - } else { - replacementRange = _selectedRange; - } - } - - /* Could be NSString or NSAttributedString, so we have - * to test and convert it */ - if ([aString isKindOfClass: [NSAttributedString class]]) - str = [[aString string] UTF8String]; - else - str = [aString UTF8String]; - - SendKeyboardText(str); - - [self unmarkText]; -} - -- (void) doCommandBySelector:(SEL) myselector -{ - // No need to do anything since we are not using Cocoa - // selectors to handle special keys, instead we use SDL - // key events to do the same job. -} - -- (BOOL) hasMarkedText -{ - return _markedText != nil; -} - -- (NSRange) markedRange -{ - return _markedRange; -} - -- (NSRange) selectedRange -{ - return _selectedRange; -} - -- (void) setMarkedText:(id) aString - selectedRange:(NSRange) selRange - replacementRange:(NSRange) replacementRange -{ - if (replacementRange.location == NSNotFound) { - if (_markedRange.location != NSNotFound) - replacementRange = _markedRange; - else - replacementRange = _selectedRange; - } - - if ([aString isKindOfClass: [NSAttributedString class]]) - aString = [aString string]; - - if ([aString length] == 0) - { - [self unmarkText]; - return; - } - - _markedText = aString; - _selectedRange = selRange; -// _markedRange = NSMakeRange(0, [aString length]); - _markedRange = NSMakeRange(replacementRange.location, [aString length]); - - SendEditingText([aString UTF8String], selRange.location, selRange.length); -} - -- (void) unmarkText -{ - _markedText = nil; - _markedRange = NSMakeRange(NSNotFound, 0); - - SendEditingText("", 0, 0); -} - -- (NSRect) firstRectForCharacterRange: (NSRange) theRange - actualRange:(NSRangePointer)actualRange -{ - if (actualRange) - *actualRange = theRange; - - NSWindow *window = [self window]; - NSRect contentRect = [window contentRectForFrameRect: [window frame]]; - double windowHeight = contentRect.size.height; - NSRect rect = NSMakeRect(_inputRect.origin.x, windowHeight - _inputRect.origin.y - _inputRect.size.height, - _inputRect.size.width, _inputRect.size.height); - - // CLog::Log(LOGDEBUG, "firstRectForCharacterRange: ({}, {}): windowHeight = {}, rect = {}", - // theRange.location, theRange.length, windowHeight, - // [NSStringFromRect(rect) UTF8String]); - rect.origin = [[self window] convertRectToScreen:rect].origin; - - return rect; -} - -- (NSAttributedString*)attributedSubstringForProposedRange:(NSRange)theRange - actualRange:(NSRangePointer)actualRange -{ - return nil; -} - -// This method returns the index for character that is -// nearest to thePoint. thPoint is in screen coordinate system. -- (NSUInteger) characterIndexForPoint:(NSPoint) thePoint -{ - return 0; -} - -// This method is the key to attribute extension. -// We could add new attributes through this method. -// NSInputServer examines the return value of this -// method & constructs appropriate attributed string. -- (NSArray *) validAttributesForMarkedText -{ - return [NSArray array]; -} - -@end diff --git a/xbmc/platform/darwin/osx/SDL/SDLMain.mm b/xbmc/platform/darwin/osx/SDL/SDLMain.mm deleted file mode 100644 index c57e3de0aadd6..0000000000000 --- a/xbmc/platform/darwin/osx/SDL/SDLMain.mm +++ /dev/null @@ -1,580 +0,0 @@ -/* - * SDLMain.mm - main entry point for our Cocoa-ized SDL app - * Initial Version: Darrell Walisser - * Non-NIB-Code & other changes: Max Horn - * - * SPDX-License-Identifier: Unlicense - * See LICENSES/README.md for more information. - */ - -#import "messaging/ApplicationMessenger.h" - -#import "platform/darwin/osx/CocoaInterface.h" -#import "platform/darwin/osx/HotKeyController.h" -#import "platform/darwin/osx/XBMCApplication.h" -#import "platform/darwin/osx/storage/OSXStorageProvider.h" - -#import -#import /* for MAXPATHLEN */ -#import - -#import "PlatformDefs.h" - -// For some reason, Apple removed setAppleMenu from the headers in 10.4, -// but the method still is there and works. To avoid warnings, we declare -// it ourselves here. -@interface NSApplication(SDL_Missing_Methods) -- (void)setAppleMenu:(NSMenu *)menu; -@end - -// Use this flag to determine whether we use CPS (docking) or not -#define SDL_USE_CPS 1 -#ifdef SDL_USE_CPS -// Portions of CPS.h -typedef struct CPSProcessSerNum -{ - UInt32 lo; - UInt32 hi; -} CPSProcessSerNum; - -extern "C" { -extern OSErr CPSGetCurrentProcess(CPSProcessSerNum *psn); -extern OSErr CPSEnableForegroundOperation(CPSProcessSerNum *psn, UInt32 _arg2, UInt32 _arg3, UInt32 _arg4, UInt32 _arg5); -extern OSErr CPSSetFrontProcess(CPSProcessSerNum *psn); -} -#endif /* SDL_USE_CPS */ - -static int gArgc; -static char **gArgv; -static BOOL gCalledAppMainline = FALSE; - -static NSString *getApplicationName(void) -{ - NSDictionary *dict; - NSString *appName = 0; - - // Determine the application name - dict = (NSDictionary *)CFBundleGetInfoDictionary(CFBundleGetMainBundle()); - if (dict) - appName = [dict objectForKey: @"CFBundleName"]; - - if (![appName length]) - appName = [[NSProcessInfo processInfo] processName]; - - return appName; -} -static void setupApplicationMenu(void) -{ - // warning: this code is very odd - NSMenu *appleMenu; - NSMenuItem *menuItem; - NSString *title; - NSString *appName; - - appName = getApplicationName(); - appleMenu = [[NSMenu alloc] initWithTitle:@""]; - - // Add menu items - title = [@"About " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(orderFrontStandardAboutPanel:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Hide " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(hide:) keyEquivalent:@"h"]; - - menuItem = (NSMenuItem *)[appleMenu addItemWithTitle:@"Hide Others" action:@selector(hideOtherApplications:) keyEquivalent:@"h"]; - [menuItem setKeyEquivalentModifierMask:(NSEventModifierFlagOption|NSEventModifierFlagCommand)]; - - [appleMenu addItemWithTitle:@"Show All" action:@selector(unhideAllApplications:) keyEquivalent:@""]; - - [appleMenu addItem:[NSMenuItem separatorItem]]; - - title = [@"Quit " stringByAppendingString:appName]; - [appleMenu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"]; - - - // Put menu into the menubar - menuItem = [[NSMenuItem alloc] initWithTitle:@"" action:nil keyEquivalent:@""]; - [menuItem setSubmenu:appleMenu]; - [[NSApp mainMenu] addItem:menuItem]; - - // Tell the application object that this is now the application menu - [NSApp setAppleMenu:appleMenu]; -} - -// Create a window menu -static void setupWindowMenu(void) -{ - NSMenu *windowMenu; - NSMenuItem *windowMenuItem; - NSMenuItem *menuItem; - - windowMenu = [[NSMenu alloc] initWithTitle:@"Window"]; - - // "Full/Windowed Toggle" item - menuItem = [[NSMenuItem alloc] initWithTitle:@"Full/Windowed Toggle" action:@selector(fullScreenToggle:) keyEquivalent:@"f"]; - // this is just for display purposes, key handling is in CWinEventsSDL::ProcessOSXShortcuts() - menuItem.keyEquivalentModifierMask = NSEventModifierFlagCommand | NSEventModifierFlagControl; - [windowMenu addItem:menuItem]; - - // "Full/Windowed Toggle" item - menuItem = [[NSMenuItem alloc] initWithTitle:@"Float on Top" action:@selector(floatOnTopToggle:) keyEquivalent:@"t"]; - [windowMenu addItem:menuItem]; - - // "Minimize" item - menuItem = [[NSMenuItem alloc] initWithTitle:@"Minimize" action:@selector(performMiniaturize:) keyEquivalent:@"m"]; - [windowMenu addItem:menuItem]; - - // "Title Bar" item - menuItem = [[NSMenuItem alloc] initWithTitle:@"Title Bar" action:@selector(titlebarToggle:) keyEquivalent:@""]; - [windowMenu addItem:menuItem]; - [menuItem setState: true]; - - // Put menu into the menubar - windowMenuItem = [[NSMenuItem alloc] initWithTitle:@"Window" action:nil keyEquivalent:@""]; - [windowMenuItem setSubmenu:windowMenu]; - [[NSApp mainMenu] addItem:windowMenuItem]; - - // Tell the application object that this is now the window menu - [NSApp setWindowsMenu:windowMenu]; -} - -@interface XBMCApplication : NSApplication -@end - -@implementation XBMCApplication - -// Called before the internal event loop has started running. -- (void) finishLaunching -{ - [super finishLaunching]; -} - -// Invoked from the Quit menu item -- (void)terminate:(id)sender -{ - // remove any notification handlers - [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self]; - [[NSNotificationCenter defaultCenter] removeObserver:self]; - - // Post a SDL_QUIT event - SDL_Event event; - event.type = SDL_QUIT; - SDL_PushEvent(&event); -} - -- (void)fullScreenToggle:(id)sender -{ - // Post an toggle full-screen event to the application thread. - SDL_Event event; - memset(&event, 0, sizeof(event)); - event.type = SDL_USEREVENT; - event.user.code = TMSG_TOGGLEFULLSCREEN; - SDL_PushEvent(&event); -} - -- (void)floatOnTopToggle:(id)sender -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSWindow* window = [[[NSOpenGLContext currentContext] view] window]; -#pragma clang diagnostic pop - if ([window level] == NSFloatingWindowLevel) - { - [window setLevel:NSNormalWindowLevel]; - [sender setState:NSControlStateValueOff]; - } - else - { - [window setLevel:NSFloatingWindowLevel]; - [sender setState:NSControlStateValueOn]; - } -} - -- (void)titlebarToggle:(id)sender -{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - NSWindow* window = [[[NSOpenGLContext currentContext] view] window]; -#pragma clang diagnostic pop - [window setStyleMask: [window styleMask] ^ NSWindowStyleMaskTitled ]; - BOOL isSet = [window styleMask] & NSWindowStyleMaskTitled; - [window setMovableByWindowBackground: !isSet]; - [sender setState: isSet]; - -} - - -@end - -// The main class of the application, the application's delegate -@implementation XBMCDelegate - -// Set the working directory to the .app's parent directory -- (void) setupWorkingDirectory -{ - char parentdir[MAXPATHLEN]; - CFURLRef url = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFURLRef url2 = CFURLCreateCopyDeletingLastPathComponent(0, url); - if (CFURLGetFileSystemRepresentation(url2, true, (UInt8 *)parentdir, MAXPATHLEN)) - { - assert( chdir (parentdir) == 0 ); /* chdir to the binary app's parent */ - } - CFRelease(url); - CFRelease(url2); -} - -// Doesnt currently get called. Keep in case we fix the root cause -- (void) applicationWillTerminate: (NSNotification *) note -{ - // disabled as currently main() explicitly calls this. - // [self appshutdownCleanup]; -} - -- (void) applicationWillResignActive:(NSNotification *) note -{ - //[[HotKeyController sharedController] sysPower:NO]; - //[[HotKeyController sharedController] sysVolume:NO]; - [[HotKeyController sharedController] setActive:NO]; -} - -- (void) applicationWillBecomeActive:(NSNotification *) note -{ - //[[HotKeyController sharedController] sysPower:YES]; - //[[HotKeyController sharedController] sysVolume:YES]; - [[HotKeyController sharedController] setActive:YES]; -} - -// To use Cocoa on secondary POSIX threads, your application must first detach -// at least one NSThread object, which can immediately exit. Some info says this -// is not required anymore, who knows ? -- (void) kickstartMultiThreaded:(id)arg -{ - @autoreleasepool - { - // empty - } -} - -- (void)appshutdownCleanup -{ - [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self - name:NSWorkspaceDidMountNotification - object:nil]; - - [[[NSWorkspace sharedWorkspace] notificationCenter] - removeObserver:self - name:NSWorkspaceDidUnmountNotification - object:nil]; - - NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; - - [center removeObserver:self name:MediaKeyPower object:nil]; - [center removeObserver:self name:MediaKeySoundMute object:nil]; - [center removeObserver:self name:MediaKeySoundUp object:nil]; - [center removeObserver:self name:MediaKeySoundDown object:nil]; - [center removeObserver:self name:MediaKeyPlayPauseNotification object:nil]; - [center removeObserver:self name:MediaKeyFastNotification object:nil]; - [center removeObserver:self name:MediaKeyRewindNotification object:nil]; - [center removeObserver:self name:MediaKeyNextNotification object:nil]; - [center removeObserver:self name:MediaKeyPreviousNotification object:nil]; - - [[HotKeyController sharedController] disableTap]; -} - -// Called after the internal event loop has started running. -- (void) applicationDidFinishLaunching: (NSNotification *) note -{ - // enable multithreading, we should NOT have to do this but as we are mixing NSThreads/pthreads... - if (![NSThread isMultiThreaded]) - [NSThread detachNewThreadSelector:@selector(kickstartMultiThreaded:) toTarget:self withObject:nil]; - - // Set the working directory to the .app's parent directory - [self setupWorkingDirectory]; - - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self - selector:@selector(deviceDidMountNotification:) - name:NSWorkspaceDidMountNotification - object:nil]; - - [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self - selector:@selector(deviceDidUnMountNotification:) - name:NSWorkspaceDidUnmountNotification - object:nil]; - - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - - // create media key handler singleton - [[HotKeyController sharedController] enableTap]; - // add media key notifications - [center addObserver:self - selector:@selector(powerKeyNotification) - name:MediaKeyPower object:nil]; - [center addObserver:self - selector:@selector(muteKeyNotification) - name:MediaKeySoundMute object:nil]; - [center addObserver:self - selector:@selector(soundUpKeyNotification) - name:MediaKeySoundUp object:nil]; - [center addObserver:self - selector:@selector(soundDownKeyNotification) - name:MediaKeySoundDown object:nil]; - [center addObserver:self - selector:@selector(playPauseKeyNotification) - name:MediaKeyPlayPauseNotification object:nil]; - [center addObserver:self - selector:@selector(fastKeyNotification) - name:MediaKeyFastNotification object:nil]; - [center addObserver:self - selector:@selector(rewindKeyNotification) - name:MediaKeyRewindNotification object:nil]; - [center addObserver:self - selector:@selector(nextKeyNotification) - name:MediaKeyNextNotification object:nil]; - [center addObserver:self - selector:@selector(previousKeyNotification) - name:MediaKeyPreviousNotification object:nil]; - - // We're going to manually manage the screensaver. - setenv("SDL_VIDEO_ALLOW_SCREENSAVER", "1", true); - - // Hand off to main application code - gCalledAppMainline = TRUE; - - // stop the main loop so we return to main (below) and can - // call SDL_main there. - [NSApp stop:nil]; - - //post a NOP event, so the run loop actually stops - //see http://www.cocoabuilder.com/archive/cocoa/219842-nsapp-stop.html - NSEvent* event = [NSEvent otherEventWithType: NSEventTypeApplicationDefined - location: NSMakePoint(0,0) - modifierFlags: 0 - timestamp: 0.0 - windowNumber: 0 - context: nil - subtype: 0 - data1: 0 - data2: 0]; - // - [NSApp postEvent: event atStart: true]; -} - -/* - * Catch document open requests...this lets us notice files when the app - * was launched by double-clicking a document, or when a document was - * dragged/dropped on the app's icon. You need to have a - * CFBundleDocumentsType section in your Info.plist to get this message, - * apparently. - * - * Files are added to gArgv, so to the app, they'll look like command line - * arguments. Previously, apps launched from the finder had nothing but - * an argv[0]. - * - * This message may be received multiple times to open several docs on launch. - * - * This message is ignored once the app's mainline has been called. - */ -- (BOOL)application:(NSApplication *)theApplication openFile:(NSString *)filename -{ - const char *temparg; - size_t arglen; - char *arg; - char **newargv; - - // app has started, ignore this document. - if (gCalledAppMainline) - return FALSE; - - temparg = [filename UTF8String]; - arglen = SDL_strlen(temparg) + 1; - arg = (char *) SDL_malloc(arglen); - if (arg == NULL) - return FALSE; - - newargv = (char **) realloc(gArgv, sizeof (char *) * (gArgc + 2)); - if (newargv == NULL) - { - SDL_free(arg); - return FALSE; - } - gArgv = newargv; - - SDL_strlcpy(arg, temparg, arglen); - gArgv[gArgc++] = arg; - gArgv[gArgc] = NULL; - - return TRUE; -} - -- (void) deviceDidMountNotification:(NSNotification *) note -{ - // calling into c++ code, need to use autorelease pools - @autoreleasepool - { - NSString* volumeLabel = [note.userInfo objectForKey:@"NSWorkspaceVolumeLocalizedNameKey"]; - const char* label = [volumeLabel UTF8String]; - - NSString* volumePath = [note.userInfo objectForKey:@"NSDevicePath"]; - const char* path = [volumePath UTF8String]; - - COSXStorageProvider::VolumeMountNotification(label, path); - } -} - -- (void) deviceDidUnMountNotification:(NSNotification *) note -{ - // calling into c++ code, need to use autorelease pools - @autoreleasepool - { - NSString* volumeLabel = [note.userInfo objectForKey:@"NSWorkspaceVolumeLocalizedNameKey"]; - const char* label = [volumeLabel UTF8String]; - - NSString* volumePath = [note.userInfo objectForKey:@"NSDevicePath"]; - const char* path = [volumePath UTF8String]; - - COSXStorageProvider::VolumeUnmountNotification(label, path); - } -} - -static void keyPress(SDLKey key) -{ - SDL_Event event; - memset(&event, 0, sizeof(event)); - event.type = SDL_KEYDOWN; - event.key.keysym.sym = key; - SDL_PushEvent(&event); - event.type = SDL_KEYUP; - SDL_PushEvent(&event); -} - -#define VK_SLEEP 0x143 -#define VK_VOLUME_MUTE 0xAD -#define VK_VOLUME_DOWN 0xAE -#define VK_VOLUME_UP 0xAF -#define VK_MEDIA_NEXT_TRACK 0x9E -#define VK_MEDIA_PREV_TRACK 0x9D -#define VK_MEDIA_STOP 0xB2 -#define VK_MEDIA_PLAY_PAUSE 0xB3 -#define VK_REWIND 0xB1 -#define VK_FAST_FWD 0xB0 - -- (void)powerKeyNotification -{ - keyPress((SDLKey)VK_SLEEP); -} - -- (void)muteKeyNotification -{ - keyPress((SDLKey)VK_VOLUME_MUTE); -} -- (void)soundUpKeyNotification -{ - keyPress((SDLKey)VK_VOLUME_UP); -} -- (void)soundDownKeyNotification -{ - keyPress((SDLKey)VK_VOLUME_DOWN); -} - -- (void)playPauseKeyNotification -{ - keyPress((SDLKey)VK_MEDIA_PLAY_PAUSE); -} - -- (void)fastKeyNotification -{ - keyPress((SDLKey)VK_FAST_FWD); -} - -- (void)rewindKeyNotification -{ - keyPress((SDLKey)VK_REWIND); -} - -- (void)nextKeyNotification -{ - keyPress((SDLKey)VK_MEDIA_NEXT_TRACK); -} - -- (void)previousKeyNotification -{ - keyPress((SDLKey)VK_MEDIA_PREV_TRACK); -} - -@end - -#ifdef main -# undef main -#endif -/* Main entry point to executable - should *not* be SDL_main! */ -int main(int argc, char *argv[]) -{ - @autoreleasepool - { - XBMCDelegate* xbmc_delegate; - - // Block SIGPIPE - // SIGPIPE repeatably kills us, turn it off - { - sigset_t set; - sigemptyset(&set); - sigaddset(&set, SIGPIPE); - sigprocmask(SIG_BLOCK, &set, NULL); - } - - /* Copy the arguments into a global variable */ - /* This is passed if we are launched by double-clicking */ - if (argc >= 2 && strncmp(argv[1], "-psn", 4) == 0) - { - gArgv = (char**)SDL_malloc(sizeof(char*) * 2); - gArgv[0] = argv[0]; - gArgv[1] = NULL; - gArgc = 1; - } - else - { - gArgc = argc; - gArgv = (char**)SDL_malloc(sizeof(char*) * (argc + 1)); - for (int i = 0; i <= argc; i++) - gArgv[i] = argv[i]; - } - - // Ensure the application object is initialised - [XBMCApplication sharedApplication]; - -#ifdef SDL_USE_CPS - { - CPSProcessSerNum PSN; - /* Tell the dock about us */ - if (!CPSGetCurrentProcess(&PSN)) - if (!CPSEnableForegroundOperation(&PSN, 0x03, 0x3C, 0x2C, 0x1103)) - if (!CPSSetFrontProcess(&PSN)) - [XBMCApplication sharedApplication]; - } -#endif - - // Set up the menubars - [NSApp setMainMenu:[[NSMenu alloc] init]]; - setupApplicationMenu(); - setupWindowMenu(); - - // Create XBMCDelegate and make it the app delegate - xbmc_delegate = [[XBMCDelegate alloc] init]; - [[NSApplication sharedApplication] setDelegate:xbmc_delegate]; - - // Start the main event loop - [NSApp run]; - - // call SDL_main which calls our real main in xbmc.cpp - // see http://lists.libsdl.org/pipermail/sdl-libsdl.org/2008-September/066542.html - int status; - status = SDL_main(gArgc, gArgv); - SDL_Quit(); - - [xbmc_delegate appshutdownCleanup]; - - return status; - } -} diff --git a/xbmc/platform/posix/main.cpp b/xbmc/platform/posix/main.cpp index 969a8f9dc1924..acac58699e9ed 100644 --- a/xbmc/platform/posix/main.cpp +++ b/xbmc/platform/posix/main.cpp @@ -6,13 +6,6 @@ * See LICENSES/README.md for more information. */ -#if defined(TARGET_DARWIN_OSX) -// SDL redefines main as SDL_main -#ifdef HAS_SDL -#include -#endif -#endif - #include "PlatformPosix.h" #include "application/AppEnvironment.h" #include "application/AppParamParser.h" diff --git a/xbmc/windowing/osx/CMakeLists.txt b/xbmc/windowing/osx/CMakeLists.txt index 433f5cfe14a0e..da0024f182289 100644 --- a/xbmc/windowing/osx/CMakeLists.txt +++ b/xbmc/windowing/osx/CMakeLists.txt @@ -1,17 +1,14 @@ set(SOURCES CocoaDPMSSupport.cpp OSScreenSaverOSX.cpp - VideoSyncOsx.mm) + VideoSyncOsx.mm + WinEventsOSX.mm + WinEventsOSXImpl.mm + WinSystemOSX.mm) set(HEADERS CocoaDPMSSupport.h OSScreenSaverOSX.h - VideoSyncOsx.h) - -if(NOT SDL_FOUND) - list(APPEND SOURCES WinEventsOSX.mm - WinEventsOSXImpl.mm - WinSystemOSX.mm) - list(APPEND HEADERS WinEventsOSX.h - WinEventsOSXImpl.h - WinSystemOSX.h) -endif() + WinEventsOSX.h + WinEventsOSXImpl.h + VideoSyncOsx.h + WinSystemOSX.h) core_add_library(windowing_osx) diff --git a/xbmc/windowing/osx/OpenGL/CMakeLists.txt b/xbmc/windowing/osx/OpenGL/CMakeLists.txt index c0136fcd97000..61dc24867f114 100644 --- a/xbmc/windowing/osx/OpenGL/CMakeLists.txt +++ b/xbmc/windowing/osx/OpenGL/CMakeLists.txt @@ -1,13 +1,10 @@ if(TARGET OpenGL::GL) - set(SOURCES WinSystemOSXGL.mm) - set(HEADERS WinSystemOSXGL.h) - - if(NOT SDL_FOUND) - list(APPEND SOURCES OSXGLView.mm - WindowControllerMacOS.mm) - list(APPEND HEADERS OSXGLView.h - WindowControllerMacOS.h) - endif() + list(APPEND SOURCES OSXGLView.mm + WindowControllerMacOS.mm + WinSystemOSXGL.mm) + list(APPEND HEADERS OSXGLView.h + WindowControllerMacOS.h + WinSystemOSXGL.h) core_add_library(windowing_osx_opengl) diff --git a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h index c38e5e3491bcc..aa6f0811a7a39 100644 --- a/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h +++ b/xbmc/windowing/osx/OpenGL/WinSystemOSXGL.h @@ -8,12 +8,8 @@ #pragma once -#if defined(HAS_SDL) -#include "windowing/osx/SDL/WinSystemOSXSDL.h" -#else -#include "windowing/osx/WinSystemOSX.h" -#endif #include "rendering/gl/RenderSystemGL.h" +#include "windowing/osx/WinSystemOSX.h" class CWinSystemOSXGL : public CWinSystemOSX, public CRenderSystemGL { diff --git a/xbmc/windowing/osx/SDL/CMakeLists.txt b/xbmc/windowing/osx/SDL/CMakeLists.txt deleted file mode 100644 index 33e25d8432f66..0000000000000 --- a/xbmc/windowing/osx/SDL/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -if(SDL_FOUND) - set(SOURCES WinEventsSDL.cpp - WinSystemOSXSDL.mm) - set(HEADERS WinEventsSDL.h - WinSystemOSXSDL.h) -endif() - -if(SOURCES) - core_add_library(windowing_osx_SDL) -endif() diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.cpp b/xbmc/windowing/osx/SDL/WinEventsSDL.cpp deleted file mode 100644 index 229f176faca27..0000000000000 --- a/xbmc/windowing/osx/SDL/WinEventsSDL.cpp +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 "WinEventsSDL.h" - -#include "GUIUserMessages.h" -#include "ServiceBroker.h" -#include "application/AppInboundProtocol.h" -#include "application/Application.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" -#include "input/InputManager.h" -#include "input/Key.h" -#include "input/mouse/MouseStat.h" -#include "messaging/ApplicationMessenger.h" -#include "settings/DisplaySettings.h" -#include "windowing/WinSystem.h" - -#include "platform/darwin/osx/CocoaInterface.h" - -bool CWinEventsOSX::MessagePump() -{ - SDL_Event event; - bool ret = false; - - while (SDL_PollEvent(&event)) - { - switch(event.type) - { - case SDL_QUIT: - if (!g_application.m_bStop) - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); - break; - - case SDL_ACTIVEEVENT: - //If the window was inconified or restored - if( event.active.state & SDL_APPACTIVE ) - { - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - appPort->SetRenderGUI(event.active.gain != 0); - CServiceBroker::GetWinSystem()->NotifyAppActiveChange(g_application.GetRenderGUI()); - } - else if (event.active.state & SDL_APPINPUTFOCUS) - { - g_application.m_AppFocused = event.active.gain != 0; - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort && g_application.m_AppFocused) - appPort->SetRenderGUI(g_application.m_AppFocused); - CServiceBroker::GetWinSystem()->NotifyAppFocusChange(g_application.m_AppFocused); - } - break; - - case SDL_KEYDOWN: - { - // process any platform specific shortcuts before handing off to XBMC - if (ProcessOSXShortcuts(event)) - { - ret = true; - break; - } - - XBMC_Event newEvent = {}; - newEvent.type = XBMC_KEYDOWN; - newEvent.key.keysym.scancode = event.key.keysym.scancode; - newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym; - newEvent.key.keysym.unicode = event.key.keysym.unicode; - - // Check if the Windows keys are down because SDL doesn't flag this. - uint16_t mod = event.key.keysym.mod; - uint8_t* keystate = SDL_GetKeyState(NULL); - if (keystate[SDLK_LSUPER] || keystate[SDLK_RSUPER]) - mod |= XBMCKMOD_LSUPER; - newEvent.key.keysym.mod = (XBMCMod) mod; - - // don't handle any more messages in the queue until we've handled keydown, - // if a keyup is in the queue it will reset the keypress before it is handled. - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - - case SDL_KEYUP: - { - XBMC_Event newEvent = {}; - newEvent.type = XBMC_KEYUP; - newEvent.key.keysym.scancode = event.key.keysym.scancode; - newEvent.key.keysym.sym = (XBMCKey) event.key.keysym.sym; - newEvent.key.keysym.mod =(XBMCMod) event.key.keysym.mod; - newEvent.key.keysym.unicode = event.key.keysym.unicode; - - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - - case SDL_MOUSEBUTTONDOWN: - { - XBMC_Event newEvent = {}; - newEvent.type = XBMC_MOUSEBUTTONDOWN; - newEvent.button.button = event.button.button; - newEvent.button.x = event.button.x; - newEvent.button.y = event.button.y; - - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - - case SDL_MOUSEBUTTONUP: - { - XBMC_Event newEvent = {}; - newEvent.type = XBMC_MOUSEBUTTONUP; - newEvent.button.button = event.button.button; - newEvent.button.x = event.button.x; - newEvent.button.y = event.button.y; - - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - - case SDL_MOUSEMOTION: - { - if (0 == (SDL_GetAppState() & SDL_APPMOUSEFOCUS)) - { - CServiceBroker::GetInputManager().SetMouseActive(false); - // See CApplication::ProcessSlow() for a description as to why we call Cocoa_HideMouse. - // this is here to restore the pointer when toggling back to window mode from fullscreen. - Cocoa_ShowMouse(); - break; - } - XBMC_Event newEvent = {}; - newEvent.type = XBMC_MOUSEMOTION; - newEvent.motion.x = event.motion.x; - newEvent.motion.y = event.motion.y; - - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - case SDL_VIDEORESIZE: - { - // Under newer osx versions sdl is so fucked up that it even fires resize events - // that exceed the screen size (maybe some HiDP incompatibility in old SDL?) - // ensure to ignore those events because it will mess with windowed size - if((event.resize.w > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iWidth) || - (event.resize.h > CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP).iHeight)) - { - break; - } - XBMC_Event newEvent = {}; - newEvent.type = XBMC_VIDEORESIZE; - newEvent.resize.w = event.resize.w; - newEvent.resize.h = event.resize.h; - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); - break; - } - case SDL_USEREVENT: - { - XBMC_Event newEvent = {}; - newEvent.type = XBMC_USEREVENT; - newEvent.user.code = event.user.code; - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - ret |= appPort->OnEvent(newEvent); - break; - } - case SDL_VIDEOEXPOSE: - CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(); - break; - } - memset(&event, 0, sizeof(SDL_Event)); - } - - return ret; -} - -bool CWinEventsOSX::ProcessOSXShortcuts(SDL_Event& event) -{ - static bool cmd = false; - - cmd = !!(SDL_GetModState() & (KMOD_LMETA | KMOD_RMETA )); - - if (cmd && event.key.type == SDL_KEYDOWN) - { - char keysymbol = event.key.keysym.sym; - - // if the unicode is in the ascii range - // use this instead for getting the real - // character based on the used keyboard layout - // see http://lists.libsdl.org/pipermail/sdl-libsdl.org/2004-May/043716.html - bool isControl = (event.key.keysym.mod & KMOD_CTRL) != 0; - if (!isControl && !(event.key.keysym.unicode & 0xff80)) - keysymbol = event.key.keysym.unicode; - - switch(keysymbol) - { - case SDLK_q: // CMD-q to quit - if (!g_application.m_bStop) - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_QUIT); - return true; - - case SDLK_f: // CMD-Ctrl-f to toggle fullscreen - if (!isControl) - return false; - g_application.OnAction(CAction(ACTION_TOGGLE_FULLSCREEN)); - return true; - - case SDLK_s: // CMD-3 to take a screenshot - g_application.OnAction(CAction(ACTION_TAKE_SCREENSHOT)); - return true; - - case SDLK_h: // CMD-h to hide - CServiceBroker::GetWinSystem()->Hide(); - return true; - - case SDLK_m: // CMD-m to minimize - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MINIMIZE); - return true; - - default: - return false; - } - } - - return false; -} diff --git a/xbmc/windowing/osx/SDL/WinEventsSDL.h b/xbmc/windowing/osx/SDL/WinEventsSDL.h deleted file mode 100644 index 06e6325b5056c..0000000000000 --- a/xbmc/windowing/osx/SDL/WinEventsSDL.h +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * This file is part of Kodi - https://kodi.tv - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSES/README.md for more information. - */ - -#pragma once - -#include "windowing/WinEvents.h" - -#include - -class CWinEventsOSX : public IWinEvents -{ -public: - CWinEventsOSX() = default; - ~CWinEventsOSX() override = default; - bool MessagePump() override; - -private: - static bool ProcessOSXShortcuts(SDL_Event& event); -}; diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h deleted file mode 100644 index 2078d0fd910c7..0000000000000 --- a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * This file is part of Kodi - https://kodi.tv - * - * SPDX-License-Identifier: GPL-2.0-or-later - * See LICENSES/README.md for more information. - */ - -#pragma once - -#include "threads/CriticalSection.h" -#include "threads/SystemClock.h" -#include "threads/Timer.h" -#include "windowing/WinSystem.h" - -#include -#include -#include - -typedef struct SDL_Surface SDL_Surface; - -class IDispResource; -class CWinEventsOSX; -class CWinSystemOSXImpl; -#ifdef __OBJC__ -@class NSOpenGLContext; -#endif - -class CWinSystemOSX : public CWinSystemBase, public ITimerCallback -{ -public: - - CWinSystemOSX(); - ~CWinSystemOSX() override; - - // ITimerCallback interface - void OnTimeout() override; - - // CWinSystemBase - bool InitWindowSystem() override; - bool DestroyWindowSystem() override; - bool CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) override; - bool DestroyWindow() override; - bool ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) override; - bool SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) override; - void UpdateResolutions() override; - void NotifyAppFocusChange(bool bGaining) override; - void ShowOSMouse(bool show) override; - bool Minimize() override; - bool Restore() override; - bool Hide() override; - bool Show(bool raise = true) override; - void OnMove(int x, int y) override; - - std::string GetClipboardText() override; - - void Register(IDispResource *resource) override; - void Unregister(IDispResource *resource) override; - - std::unique_ptr GetVideoSync(CVideoReferenceClock* clock) override; - - std::vector GetConnectedOutputs() override; - - void WindowChangedScreen(); - - void AnnounceOnLostDevice(); - void AnnounceOnResetDevice(); - void HandleOnResetDevice(); - void StartLostDeviceTimer(); - void StopLostDeviceTimer(); - - void* GetCGLContextObj(); -#ifdef __OBJC__ - NSOpenGLContext* GetNSOpenGLContext(); -#else - void* GetNSOpenGLContext(); -#endif - - // winevents override - bool MessagePump() override; - -protected: - std::unique_ptr GetOSScreenSaverImpl() override; - - void HandlePossibleRefreshrateChange(); - void GetScreenResolution(int* w, int* h, double* fps, int screenIdx); - void EnableVSync(bool enable); - bool SwitchToVideoMode(int width, int height, double refreshrate); - void FillInVideoModes(); - bool FlushBuffer(void); - bool IsObscured(void); - void StartTextInput(); - void StopTextInput(); - - std::unique_ptr m_impl; - SDL_Surface* m_SDLSurface; - CWinEventsOSX *m_osx_events; - bool m_obscured; - std::chrono::time_point m_obscured_timecheck; - - bool m_movedToOtherScreen; - int m_lastDisplayNr; - double m_refreshRate; - - CCriticalSection m_resourceSection; - std::vector m_resources; - CTimer m_lostDeviceTimer; - bool m_delayDispReset; - XbmcThreads::EndTime<> m_dispResetTimer; - int m_updateGLContext = 0; -}; diff --git a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm b/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm deleted file mode 100644 index 4886d3426582a..0000000000000 --- a/xbmc/windowing/osx/SDL/WinSystemOSXSDL.mm +++ /dev/null @@ -1,1848 +0,0 @@ -/* - * Copyright (C) 2005-2018 Team Kodi - * 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 "WinSystemOSXSDL.h" - -#include "CompileInfo.h" -#include "ServiceBroker.h" -#include "application/AppInboundProtocol.h" -#include "cores/AudioEngine/AESinkFactory.h" -#include "cores/AudioEngine/Sinks/AESinkDARWINOSX.h" -#include "cores/RetroPlayer/process/osx/RPProcessInfoOSX.h" -#include "cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h" -#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" -#include "cores/VideoPlayer/DVDCodecs/Video/VTB.h" -#include "cores/VideoPlayer/Process/osx/ProcessInfoOSX.h" -#include "cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVTBGL.h" -#include "cores/VideoPlayer/VideoRenderers/LinuxRendererGL.h" -#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" -#include "guilib/DispResource.h" -#include "guilib/GUIWindowManager.h" -#include "input/KeyboardStat.h" -#include "messaging/ApplicationMessenger.h" -#include "rendering/gl/ScreenshotSurfaceGL.h" -#include "settings/DisplaySettings.h" -#include "settings/Settings.h" -#include "settings/SettingsComponent.h" -#include "utils/StringUtils.h" -#include "utils/SystemInfo.h" -#include "utils/log.h" -#include "windowing/osx/CocoaDPMSSupport.h" -#include "windowing/osx/OSScreenSaverOSX.h" -#include "windowing/osx/SDL/WinEventsSDL.h" -#include "windowing/osx/VideoSyncOsx.h" - -#include "platform/darwin/DarwinUtils.h" -#include "platform/darwin/DictionaryUtils.h" -#include "platform/darwin/osx/CocoaInterface.h" -#import "platform/darwin/osx/SDL/OSXTextInputResponder.h" -#include "platform/darwin/osx/XBMCHelper.h" - -#include -#include -#include - -#import -#import -#import -#import -#import - -// turn off deprecated warning spew. -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" - -using namespace KODI; -using namespace WINDOWING; -using namespace std::chrono_literals; - -//------------------------------------------------------------------------------------------ -// special object-c class for handling the NSWindowDidMoveNotification callback. -@interface windowDidMoveNoteClass : NSObject -{ - void *m_userdata; -} -+ (windowDidMoveNoteClass*) initWith: (void*) userdata; -- (void) windowDidMoveNotification:(NSNotification*) note; -@end - -@implementation windowDidMoveNoteClass -+ (windowDidMoveNoteClass*) initWith: (void*) userdata -{ - windowDidMoveNoteClass *windowDidMove = [windowDidMoveNoteClass new]; - windowDidMove->m_userdata = userdata; - return windowDidMove; -} -- (void) windowDidMoveNotification:(NSNotification*) note -{ - CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata; - if (!winsys) - return; - - NSOpenGLContext* context = [NSOpenGLContext currentContext]; - if (context) - { - if ([context view]) - { - NSPoint window_origin = [[[context view] window] frame].origin; - XBMC_Event newEvent = {}; - newEvent.type = XBMC_VIDEOMOVE; - newEvent.move.x = window_origin.x; - newEvent.move.y = window_origin.y; - std::shared_ptr appPort = CServiceBroker::GetAppPort(); - if (appPort) - appPort->OnEvent(newEvent); - } - } -} -@end -//------------------------------------------------------------------------------------------ -// special object-c class for handling the NSWindowDidReSizeNotification callback. -@interface windowDidReSizeNoteClass : NSObject -{ - void *m_userdata; -} -+ (windowDidReSizeNoteClass*) initWith: (void*) userdata; -- (void) windowDidReSizeNotification:(NSNotification*) note; -@end -@implementation windowDidReSizeNoteClass -+ (windowDidReSizeNoteClass*) initWith: (void*) userdata -{ - windowDidReSizeNoteClass *windowDidReSize = [windowDidReSizeNoteClass new]; - windowDidReSize->m_userdata = userdata; - return windowDidReSize; -} -- (void) windowDidReSizeNotification:(NSNotification*) note -{ - CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata; - if (!winsys) - return; - -} -@end - -//------------------------------------------------------------------------------------------ -// special object-c class for handling the NSWindowDidChangeScreenNotification callback. -@interface windowDidChangeScreenNoteClass : NSObject -{ - void *m_userdata; -} -+ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata; -- (void) windowDidChangeScreenNotification:(NSNotification*) note; -@end -@implementation windowDidChangeScreenNoteClass -+ (windowDidChangeScreenNoteClass*) initWith: (void*) userdata -{ - windowDidChangeScreenNoteClass *windowDidChangeScreen = [windowDidChangeScreenNoteClass new]; - windowDidChangeScreen->m_userdata = userdata; - return windowDidChangeScreen; -} -- (void) windowDidChangeScreenNotification:(NSNotification*) note -{ - CWinSystemOSX *winsys = (CWinSystemOSX*)m_userdata; - if (!winsys) - return; - winsys->WindowChangedScreen(); -} -@end -//------------------------------------------------------------------------------------------ - -class CWinSystemOSXImpl -{ -public: - NSOpenGLContext* m_glContext; - static NSOpenGLContext* m_lastOwnedContext; - - windowDidMoveNoteClass* m_windowDidMove; - windowDidReSizeNoteClass* m_windowDidReSize; - windowDidChangeScreenNoteClass* m_windowChangedScreen; -}; - -NSOpenGLContext* CWinSystemOSXImpl::m_lastOwnedContext = nil; - - -#define MAX_DISPLAYS 32 -// if there was a devicelost callback -// but no device reset for 3 secs -// a timeout fires the reset callback -// (for ensuring that e.x. AE isn't stuck) -constexpr auto LOST_DEVICE_TIMEOUT_MS = 3000ms; -static NSWindow* blankingWindows[MAX_DISPLAYS]; - -//------------------------------------------------------------------------------------------ -CRect CGRectToCRect(CGRect cgrect) -{ - CRect crect = CRect( - cgrect.origin.x, - cgrect.origin.y, - cgrect.origin.x + cgrect.size.width, - cgrect.origin.y + cgrect.size.height); - return crect; -} -//--------------------------------------------------------------------------------- -void SetMenuBarVisible(bool visible) -{ - if(visible) - { - [[NSApplication sharedApplication] - setPresentationOptions: NSApplicationPresentationDefault]; - } - else - { - [[NSApplication sharedApplication] - setPresentationOptions: NSApplicationPresentationHideMenuBar | - NSApplicationPresentationHideDock]; - } -} -//--------------------------------------------------------------------------------- -CGDirectDisplayID GetDisplayID(int screen_index) -{ - CGDirectDisplayID displayArray[MAX_DISPLAYS]; - CGDisplayCount numDisplays; - - // Get the list of displays. - CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays); - if (screen_index >= 0 && screen_index < static_cast(numDisplays)) - return(displayArray[screen_index]); - else - return(displayArray[0]); -} - -size_t DisplayBitsPerPixelForMode(CGDisplayModeRef mode) -{ - size_t bitsPerPixel = 0; - - CFStringRef pixEnc = CGDisplayModeCopyPixelEncoding(mode); - if(CFStringCompare(pixEnc, CFSTR(IO32BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) - { - bitsPerPixel = 32; - } - else if(CFStringCompare(pixEnc, CFSTR(IO16BitDirectPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) - { - bitsPerPixel = 16; - } - else if(CFStringCompare(pixEnc, CFSTR(IO8BitIndexedPixels), kCFCompareCaseInsensitive) == kCFCompareEqualTo) - { - bitsPerPixel = 8; - } - - CFRelease(pixEnc); - - return bitsPerPixel; -} - -CFArrayRef GetAllDisplayModes(CGDirectDisplayID display) -{ - int value = 1; - - CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &value); - if (!number) - { - CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Number!"); - return NULL; - } - - CFStringRef key = kCGDisplayShowDuplicateLowResolutionModes; - CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&key, (const void **)&number, 1, NULL, NULL); - CFRelease(number); - - if (!options) - { - CLog::Log(LOGERROR, "GetAllDisplayModes - could not create Dictionary!"); - return NULL; - } - - CFArrayRef displayModes = CGDisplayCopyAllDisplayModes(display, options); - CFRelease(options); - - if (!displayModes) - { - CLog::Log(LOGERROR, "GetAllDisplayModes - no displaymodes found!"); - return NULL; - } - - return displayModes; -} - -// mimic former behavior of deprecated CGDisplayBestModeForParameters -CGDisplayModeRef BestMatchForMode(CGDirectDisplayID display, size_t bitsPerPixel, size_t width, size_t height, boolean_t &match) -{ - - // Get a copy of the current display mode - CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode(display); - - // Loop through all display modes to determine the closest match. - // CGDisplayBestModeForParameters is deprecated on 10.6 so we will emulate it's behavior - // Try to find a mode with the requested depth and equal or greater dimensions first. - // If no match is found, try to find a mode with greater depth and same or greater dimensions. - // If still no match is found, just use the current mode. - CFArrayRef allModes = GetAllDisplayModes(display); - - for(int i = 0; i < CFArrayGetCount(allModes); i++) { - CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); - - if(DisplayBitsPerPixelForMode(mode) != bitsPerPixel) - continue; - - if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height)) - { - CGDisplayModeRelease(displayMode); // release the copy we got before ... - displayMode = mode; - match = true; - break; - } - } - - // No depth match was found - if(!match) - { - for(int i = 0; i < CFArrayGetCount(allModes); i++) - { - CGDisplayModeRef mode = (CGDisplayModeRef)CFArrayGetValueAtIndex(allModes, i); - if(DisplayBitsPerPixelForMode(mode) >= bitsPerPixel) - continue; - - if((CGDisplayModeGetWidth(mode) == width) && (CGDisplayModeGetHeight(mode) == height)) - { - displayMode = mode; - match = true; - break; - } - } - } - - CFRelease(allModes); - - return displayMode; -} - -CGDirectDisplayID GetDisplayIDFromScreen(NSScreen *screen) -{ - NSDictionary* screenInfo = [screen deviceDescription]; - NSNumber* screenID = [screenInfo objectForKey:@"NSScreenNumber"]; - - return (CGDirectDisplayID)[screenID longValue]; -} - -int GetDisplayIndex(CGDirectDisplayID display) -{ - CGDirectDisplayID displayArray[MAX_DISPLAYS]; - CGDisplayCount numDisplays; - - // Get the list of displays. - CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays); - while (numDisplays > 0) - { - if (display == displayArray[--numDisplays]) - return numDisplays; - } - return -1; -} - -void BlankOtherDisplays(int screen_index) -{ - int i; - int numDisplays = [[NSScreen screens] count]; - - // zero out blankingWindows for debugging - for (i=0; i 0) - { - screenName = [localizedNames objectForKey:[[localizedNames allKeys] objectAtIndex:0]]; - } - } - - if (screenName == nil) - { - screenName = [[NSString alloc] initWithFormat:@"%i", displayID]; - } - else - { - // ensure screen name is unique by appending displayid - screenName = [screenName stringByAppendingFormat:@" (%@)", [@(displayID) stringValue]]; - } - - return screenName; -} - -int GetDisplayIndex(const std::string& dispName) -{ - int ret = 0; - - // Add full screen settings for additional monitors - int numDisplays = [[NSScreen screens] count]; - - for (int disp = 0; disp < numDisplays; disp++) - { - NSString *name = screenNameForDisplay(GetDisplayID(disp)); - if ([name UTF8String] == dispName) - { - ret = disp; - break; - } - } - - return ret; -} - -void ShowHideNSWindow(NSWindow *wind, bool show) -{ - if (show) - [wind orderFront:nil]; - else - [wind orderOut:nil]; -} - -static NSWindow *curtainWindow; -void fadeInDisplay(NSScreen *theScreen, double fadeTime) -{ - int fadeSteps = 100; - double fadeInterval = (fadeTime / (double) fadeSteps); - - if (curtainWindow != nil) - { - for (int step = 0; step < fadeSteps; step++) - { - double fade = 1.0 - (step * fadeInterval); - [curtainWindow setAlphaValue:fade]; - - NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:nextDate]; - } - } - [curtainWindow close]; - curtainWindow = nil; - - [NSCursor unhide]; -} - -void fadeOutDisplay(NSScreen *theScreen, double fadeTime) -{ - int fadeSteps = 100; - double fadeInterval = (fadeTime / (double) fadeSteps); - - [NSCursor hide]; - - curtainWindow = [[NSWindow alloc] - initWithContentRect:[theScreen frame] - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:YES - screen:theScreen]; - - [curtainWindow setAlphaValue:0.0]; - [curtainWindow setBackgroundColor:[NSColor blackColor]]; - [curtainWindow setLevel:NSScreenSaverWindowLevel]; - - [curtainWindow makeKeyAndOrderFront:nil]; - [curtainWindow setFrame:[curtainWindow - frameRectForContentRect:[theScreen frame]] - display:YES - animate:NO]; - - for (int step = 0; step < fadeSteps; step++) - { - double fade = step * fadeInterval; - [curtainWindow setAlphaValue:fade]; - - NSDate *nextDate = [NSDate dateWithTimeIntervalSinceNow:fadeInterval]; - [[NSRunLoop currentRunLoop] runUntilDate:nextDate]; - } -} - -// try to find mode that matches the desired size, refreshrate -// non interlaced, nonstretched, safe for hardware -CGDisplayModeRef GetMode(int width, int height, double refreshrate, int screenIdx) -{ - if ( screenIdx >= (signed)[[NSScreen screens] count]) - return NULL; - - Boolean stretched; - Boolean interlaced; - Boolean safeForHardware; - int w, h, bitsperpixel; - double rate; - RESOLUTION_INFO res; - - CLog::Log(LOGDEBUG, "GetMode looking for suitable mode with {} x {} @ {:f} Hz on display {}", - width, height, refreshrate, screenIdx); - - CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(screenIdx)); - - if (!displayModes) - return NULL; - - for (int i=0; i < CFArrayGetCount(displayModes); ++i) - { - CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i); - uint32_t flags = CGDisplayModeGetIOFlags(displayMode); - stretched = flags & kDisplayModeStretchedFlag ? true : false; - interlaced = flags & kDisplayModeInterlacedFlag ? true : false; - bitsperpixel = DisplayBitsPerPixelForMode(displayMode); - safeForHardware = flags & kDisplayModeSafetyFlags ? true : false; - w = CGDisplayModeGetWidth(displayMode); - h = CGDisplayModeGetHeight(displayMode); - rate = CGDisplayModeGetRefreshRate(displayMode); - - - if ((bitsperpixel == 32) && - (safeForHardware == YES) && - (stretched == NO) && - (interlaced == NO) && - (w == width) && - (h == height) && - (rate == refreshrate || rate == 0)) - { - CLog::Log(LOGDEBUG, "GetMode found a match!"); - return displayMode; - } - } - - CFRelease(displayModes); - CLog::Log(LOGERROR, "GetMode - no match found!"); - return NULL; -} - -//--------------------------------------------------------------------------------- -static void DisplayReconfigured(CGDirectDisplayID display, - CGDisplayChangeSummaryFlags flags, void* userData) -{ - CWinSystemOSX *winsys = (CWinSystemOSX*)userData; - if (!winsys) - return; - - CLog::Log(LOGDEBUG, "CWinSystemOSX::DisplayReconfigured with flags {}", flags); - - // we fire the callbacks on start of configuration - // or when the mode set was finished - // or when we are called with flags == 0 (which is undocumented but seems to happen - // on some macs - we treat it as device reset) - - // first check if we need to call OnLostDevice - if (flags & kCGDisplayBeginConfigurationFlag) - { - // pre/post-reconfiguration changes - RESOLUTION res = CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution(); - if (res == RES_INVALID) - return; - - NSScreen* pScreen = nil; - unsigned int screenIdx = 0; - - if ( screenIdx < [[NSScreen screens] count] ) - { - pScreen = [[NSScreen screens] objectAtIndex:screenIdx]; - } - - // kCGDisplayBeginConfigurationFlag is only fired while the screen is still - // valid - if (pScreen) - { - CGDirectDisplayID xbmc_display = GetDisplayIDFromScreen(pScreen); - if (xbmc_display == display) - { - // we only respond to changes on the display we are running on. - winsys->AnnounceOnLostDevice(); - winsys->StartLostDeviceTimer(); - } - } - } - else // the else case checks if we need to call OnResetDevice - { - // we fire if kCGDisplaySetModeFlag is set or if flags == 0 - // (which is undocumented but seems to happen - // on some macs - we treat it as device reset) - // we also don't check the screen here as we might not even have - // one anymore (e.x. when tv is turned off) - if (flags & kCGDisplaySetModeFlag || flags == 0) - { - winsys->StopLostDeviceTimer(); // no need to timeout - we've got the callback - winsys->HandleOnResetDevice(); - } - } - - if ((flags & kCGDisplayAddFlag) || (flags & kCGDisplayRemoveFlag)) - winsys->UpdateResolutions(); -} - -//------------------------------------------------------------------------------ -NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx); -void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view); - -//------------------------------------------------------------------------------ -CWinSystemOSX::CWinSystemOSX() - : CWinSystemBase() - , m_impl{new CWinSystemOSXImpl} - , m_lostDeviceTimer(this) -{ - m_SDLSurface = NULL; - m_osx_events = NULL; - m_obscured = false; - m_obscured_timecheck = std::chrono::steady_clock::now() + std::chrono::milliseconds(1000); - m_lastDisplayNr = -1; - m_movedToOtherScreen = false; - m_refreshRate = 0.0; - m_delayDispReset = false; - - m_winEvents.reset(new CWinEventsOSX()); - - AE::CAESinkFactory::ClearSinks(); - CAESinkDARWINOSX::Register(); - m_dpms = std::make_shared(); -} - -CWinSystemOSX::~CWinSystemOSX() = default; - -void CWinSystemOSX::StartLostDeviceTimer() -{ - if (m_lostDeviceTimer.IsRunning()) - m_lostDeviceTimer.Restart(); - else - m_lostDeviceTimer.Start(LOST_DEVICE_TIMEOUT_MS, false); -} - -void CWinSystemOSX::StopLostDeviceTimer() -{ - m_lostDeviceTimer.Stop(); -} - -void CWinSystemOSX::OnTimeout() -{ - HandleOnResetDevice(); -} - -bool CWinSystemOSX::InitWindowSystem() -{ - CLog::LogF(LOGINFO, "Setup SDL"); - - /* Clean up on exit, exit on window close and interrupt */ - std::atexit(SDL_Quit); - - if (SDL_Init(SDL_INIT_VIDEO) != 0) - { - CLog::LogF(LOGFATAL, "Unable to initialize SDL: {}", SDL_GetError()); - return false; - } - // SDL_Init will install a handler for segfaults, restore the default handler. - signal(SIGSEGV, SIG_DFL); - - SDL_EnableUNICODE(1); - - // set repeat to 10ms to ensure repeat time < frame time - // so that hold times can be reliably detected - SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, 10); - - if (!CWinSystemBase::InitWindowSystem()) - return false; - - m_osx_events = new CWinEventsOSX(); - - CGDisplayRegisterReconfigurationCallback(DisplayReconfigured, (void*)this); - - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - auto windowDidMove = [windowDidMoveNoteClass initWith:this]; - [center addObserver:windowDidMove - selector:@selector(windowDidMoveNotification:) - name:NSWindowDidMoveNotification object:nil]; - m_impl->m_windowDidMove = windowDidMove; - - auto windowDidReSize = [windowDidReSizeNoteClass initWith:this]; - [center addObserver:windowDidReSize - selector:@selector(windowDidReSizeNotification:) - name:NSWindowDidResizeNotification object:nil]; - m_impl->m_windowDidReSize = windowDidReSize; - - auto windowDidChangeScreen = [windowDidChangeScreenNoteClass initWith:this]; - [center addObserver:windowDidChangeScreen - selector:@selector(windowDidChangeScreenNotification:) - name:NSWindowDidChangeScreenNotification object:nil]; - m_impl->m_windowChangedScreen = windowDidChangeScreen; - - return true; -} - -bool CWinSystemOSX::DestroyWindowSystem() -{ - NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; - [center removeObserver:m_impl->m_windowDidMove name:NSWindowDidMoveNotification object:nil]; - [center removeObserver:m_impl->m_windowDidReSize name:NSWindowDidResizeNotification object:nil]; - [center removeObserver:m_impl->m_windowChangedScreen - name:NSWindowDidChangeScreenNotification - object:nil]; - - CGDisplayRemoveReconfigurationCallback(DisplayReconfigured, (void*)this); - - delete m_osx_events; - m_osx_events = NULL; - - UnblankDisplays(); - m_impl->m_glContext = nil; - return true; -} - -bool CWinSystemOSX::CreateNewWindow(const std::string& name, bool fullScreen, RESOLUTION_INFO& res) -{ - // force initial window creation to be windowed, if fullscreen, it will switch to it below - // fixes the white screen of death if starting fullscreen and switching to windowed. - RESOLUTION_INFO resInfo = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW); - m_nWidth = resInfo.iWidth; - m_nHeight = resInfo.iHeight; - m_bFullScreen = false; - - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - - // Enable vertical sync to avoid any tearing. - SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); - - m_SDLSurface = SDL_SetVideoMode(m_nWidth, m_nHeight, 0, SDL_OPENGL | SDL_RESIZABLE); - if (!m_SDLSurface) - return false; - - // the context SDL creates isn't full screen compatible, so we create new one - // first, find the current contect and make sure a view is attached - NSOpenGLContext* cur_context = [NSOpenGLContext currentContext]; - NSView* view = [cur_context view]; - if (!view) - return false; - - if (CDisplaySettings::GetInstance().GetCurrentResolution() != RES_WINDOW) - { - // If we are not starting up windowed, then hide the initial SDL window - // so we do not see it flash before the fade-out and switch to fullscreen. - ShowHideNSWindow([view window], false); - } - - // disassociate view from context - [cur_context clearDrawable]; - - // release the context - if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context) - { - [ NSOpenGLContext clearCurrentContext ]; - [ cur_context clearDrawable ]; - cur_context = nil; - } - - // create a new context - auto new_context = CreateWindowedContext(nil); - if (!new_context) - return false; - - // associate with current view - [new_context setView:view]; - [new_context makeCurrentContext]; - - // set the window title - [[[new_context view] window] - setTitle:[NSString stringWithFormat:@"%s Media Center", CCompileInfo::GetAppName()]]; - - m_impl->m_glContext = new_context; - CWinSystemOSXImpl::m_lastOwnedContext = new_context; - m_bWindowCreated = true; - - // get screen refreshrate - this is needed - // when we startup in windowed mode and don't run through SetFullScreen - int dummy; - GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr); - - // register platform dependent objects - CDVDFactoryCodec::ClearHWAccels(); - VTB::CDecoder::Register(); - VIDEOPLAYER::CRendererFactory::ClearRenderer(); - CLinuxRendererGL::Register(); - CRendererVTB::Register(); - VIDEOPLAYER::CProcessInfoOSX::Register(); - RETRO::CRPProcessInfoOSX::Register(); - RETRO::CRPProcessInfoOSX::RegisterRendererFactory(new RETRO::CRendererFactoryOpenGL); - CScreenshotSurfaceGL::Register(); - - return true; -} - -bool CWinSystemOSX::DestroyWindow() -{ - return true; -} - -void ResizeWindowInternal(int newWidth, int newHeight, int newLeft, int newTop, NSView* last_view) -{ - if (last_view && [last_view window]) - { - auto size = NSMakeSize(newWidth, newHeight); - NSWindow* lastWindow = [last_view window]; - [lastWindow setContentSize:size]; - [lastWindow update]; - [last_view setFrameSize:size]; - } -} -bool CWinSystemOSX::ResizeWindow(int newWidth, int newHeight, int newLeft, int newTop) -{ - if (!m_impl->m_glContext) - return false; - - NSOpenGLContext* context = [NSOpenGLContext currentContext]; - NSView* view; - NSWindow* window; - - view = [context view]; - - if (view) - { - // It seems, that in macOS 10.15 this defaults to YES, but we currently do not support - // Retina resolutions properly. Ensure that the view uses a 1 pixel per point framebuffer. - view.wantsBestResolutionOpenGLSurface = NO; - } - - if (view && (newWidth > 0) && (newHeight > 0)) - { - window = [view window]; - if (window) - { - [window setContentSize:NSMakeSize(newWidth, newHeight)]; - [window update]; - [view setFrameSize:NSMakeSize(newWidth, newHeight)]; - [context update]; - // this is needed in case we traverse from fullscreen screen 2 - // to windowed on screen 1 directly where in ScreenChangedNotification - // we don't have a window to get the current screen on - // in that case ResizeWindow is called at a later stage from SetFullScreen(false) - // and we can grab the correct display number here then - m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen( [window screen] )); - } - } - - // HACK: resize SDL's view manually so that mouse bounds are correctly updated. - // there are two parts to this, the internal SDL (current_video->screen) and - // the cocoa view ( handled in SetFullScreen). - SDL_SetWidthHeight(newWidth, newHeight); - - [context makeCurrentContext]; - - m_nWidth = newWidth; - m_nHeight = newHeight; - m_impl->m_glContext = context; - CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(m_refreshRate); - - return true; -} - -static bool needtoshowme = true; - -bool CWinSystemOSX::SetFullScreen(bool fullScreen, RESOLUTION_INFO& res, bool blankOtherDisplays) -{ - static NSWindow* windowedFullScreenwindow = nil; - static NSPoint last_window_origin; - static NSView* last_view = nil; - static NSSize last_view_size; - static NSPoint last_view_origin; - static NSInteger last_window_level = NSNormalWindowLevel; - bool was_fullscreen = m_bFullScreen; - NSOpenGLContext* cur_context; - - // Fade to black to hide resolution-switching flicker and garbage. - CGDisplayFadeReservationToken fade_token = DisplayFadeToBlack(needtoshowme); - - // If we're already fullscreen then we must be moving to a different display. - // or if we are still on the same display - it might be only a refreshrate/resolution - // change request. - // Recurse to reset fullscreen mode and then continue. - if (was_fullscreen && fullScreen) - { - needtoshowme = false; - ShowHideNSWindow([last_view window], needtoshowme); - RESOLUTION_INFO& window = CDisplaySettings::GetInstance().GetResolutionInfo(RES_WINDOW); - CWinSystemOSX::SetFullScreen(false, window, blankOtherDisplays); - needtoshowme = true; - } - - const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); - m_lastDisplayNr = GetDisplayIndex(settings->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR)); - m_nWidth = res.iWidth; - m_nHeight = res.iHeight; - m_bFullScreen = fullScreen; - - cur_context = [NSOpenGLContext currentContext]; - - //handle resolution/refreshrate switching early here - if (m_bFullScreen) - { - // switch videomode - SwitchToVideoMode(res.iWidth, res.iHeight, static_cast(res.fRefreshRate)); - } - - //no context? done. - if (!cur_context) - { - DisplayFadeFromBlack(fade_token, needtoshowme); - return false; - } - - if (windowedFullScreenwindow != nil) - { - [windowedFullScreenwindow close]; - windowedFullScreenwindow = nil; - } - - if (m_bFullScreen) - { - // FullScreen Mode - NSOpenGLContext* newContext = nil; - - // Save info about the windowed context so we can restore it when returning to windowed. - last_view = [cur_context view]; - last_view_size = [last_view frame].size; - last_view_origin = [last_view frame].origin; - last_window_origin = [[last_view window] frame].origin; - last_window_level = [[last_view window] level]; - - // This is Cocoa Windowed FullScreen Mode - // Get the screen rect of our current display - NSScreen* pScreen = [[NSScreen screens] objectAtIndex:m_lastDisplayNr]; - NSRect screenRect = [pScreen frame]; - - // remove frame origin offset of original display - screenRect.origin = NSZeroPoint; - - // make a new window to act as the windowedFullScreen - windowedFullScreenwindow = [[NSWindow alloc] initWithContentRect:screenRect - styleMask:NSBorderlessWindowMask - backing:NSBackingStoreBuffered - defer:NO - screen:pScreen]; - windowedFullScreenwindow.releasedWhenClosed = NO; - - [windowedFullScreenwindow setBackgroundColor:[NSColor blackColor]]; - [windowedFullScreenwindow makeKeyAndOrderFront:nil]; - - // make our window the same level as the rest to enable cmd+tab switching - [windowedFullScreenwindow setLevel:NSNormalWindowLevel]; - // this will make our window topmost and hide all system messages - //[windowedFullScreenwindow setLevel:CGShieldingWindowLevel()]; - - // ...and the original one beneath it and on the same screen. - [[last_view window] setLevel:NSNormalWindowLevel-1]; - [[last_view window] setFrameOrigin:[pScreen frame].origin]; - // expand the mouse bounds in SDL view to fullscreen - [ last_view setFrameOrigin:NSMakePoint(0.0, 0.0)]; - [ last_view setFrameSize:NSMakeSize(m_nWidth, m_nHeight) ]; - - NSView* blankView = [[NSView alloc] init]; - [windowedFullScreenwindow setContentView:blankView]; - [windowedFullScreenwindow setContentSize:NSMakeSize(m_nWidth, m_nHeight)]; - [windowedFullScreenwindow update]; - [blankView setFrameSize:NSMakeSize(m_nWidth, m_nHeight)]; - - // force SDL's window origin to be at zero: - // on Monterey, X coordinate becomes non-zero somewhere after this method returns - const auto twoSeconds = dispatch_time(DISPATCH_TIME_NOW, 2LL * NSEC_PER_SEC); - dispatch_after(twoSeconds, dispatch_get_main_queue(), ^{ - for (NSWindow* w in NSApp.windows) - { - if (w == windowedFullScreenwindow) - continue; - [w setFrameOrigin:w.screen.frame.origin]; - break; - } - }); - - // Obtain windowed pixel format and create a new context. - newContext = CreateWindowedContext(cur_context); - [newContext setView:blankView]; - - // Hide the menu bar. - SetMenuBarVisible(false); - - // Blank other displays if requested. - if (blankOtherDisplays) - BlankOtherDisplays(m_lastDisplayNr); - - // Hide the mouse. - [NSCursor hide]; - - // Release old context if we created it. - if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context) - { - [NSOpenGLContext clearCurrentContext]; - [cur_context clearDrawable]; - } - - // activate context - [newContext makeCurrentContext]; - CWinSystemOSXImpl::m_lastOwnedContext = newContext; - } - else - { - // Windowed Mode - // exit fullscreen - [cur_context clearDrawable]; - - [NSCursor unhide]; - - // Show menubar. - SetMenuBarVisible(true); - - // restore the windowed window level - [[last_view window] setLevel:last_window_level]; - - // Unblank. - // Force the unblank when returning from fullscreen, we get called with blankOtherDisplays set false. - //if (blankOtherDisplays) - UnblankDisplays(); - - // create our new context (sharing with the current one) - auto newContext = CreateWindowedContext(cur_context); - if (!newContext) - return false; - - // Assign view from old context, move back to original screen. - [newContext setView:last_view]; - [[last_view window] setFrameOrigin:last_window_origin]; - // return the mouse bounds in SDL view to previous size - [last_view setFrameSize:last_view_size]; - [last_view setFrameOrigin:last_view_origin]; - - // Release the fullscreen context. - if (CWinSystemOSXImpl::m_lastOwnedContext == cur_context) - { - [NSOpenGLContext clearCurrentContext]; - [cur_context clearDrawable]; - } - - // Activate context. - [newContext makeCurrentContext]; - CWinSystemOSXImpl::m_lastOwnedContext = newContext; - } - - DisplayFadeFromBlack(fade_token, needtoshowme); - - ShowHideNSWindow([last_view window], needtoshowme); - // need to make sure SDL tracks any window size changes - ResizeWindow(m_nWidth, m_nHeight, -1, -1); - ResizeWindowInternal(m_nWidth, m_nHeight, -1, -1, last_view); - // restore origin once again when going to windowed mode - if (!fullScreen) - { - [[last_view window] setFrameOrigin:last_window_origin]; - } - HandlePossibleRefreshrateChange(); - - m_updateGLContext = 0; - return true; -} - -void CWinSystemOSX::UpdateResolutions() -{ - CWinSystemBase::UpdateResolutions(); - - // Add desktop resolution - int w, h; - double fps; - - int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR)); - GetScreenResolution(&w, &h, &fps, dispIdx); - NSString* dispName = screenNameForDisplay(GetDisplayID(dispIdx)); - UpdateDesktopResolution(CDisplaySettings::GetInstance().GetResolutionInfo(RES_DESKTOP), [dispName UTF8String], w, h, fps, 0); - - CDisplaySettings::GetInstance().ClearCustomResolutions(); - - // now just fill in the possible resolutions for the attached screens - // and push to the resolution info vector - FillInVideoModes(); - CDisplaySettings::GetInstance().ApplyCalibrations(); -} - -/* -void* Cocoa_GL_CreateContext(void* pixFmt, void* shareCtx) -{ - if (!pixFmt) - return nil; - - NSOpenGLContext* newContext = [[NSOpenGLContext alloc] initWithFormat:(NSOpenGLPixelFormat*)pixFmt - shareContext:(NSOpenGLContext*)shareCtx]; - - // snipit from SDL_cocoaopengl.m - // - // Wisdom from Apple engineer in reference to UT2003's OpenGL performance: - // "You are blowing a couple of the internal OpenGL function caches. This - // appears to be happening in the VAO case. You can tell OpenGL to up - // the cache size by issuing the following calls right after you create - // the OpenGL context. The default cache size is 16." --ryan. - // - - #ifndef GLI_ARRAY_FUNC_CACHE_MAX - #define GLI_ARRAY_FUNC_CACHE_MAX 284 - #endif - - #ifndef GLI_SUBMIT_FUNC_CACHE_MAX - #define GLI_SUBMIT_FUNC_CACHE_MAX 280 - #endif - - { - long cache_max = 64; - CGLContextObj ctx = (CGLContextObj)[newContext CGLContextObj]; - CGLSetParameter(ctx, (CGLContextParameter)GLI_SUBMIT_FUNC_CACHE_MAX, &cache_max); - CGLSetParameter(ctx, (CGLContextParameter)GLI_ARRAY_FUNC_CACHE_MAX, &cache_max); - } - - // End Wisdom from Apple Engineer section. --ryan. - return newContext; -} -*/ - -NSOpenGLContext* CreateWindowedContext(NSOpenGLContext* shareCtx) -{ - NSOpenGLPixelFormat* pixFmt; - if (getenv("KODI_GL_PROFILE_LEGACY")) - { - NSOpenGLPixelFormatAttribute wattrs[] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFANoRecovery, - NSOpenGLPFAAccelerated, - NSOpenGLPFADepthSize, - static_cast(8), - static_cast(0)}; - pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs]; - } - else - { - NSOpenGLPixelFormatAttribute wattrs_gl3[] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAOpenGLProfile, - NSOpenGLProfileVersion3_2Core, - NSOpenGLPFANoRecovery, - NSOpenGLPFAAccelerated, - NSOpenGLPFADepthSize, - static_cast(24), - static_cast(0)}; - pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs_gl3]; - } - - auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx]; - - if (!newContext) - { - // bah, try again for non-accelerated renderer - NSOpenGLPixelFormatAttribute wattrs2[] = - { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFANoRecovery, - NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)8, - (NSOpenGLPixelFormatAttribute)0 - }; - newContext = [[NSOpenGLContext alloc] - initWithFormat:[[NSOpenGLPixelFormat alloc] initWithAttributes:wattrs2] - shareContext:shareCtx]; - } - - return newContext; -} - -NSOpenGLContext* CreateFullScreenContext(int screen_index, NSOpenGLContext* shareCtx) -{ - CGDirectDisplayID displayArray[MAX_DISPLAYS]; - CGDisplayCount numDisplays; - CGDirectDisplayID displayID; - - // Get the list of displays. - CGGetActiveDisplayList(MAX_DISPLAYS, displayArray, &numDisplays); - displayID = displayArray[screen_index]; - - NSOpenGLPixelFormat* pixFmt; - if (getenv("KODI_GL_PROFILE_LEGACY")) - { - NSOpenGLPixelFormatAttribute fsattrs[] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFANoRecovery, - NSOpenGLPFAAccelerated, - NSOpenGLPFADepthSize, - static_cast(8), - NSOpenGLPFAScreenMask, - static_cast(CGDisplayIDToOpenGLDisplayMask(displayID)), - static_cast(0)}; - pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs]; - } - else - { - NSOpenGLPixelFormatAttribute fsattrs_gl3[] = { - NSOpenGLPFADoubleBuffer, - NSOpenGLPFANoRecovery, - NSOpenGLPFAAccelerated, - NSOpenGLPFADepthSize, - static_cast(24), - NSOpenGLPFAOpenGLProfile, - NSOpenGLProfileVersion3_2Core, - NSOpenGLPFAScreenMask, - static_cast(CGDisplayIDToOpenGLDisplayMask(displayID)), - static_cast(0)}; - pixFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:fsattrs_gl3]; - } - - if (!pixFmt) - return nil; - - auto newContext = [[NSOpenGLContext alloc] initWithFormat:pixFmt shareContext:shareCtx]; - - return newContext; -} - -void CWinSystemOSX::GetScreenResolution(int* w, int* h, double* fps, int screenIdx) -{ - CGDirectDisplayID display_id = (CGDirectDisplayID)GetDisplayID(screenIdx); - CGDisplayModeRef mode = CGDisplayCopyDisplayMode(display_id); - *w = CGDisplayModeGetWidth(mode); - *h = CGDisplayModeGetHeight(mode); - *fps = CGDisplayModeGetRefreshRate(mode); - CGDisplayModeRelease(mode); - if ((int)*fps == 0) - { - // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays. - *fps = 60.0; - } -} - -void CWinSystemOSX::EnableVSync(bool enable) -{ - // OpenGL Flush synchronised with vertical retrace - GLint swapInterval = enable ? 1 : 0; - [[NSOpenGLContext currentContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; -} - -bool CWinSystemOSX::SwitchToVideoMode(int width, int height, double refreshrate) -{ - boolean_t match = false; - CGDisplayModeRef dispMode = NULL; - - int screenIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR)); - - // Figure out the screen size. (default to main screen) - CGDirectDisplayID display_id = GetDisplayID(screenIdx); - - // find mode that matches the desired size, refreshrate - // non interlaced, nonstretched, safe for hardware - dispMode = GetMode(width, height, refreshrate, screenIdx); - - //not found - fallback to bestemdeforparameters - if (!dispMode) - { - dispMode = BestMatchForMode(display_id, 32, width, height, match); - - if (!match) - dispMode = BestMatchForMode(display_id, 16, width, height, match); - - // still no match? fallback to current resolution of the display which HAS to work [tm] - if (!match) - { - int tmpWidth; - int tmpHeight; - double tmpRefresh; - - GetScreenResolution(&tmpWidth, &tmpHeight, &tmpRefresh, screenIdx); - dispMode = GetMode(tmpWidth, tmpHeight, tmpRefresh, screenIdx); - - // no way to get a resolution set - if (!dispMode) - return false; - } - - if (!match) - return false; - } - - // switch mode and return success - CGDisplayCapture(display_id); - CGDisplayConfigRef cfg; - CGBeginDisplayConfiguration(&cfg); - CGConfigureDisplayWithDisplayMode(cfg, display_id, dispMode, nullptr); - CGError err = CGCompleteDisplayConfiguration(cfg, kCGConfigureForAppOnly); - CGDisplayRelease(display_id); - - m_refreshRate = CGDisplayModeGetRefreshRate(dispMode); - - Cocoa_CVDisplayLinkUpdate(); - - return (err == kCGErrorSuccess); -} - -void CWinSystemOSX::FillInVideoModes() -{ - int dispIdx = GetDisplayIndex(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_VIDEOSCREEN_MONITOR)); - - // Add full screen settings for additional monitors - int numDisplays = [[NSScreen screens] count]; - - for (int disp = 0; disp < numDisplays; disp++) - { - Boolean stretched; - Boolean interlaced; - Boolean safeForHardware; - int w, h, bitsperpixel; - double refreshrate; - RESOLUTION_INFO res; - - CFArrayRef displayModes = GetAllDisplayModes(GetDisplayID(disp)); - NSString *dispName = screenNameForDisplay(GetDisplayID(disp)); - - CLog::Log(LOGINFO, "Display {} has name {}", disp, [dispName UTF8String]); - - if (NULL == displayModes) - continue; - - for (int i=0; i < CFArrayGetCount(displayModes); ++i) - { - CGDisplayModeRef displayMode = (CGDisplayModeRef)CFArrayGetValueAtIndex(displayModes, i); - - uint32_t flags = CGDisplayModeGetIOFlags(displayMode); - stretched = flags & kDisplayModeStretchedFlag ? true : false; - interlaced = flags & kDisplayModeInterlacedFlag ? true : false; - bitsperpixel = DisplayBitsPerPixelForMode(displayMode); - safeForHardware = flags & kDisplayModeSafetyFlags ? true : false; - - if ((bitsperpixel == 32) && - (safeForHardware == YES) && - (stretched == NO) && - (interlaced == NO)) - { - w = CGDisplayModeGetWidth(displayMode); - h = CGDisplayModeGetHeight(displayMode); - refreshrate = CGDisplayModeGetRefreshRate(displayMode); - if ((int)refreshrate == 0) // LCD display? - { - // NOTE: The refresh rate will be REPORTED AS 0 for many DVI and notebook displays. - refreshrate = 60.0; - } - CLog::Log(LOGINFO, "Found possible resolution for display {} with {} x {} @ {:f} Hz", disp, - w, h, refreshrate); - - // only add the resolution if it belongs to "our" screen - // all others are only logged above... - if (disp == dispIdx) - { - UpdateDesktopResolution(res, (dispName != nil) ? [dispName UTF8String] : "Unknown", w, h, refreshrate, 0); - CServiceBroker::GetWinSystem()->GetGfxContext().ResetOverscan(res); - CDisplaySettings::GetInstance().AddResolutionInfo(res); - } - } - } - CFRelease(displayModes); - } -} - -bool CWinSystemOSX::FlushBuffer(void) -{ - if (m_updateGLContext < 5) - { - [m_impl->m_glContext update]; - m_updateGLContext++; - } - - [m_impl->m_glContext flushBuffer]; - - return true; -} - -bool CWinSystemOSX::IsObscured(void) -{ - // check once a second if we are obscured. - auto now_time = std::chrono::steady_clock::now(); - if (m_obscured_timecheck > now_time) - return m_obscured; - else - m_obscured_timecheck = now_time + std::chrono::milliseconds(1000); - - NSOpenGLContext* cur_context = [NSOpenGLContext currentContext]; - NSView* view = [cur_context view]; - if (!view) - { - // sanity check, we should always have a view - m_obscured = true; - return m_obscured; - } - - NSWindow *window = [view window]; - if (!window) - { - // sanity check, we should always have a window - m_obscured = true; - return m_obscured; - } - - if ([window isVisible] == NO) - { - // not visible means the window is not showing. - // this should never really happen as we are always visible - // even when minimized in dock. - m_obscured = true; - return m_obscured; - } - - // check if we are minimized (to an icon in the Dock). - if ([window isMiniaturized] == YES) - { - m_obscured = true; - return m_obscured; - } - - // check if we are showing on the active workspace. - if ([window isOnActiveSpace] == NO) - { - m_obscured = true; - return m_obscured; - } - - // default to false before we start parsing though the windows. - // if we are are obscured by any windows, then set true. - m_obscured = false; - static bool obscureLogged = false; - - CGWindowListOption opts; - opts = kCGWindowListOptionOnScreenAboveWindow | kCGWindowListExcludeDesktopElements; - CFArrayRef windowIDs =CGWindowListCreate(opts, (CGWindowID)[window windowNumber]); - - if (!windowIDs) - return m_obscured; - - CFArrayRef windowDescs = CGWindowListCreateDescriptionFromArray(windowIDs); - if (!windowDescs) - { - CFRelease(windowIDs); - return m_obscured; - } - - CGRect bounds = NSRectToCGRect([window frame]); - // kCGWindowBounds measures the origin as the top-left corner of the rectangle - // relative to the top-left corner of the screen. - // NSWindow’s frame property measures the origin as the bottom-left corner - // of the rectangle relative to the bottom-left corner of the screen. - // convert bounds from NSWindow to CGWindowBounds here. - bounds.origin.y = [[window screen] frame].size.height - bounds.origin.y - bounds.size.height; - - std::vector partialOverlaps; - CRect ourBounds = CGRectToCRect(bounds); - - for (CFIndex idx=0; idx < CFArrayGetCount(windowDescs); idx++) - { - // walk the window list of windows that are above us and are not desktop elements - CFDictionaryRef windowDictionary = (CFDictionaryRef)CFArrayGetValueAtIndex(windowDescs, idx); - - // skip the Dock window, it actually covers the entire screen. - CFStringRef ownerName = (CFStringRef)CFDictionaryGetValue(windowDictionary, kCGWindowOwnerName); - if (CFStringCompare(ownerName, CFSTR("Dock"), 0) == kCFCompareEqualTo) - continue; - - // Ignore known brightness tools for dimming the screen. They claim to cover - // the whole XBMC window and therefore would make the framerate limiter - // kicking in. Unfortunately even the alpha of these windows is 1.0 so - // we have to check the ownerName. - if (CFStringCompare(ownerName, CFSTR("Shades"), 0) == kCFCompareEqualTo || - CFStringCompare(ownerName, CFSTR("SmartSaver"), 0) == kCFCompareEqualTo || - CFStringCompare(ownerName, CFSTR("Brightness Slider"), 0) == kCFCompareEqualTo || - CFStringCompare(ownerName, CFSTR("Displaperture"), 0) == kCFCompareEqualTo || - CFStringCompare(ownerName, CFSTR("Dreamweaver"), 0) == kCFCompareEqualTo || - CFStringCompare(ownerName, CFSTR("Window Server"), 0) == kCFCompareEqualTo) - continue; - - CFDictionaryRef rectDictionary = (CFDictionaryRef)CFDictionaryGetValue(windowDictionary, kCGWindowBounds); - if (!rectDictionary) - continue; - - CGRect windowBounds; - if (CGRectMakeWithDictionaryRepresentation(rectDictionary, &windowBounds)) - { - if (CGRectContainsRect(windowBounds, bounds)) - { - // if the windowBounds completely encloses our bounds, we are obscured. - if (!obscureLogged) - { - std::string appName; - if (CDarwinUtils::CFStringRefToUTF8String(ownerName, appName)) - CLog::Log(LOGDEBUG, "WinSystemOSX: Fullscreen window {} obscures Kodi!", appName); - obscureLogged = true; - } - m_obscured = true; - break; - } - - // handle overlapping windows above us that combine - // to obscure by collecting any partial overlaps, - // then subtract them from our bounds and check - // for any remaining area. - CRect intersection = CGRectToCRect(windowBounds); - intersection.Intersect(ourBounds); - if (!intersection.IsEmpty()) - partialOverlaps.push_back(intersection); - } - } - - if (!m_obscured) - { - // if we are here we are not obscured by any fullscreen window - reset flag - // for allowing the logmessage above to show again if this changes. - if (obscureLogged) - obscureLogged = false; - std::vector rects = ourBounds.SubtractRects(partialOverlaps); - // they got us covered - if (rects.empty()) - m_obscured = true; - } - - CFRelease(windowDescs); - CFRelease(windowIDs); - - return m_obscured; -} - -void CWinSystemOSX::NotifyAppFocusChange(bool bGaining) -{ - if (!(m_bFullScreen && bGaining)) - return; - @autoreleasepool - { - // find the window - NSOpenGLContext* context = [NSOpenGLContext currentContext]; - if (context) - { - NSView* view; - - view = [context view]; - if (view) - { - NSWindow* window; - window = [view window]; - if (window) - { - SetMenuBarVisible(false); - [window orderFront:nil]; - } - } - } - } -} - -void CWinSystemOSX::ShowOSMouse(bool show) -{ - SDL_ShowCursor(show ? 1 : 0); -} - -bool CWinSystemOSX::Minimize() -{ - @autoreleasepool - { - [[NSApplication sharedApplication] miniaturizeAll:nil]; - } - return true; -} - -bool CWinSystemOSX::Restore() -{ - @autoreleasepool - { - [[NSApplication sharedApplication] unhide:nil]; - } - return true; -} - -bool CWinSystemOSX::Hide() -{ - @autoreleasepool - { - [[NSApplication sharedApplication] hide:nil]; - } - return true; -} - -void CWinSystemOSX::HandlePossibleRefreshrateChange() -{ - static double oldRefreshRate = m_refreshRate; - Cocoa_CVDisplayLinkUpdate(); - int dummy = 0; - - GetScreenResolution(&dummy, &dummy, &m_refreshRate, m_lastDisplayNr); - - if (oldRefreshRate != m_refreshRate) - { - oldRefreshRate = m_refreshRate; - // send a message so that videoresolution (and refreshrate) - // is changed - CServiceBroker::GetAppMessenger()->PostMsg(TMSG_VIDEORESIZE, m_SDLSurface->w, m_SDLSurface->h); - } -} - -void CWinSystemOSX::OnMove(int x, int y) -{ - HandlePossibleRefreshrateChange(); -} - -std::unique_ptr CWinSystemOSX::GetOSScreenSaverImpl() -{ - return std::unique_ptr (new COSScreenSaverOSX); -} - -OSXTextInputResponder *g_textInputResponder = nil; - -void CWinSystemOSX::StartTextInput() -{ - NSView *parentView = [[NSApp keyWindow] contentView]; - - /* We only keep one field editor per process, since only the front most - * window can receive text input events, so it make no sense to keep more - * than one copy. When we switched to another window and requesting for - * text input, simply remove the field editor from its superview then add - * it to the front most window's content view */ - if (!g_textInputResponder) { - g_textInputResponder = - [[OSXTextInputResponder alloc] initWithFrame: NSMakeRect(0.0, 0.0, 0.0, 0.0)]; - } - - if (![[g_textInputResponder superview] isEqual: parentView]) - { -// DLOG(@"add fieldEdit to window contentView"); - [g_textInputResponder removeFromSuperview]; - [parentView addSubview: g_textInputResponder]; - [[NSApp keyWindow] makeFirstResponder: g_textInputResponder]; - } -} -void CWinSystemOSX::StopTextInput() -{ - if (g_textInputResponder) - { - [g_textInputResponder removeFromSuperview]; - g_textInputResponder = nil; - } -} - -void CWinSystemOSX::Register(IDispResource *resource) -{ - std::unique_lock lock(m_resourceSection); - m_resources.push_back(resource); -} - -void CWinSystemOSX::Unregister(IDispResource* resource) -{ - std::unique_lock lock(m_resourceSection); - std::vector::iterator i = find(m_resources.begin(), m_resources.end(), resource); - if (i != m_resources.end()) - m_resources.erase(i); -} - -bool CWinSystemOSX::Show(bool raise) -{ - @autoreleasepool - { - auto app = [NSApplication sharedApplication]; - if (raise) - { - [app unhide:nil]; - [app activateIgnoringOtherApps:YES]; - [app arrangeInFront:nil]; - } - else - { - [app unhideWithoutActivation]; - } - } - return true; -} - -void CWinSystemOSX::WindowChangedScreen() -{ - // user has moved the window to a - // different screen - NSOpenGLContext* context = [NSOpenGLContext currentContext]; - m_lastDisplayNr = -1; - - // if we are here the user dragged the window to a different - // screen and we return the screen of the window - if (context) - { - NSView* view; - - view = [context view]; - if (view) - { - NSWindow* window; - window = [view window]; - if (window) - { - m_lastDisplayNr = GetDisplayIndex(GetDisplayIDFromScreen([window screen])); - } - } - } - if (m_lastDisplayNr == -1) - m_lastDisplayNr = 0;// default to main screen -} - -void CWinSystemOSX::AnnounceOnLostDevice() -{ - std::unique_lock lock(m_resourceSection); - // tell any shared resources - CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnLostDevice"); - for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) - (*i)->OnLostDisplay(); -} - -void CWinSystemOSX::HandleOnResetDevice() -{ - - auto delay = - std::chrono::milliseconds(CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( - "videoscreen.delayrefreshchange") * - 100); - if (delay > 0ms) - { - m_delayDispReset = true; - m_dispResetTimer.Set(delay); - } - else - { - AnnounceOnResetDevice(); - } -} - -void CWinSystemOSX::AnnounceOnResetDevice() -{ - double currentFps = m_refreshRate; - int w = 0; - int h = 0; - int currentScreenIdx = m_lastDisplayNr; - // ensure that graphics context knows about the current refreshrate before - // doing the callbacks - GetScreenResolution(&w, &h, ¤tFps, currentScreenIdx); - - CServiceBroker::GetWinSystem()->GetGfxContext().SetFPS(currentFps); - - std::unique_lock lock(m_resourceSection); - // tell any shared resources - CLog::Log(LOGDEBUG, "CWinSystemOSX::AnnounceOnResetDevice"); - for (std::vector::iterator i = m_resources.begin(); i != m_resources.end(); ++i) - (*i)->OnResetDisplay(); -} - -void* CWinSystemOSX::GetCGLContextObj() -{ - return [m_impl->m_glContext CGLContextObj]; -} - -NSOpenGLContext* CWinSystemOSX::GetNSOpenGLContext() -{ - return m_impl->m_glContext; -} - -std::string CWinSystemOSX::GetClipboardText(void) -{ - std::string utf8_text; - - const char *szStr = Cocoa_Paste(); - if (szStr) - utf8_text = szStr; - - return utf8_text; -} - -std::unique_ptr CWinSystemOSX::GetVideoSync(CVideoReferenceClock* clock) -{ - std::unique_ptr pVSync(new CVideoSyncOsx(clock)); - return pVSync; -} - -bool CWinSystemOSX::MessagePump() -{ - return m_winEvents->MessagePump(); -} - -std::vector CWinSystemOSX::GetConnectedOutputs() -{ - std::vector outputs; - outputs.emplace_back("Default"); - - int numDisplays = [[NSScreen screens] count]; - - for (int disp = 0; disp < numDisplays; disp++) - { - NSString *dispName = screenNameForDisplay(GetDisplayID(disp)); - outputs.emplace_back([dispName UTF8String]); - } - - return outputs; -} From 09d33bcf111c174ec6c524a2399c849f88d65236 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Tue, 6 Jun 2023 22:00:16 +0100 Subject: [PATCH 358/811] [macos] cleanup unused methods in cocoa interface --- xbmc/platform/darwin/osx/CocoaInterface.h | 5 ----- xbmc/platform/darwin/osx/CocoaInterface.mm | 10 ---------- 2 files changed, 15 deletions(-) diff --git a/xbmc/platform/darwin/osx/CocoaInterface.h b/xbmc/platform/darwin/osx/CocoaInterface.h index 1f76a5df0e26f..41d5788a23130 100644 --- a/xbmc/platform/darwin/osx/CocoaInterface.h +++ b/xbmc/platform/darwin/osx/CocoaInterface.h @@ -30,11 +30,6 @@ extern "C" char* Cocoa_MountPoint2DeviceName(char *path); bool Cocoa_GetVolumeNameFromMountPoint(const std::string &mountPoint, std::string &volumeName); - // Mouse. - // - void Cocoa_HideMouse(); - void Cocoa_ShowMouse(); - const char *Cocoa_Paste() ; #ifdef __cplusplus diff --git a/xbmc/platform/darwin/osx/CocoaInterface.mm b/xbmc/platform/darwin/osx/CocoaInterface.mm index a1c52943bf930..be3ad1e7c7ee3 100644 --- a/xbmc/platform/darwin/osx/CocoaInterface.mm +++ b/xbmc/platform/darwin/osx/CocoaInterface.mm @@ -226,16 +226,6 @@ bool Cocoa_GetVolumeNameFromMountPoint(const std::string &mountPoint, std::strin } } -void Cocoa_HideMouse() -{ - [NSCursor hide]; -} - -void Cocoa_ShowMouse() -{ - [NSCursor unhide]; -} - //--------------------------------------------------------------------------------- const char *Cocoa_Paste() { From a06ff3e9af8e035708e18fd03e02a2813c304c90 Mon Sep 17 00:00:00 2001 From: Miguel Borges de Freitas <92enen@gmail.com> Date: Thu, 5 Oct 2023 14:44:21 +0100 Subject: [PATCH 359/811] [macos][cocoainterface] Address deprecations --- xbmc/platform/darwin/osx/CocoaInterface.mm | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/xbmc/platform/darwin/osx/CocoaInterface.mm b/xbmc/platform/darwin/osx/CocoaInterface.mm index be3ad1e7c7ee3..3e25da8744b15 100644 --- a/xbmc/platform/darwin/osx/CocoaInterface.mm +++ b/xbmc/platform/darwin/osx/CocoaInterface.mm @@ -30,11 +30,6 @@ CGDirectDisplayID Cocoa_GetDisplayIDFromScreen(NSScreen *screen); -NSOpenGLContext* Cocoa_GL_GetCurrentContext(void) -{ - return [NSOpenGLContext currentContext]; -} - uint32_t Cocoa_GL_GetCurrentDisplayID(void) { // Find which display we are on from the current context (default to main display) @@ -42,10 +37,8 @@ uint32_t Cocoa_GL_GetCurrentDisplayID(void) NSNumber* __block screenID; auto getScreenNumber = ^{ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - screenID = Cocoa_GL_GetCurrentContext().view.window.screen.deviceDescription[@"NSScreenNumber"]; -#pragma clang diagnostic pop + screenID = + NSApplication.sharedApplication.keyWindow.screen.deviceDescription[@"NSScreenNumber"]; }; if (NSThread.isMainThread) getScreenNumber(); @@ -64,10 +57,8 @@ bool Cocoa_CVDisplayLinkCreate(void *displayLinkcallback, void *displayLinkConte // OpenGL Flush synchronised with vertical retrace GLint swapInterval = 1; -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[NSOpenGLContext currentContext] setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; -#pragma clang diagnostic pop + [[NSOpenGLContext currentContext] setValues:&swapInterval + forParameter:NSOpenGLContextParameterSwapInterval]; display_id = (CGDirectDisplayID)Cocoa_GL_GetCurrentDisplayID(); if (!displayLink) From c778d7f49d77e8a0f5563ecd477e84ed6ca72ed6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:38:10 +0200 Subject: [PATCH 360/811] [fileitem] Fix CFileItem::LoadDetails to self-assign video tag only if successfully retrieved from video db. --- xbmc/FileItem.cpp | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 049f7d3229b32..ff1b30805fb5e 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3714,14 +3714,16 @@ bool CFileItem::LoadDetails() VIDEODATABASEDIRECTORY::CQueryParams params; VIDEODATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(GetPath(), params); + bool ret{false}; + auto tag{std::make_unique()}; if (params.GetMovieId() >= 0) - db.GetMovieInfo(GetPath(), *GetVideoInfoTag(), static_cast(params.GetMovieId())); + ret = db.GetMovieInfo(GetPath(), *tag, static_cast(params.GetMovieId())); else if (params.GetMVideoId() >= 0) - db.GetMusicVideoInfo(GetPath(), *GetVideoInfoTag(), static_cast(params.GetMVideoId())); + ret = db.GetMusicVideoInfo(GetPath(), *tag, static_cast(params.GetMVideoId())); else if (params.GetEpisodeId() >= 0) - db.GetEpisodeInfo(GetPath(), *GetVideoInfoTag(), static_cast(params.GetEpisodeId())); + ret = db.GetEpisodeInfo(GetPath(), *tag, static_cast(params.GetEpisodeId())); else if (params.GetSetId() >= 0) // movie set - db.GetSetInfo(static_cast(params.GetSetId()), *GetVideoInfoTag(), this); + ret = db.GetSetInfo(static_cast(params.GetSetId()), *tag, this); else if (params.GetTvShowId() >= 0) { if (params.GetSeason() >= 0) @@ -3729,16 +3731,16 @@ bool CFileItem::LoadDetails() const int idSeason = db.GetSeasonId(static_cast(params.GetTvShowId()), static_cast(params.GetSeason())); if (idSeason >= 0) - db.GetSeasonInfo(idSeason, *GetVideoInfoTag(), this); + ret = db.GetSeasonInfo(idSeason, *tag, this); } else - db.GetTvShowInfo(GetPath(), *GetVideoInfoTag(), static_cast(params.GetTvShowId()), - this); + ret = db.GetTvShowInfo(GetPath(), *tag, static_cast(params.GetTvShowId()), this); } - else - return false; - return true; + if (ret) + m_videoInfoTag = tag.release(); + + return ret; } if (URIUtils::IsPVRRecordingFileOrFolder(GetPath())) @@ -3778,8 +3780,12 @@ bool CFileItem::LoadDetails() return false; } - if (db.LoadVideoInfo(GetDynPath(), *GetVideoInfoTag())) + auto tag{std::make_unique()}; + if (db.LoadVideoInfo(GetDynPath(), *tag)) + { + m_videoInfoTag = tag.release(); return true; + } CLog::LogF(LOGERROR, "Error filling item details (path={})", GetPath()); return false; From 0f3a7b04891e115f3ad0cdb9ad71eb50a78baf5f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Thu, 5 Oct 2023 19:41:47 +0200 Subject: [PATCH 361/811] [fileitem] Fix CFileItem::LoadDetails not to try to load video tag from video db because this will never succeed (eliminates log spam, and saves some cpu cycles.) --- xbmc/FileItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index ff1b30805fb5e..ebe38fd340cec 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3768,7 +3768,7 @@ bool CFileItem::LoadDetails() return false; } - if (IsVideo()) + if (!IsPlayList() && IsVideo()) { if (HasVideoInfoTag()) return true; From a5bfc5daf19721c32538f2b79ffaf796408af731 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Fri, 6 Oct 2023 01:24:31 +0200 Subject: [PATCH 362/811] [webOS] Renderer: Fix render region --- .../HwDecRender/RendererStarfish.cpp | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp index b2231ff84f3a6..c065762a73dca 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererStarfish.cpp @@ -83,8 +83,28 @@ bool CRendererStarfish::Register() void CRendererStarfish::ManageRenderArea() { + // this hack is needed to get the 2D mode of a 3D movie going + RENDER_STEREO_MODE stereoMode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + if (stereoMode == RENDER_STEREO_MODE_MONO) + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_LEFT); + CBaseRenderer::ManageRenderArea(); + if (stereoMode == RENDER_STEREO_MODE_MONO) + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoView(RENDER_STEREO_VIEW_OFF); + + switch (stereoMode) + { + case RENDER_STEREO_MODE_SPLIT_HORIZONTAL: + m_destRect.y2 *= 2.0f; + break; + case RENDER_STEREO_MODE_SPLIT_VERTICAL: + m_destRect.x2 *= 2.0f; + break; + default: + break; + } + if ((m_exportedDestRect != m_destRect || m_exportedSourceRect != m_sourceRect) && !m_sourceRect.IsEmpty() && !m_destRect.IsEmpty()) { @@ -160,8 +180,6 @@ void CRendererStarfish::Update() { return; } - - ManageRenderArea(); } void CRendererStarfish::RenderUpdate( @@ -171,4 +189,6 @@ void CRendererStarfish::RenderUpdate( { return; } + + ManageRenderArea(); } From 68150705d243a3e9bc2712b7150a1e1e0563e805 Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Sat, 7 Oct 2023 09:56:46 +0200 Subject: [PATCH 363/811] [VideoPlayer] Updates audio/video queues for nowadays maximum bitrates --- xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp | 3 ++- xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp index 2c246da6204f5..9d4e16700bbfe 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerAudio.cpp @@ -61,7 +61,8 @@ CVideoPlayerAudio::CVideoPlayerAudio(CDVDClock* pClock, CDVDMessageQueue& parent m_prevskipped = false; m_maxspeedadjust = 0.0; - m_messageQueue.SetMaxDataSize(6 * 1024 * 1024); + // 18 MB allows max bitrate of 18 Mbit/s (TrueHD max peak) during 8 seconds + m_messageQueue.SetMaxDataSize(18 * 1024 * 1024); m_messageQueue.SetMaxTimeSize(8.0); m_disconAdjustTimeMs = processInfo.GetMaxPassthroughOffSyncDuration(); } diff --git a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp index ccfca71b7951c..e4f0e993de1df 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerVideo.cpp @@ -66,7 +66,9 @@ CVideoPlayerVideo::CVideoPlayerVideo(CDVDClock* pClock m_iLateFrames = 0; m_iDroppedRequest = 0; m_fForcedAspectRatio = 0; - m_messageQueue.SetMaxDataSize(40 * 1024 * 1024); + + // 128 MB allows max bitrate of 128 Mbit/s (e.g. UHD Blu-Ray) during 8 seconds + m_messageQueue.SetMaxDataSize(128 * 1024 * 1024); m_messageQueue.SetMaxTimeSize(8.0); m_iDroppedFrames = 0; From 5407b419f714f2e8cdc364ece77b23ad1cd41ab4 Mon Sep 17 00:00:00 2001 From: Jose Luis Marti Date: Sun, 8 Oct 2023 12:24:24 +0200 Subject: [PATCH 364/811] [Android][docs] Install NDK using Android SDK Manager --- docs/README.Android.md | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/docs/README.Android.md b/docs/README.Android.md index 5b3de45c88eda..f796f163cf61b 100644 --- a/docs/README.Android.md +++ b/docs/README.Android.md @@ -9,7 +9,7 @@ It should work if you're using macOS. If that is the case, read **[macOS specifi 1. **[Document conventions](#1-document-conventions)** 2. **[Install the required packages](#2-install-the-required-packages)** 3. **[Prerequisites](#3-prerequisites)** - 3.1. **[Extract Android SDK and NDK](#31-extract-android-sdk-and-ndk)** + 3.1. **[Extract Android SDK](#31-extract-android-sdk)** 3.2. **[Configure Android SDK](#32-configure-android-sdk)** 3.3. **[Create a key to sign debug APKs](#33-create-a-key-to-sign-debug-apks)** 4. **[Get the source code](#4-get-the-source-code)** @@ -70,9 +70,8 @@ Building Kodi for Android requires Android NDK revision 20b. For the SDK just us Kodi CI/CD platforms currently use r21e for build testing and releases, so we recommend using r21e for the most tested build experience * **[Android SDK](https://developer.android.com/studio/index.html)** (Look for `Get just the command line tools`) -* **[Android NDK](https://developer.android.com/ndk/downloads/index.html)** -### 3.1. Extract Android SDK and NDK +### 3.1. Extract Android SDK Create needed directories: ``` mkdir -p $HOME/android-tools/android-sdk-linux @@ -85,11 +84,6 @@ unzip $HOME/Downloads/commandlinetools-linux-6200805_latest.zip -d $HOME/android **NOTE:** Since we're using the latest SDK Command line tools available, filename can change over time. Adapt the `unzip` command accordingly. -Extract Android NDK: -``` -unzip $HOME/Downloads/android-ndk-r21e-linux-x86_64.zip -d $HOME/android-tools -``` - ### 3.2. Configure Android SDK Before Android SDK can be used, you need to accept the licenses and configure it: ``` @@ -97,7 +91,8 @@ cd $HOME/android-tools/android-sdk-linux/cmdline-tools/bin ./sdkmanager --sdk_root=$(pwd)/../.. --licenses ./sdkmanager --sdk_root=$(pwd)/../.. platform-tools ./sdkmanager --sdk_root=$(pwd)/../.. "platforms;android-33" -./sdkmanager --sdk_root=$(pwd)/../.. "build-tools;30.0.3" +./sdkmanager --sdk_root=$(pwd)/../.. "build-tools;33.0.1" +./sdkmanager --sdk_root=$(pwd)/../.. "ndk;21.4.7075529" ``` ### 3.3. Create a key to sign debug APKs @@ -131,22 +126,22 @@ cd $HOME/kodi/tools/depends Configure build for aarch64: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=aarch64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-ndk-r21e --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=aarch64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/21.4.7075529 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for arm: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=arm-linux-androideabi --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-ndk-r21e --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=arm-linux-androideabi --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/21.4.7075529 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for x86: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=i686-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-ndk-r21e --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=i686-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/21.4.7075529 --prefix=$HOME/android-tools/xbmc-depends ``` Or configure build for x86_64: ``` -./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=x86_64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-ndk-r21e --prefix=$HOME/android-tools/xbmc-depends +./configure --with-tarballs=$HOME/android-tools/xbmc-tarballs --host=x86_64-linux-android --with-sdk-path=$HOME/android-tools/android-sdk-linux --with-ndk-path=$HOME/android-tools/android-sdk-linux/ndk/21.4.7075529 --prefix=$HOME/android-tools/xbmc-depends ``` > **Note:** Android x86 and x86_64 are not maintained and are not 100% sure that everything works correctly! From bc62215d60a1b956474cd821e9bd08b4b48dafab Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Sun, 8 Oct 2023 16:18:18 +0200 Subject: [PATCH 365/811] VideoLayerBridgeDRMPRIME: Remove pitch check With some formats, like single plane and/or compressed ones, pitch can be 0, since it has no meaning there. In any case, drmModeAddFB2WithModifiers() will verify arguments, including pitch (if applicable), and reject invalid combinations. --- .../VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp index 233e6310bb823..34d1ab6235591 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/VideoLayerBridgeDRMPRIME.cpp @@ -105,7 +105,7 @@ bool CVideoLayerBridgeDRMPRIME::Map(CVideoBufferDRMPRIME* buffer) { int object = layer->planes[plane].object_index; uint32_t handle = buffer->m_handles[object]; - if (handle && layer->planes[plane].pitch) + if (handle) { handles[plane] = handle; pitches[plane] = layer->planes[plane].pitch; From ad013d9f349d9e3b60169118be648e354d3b5feb Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Sun, 8 Oct 2023 15:44:03 +0100 Subject: [PATCH 366/811] [Teletext] Only override the language code from header if it is actually valid --- xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp index 600e4333037b8..4e82c2d8c22aa 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayerTeletext.cpp @@ -361,8 +361,12 @@ void CDVDTeletextData::Process() b1 = dehamming[vtxt_row[9]]; if (b1 != 0xFF) { - pageinfo_thread->nationalvalid = 1; - pageinfo_thread->national = rev_lut[b1] & 0x07; + int countryCode = rev_lut[b1] & 0x07; + if (countryCode != NAT_DEFAULT) + { + pageinfo_thread->nationalvalid = 1; + pageinfo_thread->national = countryCode; + } } if (dehamming[vtxt_row[7]] & 0x08)// subtitle page @@ -583,10 +587,11 @@ void CDVDTeletextData::Process() { int t1 = CDVDTeletextTools::deh24(&vtxt_row[7-4]); pageinfo_thread->function = t1 & 0x0f; - if (!pageinfo_thread->nationalvalid) + int countryCode = (t1 >> 4) & 0x07; + if (!pageinfo_thread->nationalvalid && countryCode != NAT_DEFAULT) { pageinfo_thread->nationalvalid = 1; - pageinfo_thread->national = (t1>>4) & 0x07; + pageinfo_thread->national = countryCode; } } From 8905d673db80bac1312c925d545358e557384e21 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Sat, 7 Oct 2023 12:58:31 -0700 Subject: [PATCH 367/811] CPlatformFreebsd: use app params to set audio backend Signed-off-by: Lukas Rusak --- cmake/scripts/freebsd/ArchSetup.cmake | 2 ++ tools/Linux/kodi.sh.in | 2 ++ xbmc/platform/freebsd/PlatformFreebsd.cpp | 16 ++++++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cmake/scripts/freebsd/ArchSetup.cmake b/cmake/scripts/freebsd/ArchSetup.cmake index 87f4f0c9cc722..9e128df16262f 100644 --- a/cmake/scripts/freebsd/ArchSetup.cmake +++ b/cmake/scripts/freebsd/ArchSetup.cmake @@ -44,3 +44,5 @@ if(NOT USE_INTERNAL_LIBS) set(USE_INTERNAL_LIBS OFF) endif() endif() + +list(APPEND AUDIO_BACKENDS_LIST "oss") diff --git a/tools/Linux/kodi.sh.in b/tools/Linux/kodi.sh.in index 5dc1dd52600db..a9824f07b4415 100644 --- a/tools/Linux/kodi.sh.in +++ b/tools/Linux/kodi.sh.in @@ -182,6 +182,8 @@ if [ -n "${KODI_AE_SINK}" ]; then ENV_ARGS="--audio-backend=pulseaudio" elif [ "${KODI_AE_SINK}" = "ALSA" ]; then ENV_ARGS="--audio-backend=alsa" + elif [ "${KODI_AE_SINK}" = "OSS" ]; then + ENV_ARGS="--audio-backend=oss" elif [ "${KODI_AE_SINK}" = "SNDIO" ]; then ENV_ARGS="--audio-backend=sndio" elif [ "${KODI_AE_SINK}" = "ALSA+PULSE" ]; then diff --git a/xbmc/platform/freebsd/PlatformFreebsd.cpp b/xbmc/platform/freebsd/PlatformFreebsd.cpp index b65501b5221f6..c949a339cbebc 100644 --- a/xbmc/platform/freebsd/PlatformFreebsd.cpp +++ b/xbmc/platform/freebsd/PlatformFreebsd.cpp @@ -8,6 +8,8 @@ #include "PlatformFreebsd.h" +#include "ServiceBroker.h" +#include "application/AppParams.h" #include "utils/StringUtils.h" #include "platform/freebsd/OptionalsReg.h" @@ -79,27 +81,25 @@ bool CPlatformFreebsd::InitStageOne() CLinuxPowerSyscall::Register(); - std::string envSink; - if (getenv("KODI_AE_SINK")) - envSink = getenv("KODI_AE_SINK"); + std::string_view sink = CServiceBroker::GetAppParams()->GetAudioBackend(); - if (StringUtils::EqualsNoCase(envSink, "ALSA")) + if (sink == "alsa") { OPTIONALS::ALSARegister(); } - else if (StringUtils::EqualsNoCase(envSink, "PULSE")) + else if (sink == "pulseaudio") { OPTIONALS::PulseAudioRegister(); } - else if (StringUtils::EqualsNoCase(envSink, "OSS")) + else if (sink == "oss") { OPTIONALS::OSSRegister(); } - else if (StringUtils::EqualsNoCase(envSink, "SNDIO")) + else if (sink == "sndio") { OPTIONALS::SndioRegister(); } - else if (StringUtils::EqualsNoCase(envSink, "ALSA+PULSE")) + else if (sink == "alsa+pulseaudio") { OPTIONALS::ALSARegister(); OPTIONALS::PulseAudioRegister(); From cd61b3a6841b56bf03f7619544ae9ad43dba46ec Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:28:16 +0200 Subject: [PATCH 368/811] [favourites] Fix some favourites not detected, offered for adding to favourites, although already added. --- xbmc/favourites/FavouritesService.cpp | 79 +++++++++++++++++++++++---- xbmc/favourites/FavouritesService.h | 6 +- 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/xbmc/favourites/FavouritesService.cpp b/xbmc/favourites/FavouritesService.cpp index fac1cdb1908f0..cdc6843ec3756 100644 --- a/xbmc/favourites/FavouritesService.cpp +++ b/xbmc/favourites/FavouritesService.cpp @@ -13,6 +13,7 @@ #include "ServiceBroker.h" #include "Util.h" #include "favourites/FavouritesURL.h" +#include "input/WindowTranslator.h" #include "profiles/ProfileManager.h" #include "settings/SettingsComponent.h" #include "utils/ContentUtils.h" @@ -204,27 +205,24 @@ void CFavouritesService::OnUpdated() m_events.Publish(FavouritesUpdated{}); } -std::string CFavouritesService::GetFavouritesUrl(const CFileItem& item, int contextWindow) const -{ - return CFavouritesURL(item, contextWindow).GetURL(); -} - bool CFavouritesService::AddOrRemove(const CFileItem& item, int contextWindow) { - auto favUrl = GetFavouritesUrl(item, contextWindow); { std::unique_lock lock(m_criticalSection); - CFileItemPtr match = m_favourites.Get(favUrl); + + const std::shared_ptr match{GetFavourite(item, contextWindow)}; if (match) { // remove the item m_favourites.Remove(match.get()); } else - { // create our new favourite item - const CFileItemPtr favourite(std::make_shared(item.GetLabel())); + { + // create our new favourite item + const auto favourite{std::make_shared(item.GetLabel())}; if (item.GetLabel().empty()) favourite->SetLabel(CUtil::GetTitleFromPath(item.GetPath(), item.m_bIsFolder)); favourite->SetArt("thumb", ContentUtils::GetPreferredArtImage(item)); + const std::string favUrl{CFavouritesURL(item, contextWindow).GetURL()}; favourite->SetPath(favUrl); m_favourites.Add(favourite); } @@ -234,10 +232,69 @@ bool CFavouritesService::AddOrRemove(const CFileItem& item, int contextWindow) return true; } -bool CFavouritesService::IsFavourited(const CFileItem& item, int contextWindow) const +std::shared_ptr CFavouritesService::GetFavourite(const CFileItem& item, + int contextWindow) const { std::unique_lock lock(m_criticalSection); - return m_favourites.Contains(GetFavouritesUrl(item, contextWindow)); + + const CFavouritesURL favURL{item, contextWindow}; + const bool isVideoDb{URIUtils::IsVideoDb(favURL.GetTarget())}; + const bool isMusicDb{URIUtils::IsMusicDb(favURL.GetTarget())}; + + for (const auto& favItem : m_favourites) + { + const CFavouritesURL favItemURL{*favItem, contextWindow}; + + // Compare the whole target URLs + if (favItemURL.GetTarget() == item.GetPath()) + return favItem; + + // Compare the target URLs ignoring optional parameters + if (favItemURL.GetAction() == favURL.GetAction() && + (favItemURL.GetAction() != CFavouritesURL::Action::ACTIVATE_WINDOW || + favItemURL.GetWindowID() == favURL.GetWindowID())) + { + if (favItemURL.GetTarget() == favURL.GetTarget()) + return favItem; + + // Check videodb and musicdb paths. Might be different strings pointing to same resource! + // Example: "musicdb://recentlyaddedalbums/4711/" and "musicdb://recentlyplayedalbums/4711/", + // both pointing to same album with db id 4711. + if ((isVideoDb && URIUtils::IsVideoDb(favItemURL.GetTarget())) || + (isMusicDb && URIUtils::IsMusicDb(favItemURL.GetTarget()))) + { + const std::shared_ptr targetItem{ResolveFavourite(*favItem)}; + if (targetItem && targetItem->IsSamePath(&item)) + return favItem; + } + } + } + return {}; +} + +bool CFavouritesService::IsFavourited(const CFileItem& item, int contextWindow) const +{ + return (GetFavourite(item, contextWindow) != nullptr); +} + +std::shared_ptr CFavouritesService::ResolveFavourite(const CFileItem& item) const +{ + if (item.IsFavourite()) + { + const CFavouritesURL favURL{item.GetPath()}; + if (favURL.IsValid()) + { + auto targetItem{std::make_shared(favURL.GetTarget(), favURL.IsDir())}; + targetItem->LoadDetails(); + if (favURL.GetWindowID() != -1) + { + const std::string window{CWindowTranslator::TranslateWindow(favURL.GetWindowID())}; + targetItem->SetProperty("targetwindow", CVariant{window}); + } + return targetItem; + } + } + return {}; } void CFavouritesService::GetAll(CFileItemList& items) const diff --git a/xbmc/favourites/FavouritesService.h b/xbmc/favourites/FavouritesService.h index 5a1645955733b..a022e1e0c3182 100644 --- a/xbmc/favourites/FavouritesService.h +++ b/xbmc/favourites/FavouritesService.h @@ -12,6 +12,7 @@ #include "threads/CriticalSection.h" #include "utils/EventStream.h" +#include #include #include @@ -25,6 +26,9 @@ class CFavouritesService void ReInit(std::string userDataFolder); bool IsFavourited(const CFileItem& item, int contextWindow) const; + std::shared_ptr GetFavourite(const CFileItem& item, int contextWindow) const; + std::shared_ptr ResolveFavourite(const CFileItem& favItem) const; + void GetAll(CFileItemList& items) const; bool AddOrRemove(const CFileItem& item, int contextWindow); bool Save(const CFileItemList& items); @@ -46,11 +50,9 @@ class CFavouritesService void OnUpdated(); bool Persist(); - std::string GetFavouritesUrl(const CFileItem &item, int contextWindow) const; std::string m_userDataFolder; CFileItemList m_favourites; CEventSource m_events; mutable CCriticalSection m_criticalSection; }; - From 63c2246d2a7c0d238484fbde936746a381b20ab7 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Mon, 9 Oct 2023 05:57:13 +1000 Subject: [PATCH 369/811] [cmake][linux] remove unneccessary use set of USE_OPENGLES/USE_OPENGL These cmake variables arent used anywhere in the project. So just remove them --- cmake/scripts/linux/Install.cmake | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/cmake/scripts/linux/Install.cmake b/cmake/scripts/linux/Install.cmake index 8f0941cdc9ce0..addb040894e38 100644 --- a/cmake/scripts/linux/Install.cmake +++ b/cmake/scripts/linux/Install.cmake @@ -3,16 +3,6 @@ if(X_FOUND) else() set(USE_X11 0) endif() -if(TARGET OpenGL::GLES) - set(USE_OPENGL 1) -else() - set(USE_OPENGL 0) -endif() -if(TARGET OpenGL::GLES) - set(USE_OPENGLES 1) -else() - set(USE_OPENGLES 0) -endif() # CMake config set(APP_BINARY ${APP_NAME_LC}${APP_BINARY_SUFFIX}) From 00ce5a515268be6bcde92fabb1f5167c0b18ca38 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 9 Oct 2023 11:10:36 +0200 Subject: [PATCH 370/811] [video] Fix 'Play using' and external default player not working. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 2a81f0e48bb8e..bd00fbf010565 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -574,7 +574,7 @@ class CVideoSelectActionProcessor : public CVideoSelectActionProcessorBase return true; } // play the video - return m_window.OnClick(m_itemIndex); + return m_window.OnClick(m_itemIndex, m_player); } bool OnQueueSelected() override From 1cbf03c7f13370d9f5f409ef0f334ae96c1e7c87 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 8 Oct 2023 05:24:54 +0200 Subject: [PATCH 371/811] [favourites] CFavouritesService: Introduce cache for resolved favourite targets. --- xbmc/favourites/FavouritesService.cpp | 14 +++++++++++++- xbmc/favourites/FavouritesService.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/xbmc/favourites/FavouritesService.cpp b/xbmc/favourites/FavouritesService.cpp index cdc6843ec3756..ccfe879d1b61a 100644 --- a/xbmc/favourites/FavouritesService.cpp +++ b/xbmc/favourites/FavouritesService.cpp @@ -149,6 +149,7 @@ void CFavouritesService::ReInit(std::string userDataFolder) { m_userDataFolder = std::move(userDataFolder); m_favourites.Clear(); + m_targets.clear(); m_favourites.SetContent("favourites"); std::string favourites = "special://xbmc/system/favourites.xml"; @@ -193,6 +194,7 @@ bool CFavouritesService::Save(const CFileItemList& items) { std::unique_lock lock(m_criticalSection); m_favourites.Clear(); + m_targets.clear(); m_favourites.Copy(items); Persist(); } @@ -212,7 +214,12 @@ bool CFavouritesService::AddOrRemove(const CFileItem& item, int contextWindow) const std::shared_ptr match{GetFavourite(item, contextWindow)}; if (match) - { // remove the item + { + // remove the item + const auto it = m_targets.find(match->GetPath()); + if (it != m_targets.end()) + m_targets.erase(it); + m_favourites.Remove(match.get()); } else @@ -281,6 +288,10 @@ std::shared_ptr CFavouritesService::ResolveFavourite(const CFileItem& { if (item.IsFavourite()) { + const auto it = m_targets.find(item.GetPath()); + if (it != m_targets.end()) + return (*it).second; + const CFavouritesURL favURL{item.GetPath()}; if (favURL.IsValid()) { @@ -291,6 +302,7 @@ std::shared_ptr CFavouritesService::ResolveFavourite(const CFileItem& const std::string window{CWindowTranslator::TranslateWindow(favURL.GetWindowID())}; targetItem->SetProperty("targetwindow", CVariant{window}); } + m_targets.insert({item.GetPath(), targetItem}); return targetItem; } } diff --git a/xbmc/favourites/FavouritesService.h b/xbmc/favourites/FavouritesService.h index a022e1e0c3182..65f56ff0c9243 100644 --- a/xbmc/favourites/FavouritesService.h +++ b/xbmc/favourites/FavouritesService.h @@ -14,6 +14,7 @@ #include #include +#include #include class CFavouritesService @@ -53,6 +54,7 @@ class CFavouritesService std::string m_userDataFolder; CFileItemList m_favourites; + mutable std::unordered_map> m_targets; CEventSource m_events; mutable CCriticalSection m_criticalSection; }; From 8c2807d39e54dc5d3d13ecfdaa117702c5d9c23c Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 6 Oct 2023 19:06:06 +0200 Subject: [PATCH 372/811] [favourites] clang-format ContextMenus.(cpp|h). --- xbmc/favourites/ContextMenus.cpp | 92 +++++++++++++++----------------- xbmc/favourites/ContextMenus.h | 2 +- 2 files changed, 45 insertions(+), 49 deletions(-) diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp index 821841f7a7580..32e71273fbbac 100644 --- a/xbmc/favourites/ContextMenus.cpp +++ b/xbmc/favourites/ContextMenus.cpp @@ -16,65 +16,61 @@ namespace CONTEXTMENU { - bool CFavouriteContextMenuAction::IsVisible(const CFileItem& item) const - { - return URIUtils::IsProtocol(item.GetPath(), "favourites"); - } +bool CFavouriteContextMenuAction::IsVisible(const CFileItem& item) const +{ + return URIUtils::IsProtocol(item.GetPath(), "favourites"); +} - bool CFavouriteContextMenuAction::Execute(const std::shared_ptr& item) const +bool CFavouriteContextMenuAction::Execute(const std::shared_ptr& item) const +{ + CFileItemList items; + CServiceBroker::GetFavouritesService().GetAll(items); + for (const auto& favourite : items) { - CFileItemList items; - CServiceBroker::GetFavouritesService().GetAll(items); - for (const auto& favourite : items) + if (favourite->GetPath() == item->GetPath()) { - if (favourite->GetPath() == item->GetPath()) - { - if (DoExecute(items, favourite)) - return CServiceBroker::GetFavouritesService().Save(items); - } + if (DoExecute(items, favourite)) + return CServiceBroker::GetFavouritesService().Save(items); } - return false; } + return false; +} - bool CMoveUpFavourite::DoExecute(CFileItemList& items, - const std::shared_ptr& item) const - { - return FAVOURITES_UTILS::MoveItem(items, item, -1); - } +bool CMoveUpFavourite::DoExecute(CFileItemList& items, const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::MoveItem(items, item, -1); +} - bool CMoveUpFavourite::IsVisible(const CFileItem& item) const - { - return CFavouriteContextMenuAction::IsVisible(item) && - FAVOURITES_UTILS::ShouldEnableMoveItems(); - } +bool CMoveUpFavourite::IsVisible(const CFileItem& item) const +{ + return CFavouriteContextMenuAction::IsVisible(item) && FAVOURITES_UTILS::ShouldEnableMoveItems(); +} - bool CMoveDownFavourite::DoExecute(CFileItemList& items, - const std::shared_ptr& item) const - { - return FAVOURITES_UTILS::MoveItem(items, item, +1); - } +bool CMoveDownFavourite::DoExecute(CFileItemList& items, + const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::MoveItem(items, item, +1); +} - bool CMoveDownFavourite::IsVisible(const CFileItem& item) const - { - return CFavouriteContextMenuAction::IsVisible(item) && - FAVOURITES_UTILS::ShouldEnableMoveItems(); - } +bool CMoveDownFavourite::IsVisible(const CFileItem& item) const +{ + return CFavouriteContextMenuAction::IsVisible(item) && FAVOURITES_UTILS::ShouldEnableMoveItems(); +} - bool CRemoveFavourite::DoExecute(CFileItemList& items, - const std::shared_ptr& item) const - { - return FAVOURITES_UTILS::RemoveItem(items, item); - } +bool CRemoveFavourite::DoExecute(CFileItemList& items, const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::RemoveItem(items, item); +} - bool CRenameFavourite::DoExecute(CFileItemList&, const std::shared_ptr& item) const - { - return FAVOURITES_UTILS::ChooseAndSetNewName(*item); - } +bool CRenameFavourite::DoExecute(CFileItemList&, const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::ChooseAndSetNewName(*item); +} - bool CChooseThumbnailForFavourite::DoExecute(CFileItemList&, - const std::shared_ptr& item) const - { - return FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*item); - } +bool CChooseThumbnailForFavourite::DoExecute(CFileItemList&, + const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*item); +} } // namespace CONTEXTMENU diff --git a/xbmc/favourites/ContextMenus.h b/xbmc/favourites/ContextMenus.h index fe1edc8629676..c0f94953c792b 100644 --- a/xbmc/favourites/ContextMenus.h +++ b/xbmc/favourites/ContextMenus.h @@ -73,4 +73,4 @@ class CChooseThumbnailForFavourite : public CFavouriteContextMenuAction bool DoExecute(CFileItemList& items, const std::shared_ptr& item) const override; }; -} +} // namespace CONTEXTMENU From 6a5a819ac65881f5956273d3748c16f301857fc8 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Mon, 9 Oct 2023 12:33:15 +0100 Subject: [PATCH 373/811] [GUIDialogTeletext] Mark dirty if data has changed in the decoder (subtitles or time string) --- xbmc/video/Teletext.cpp | 33 ++++++++++++++---------- xbmc/video/Teletext.h | 7 +++++ xbmc/video/dialogs/GUIDialogTeletext.cpp | 4 +++ 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/xbmc/video/Teletext.cpp b/xbmc/video/Teletext.cpp index 1a638296a4e0f..aa64ec1c9662c 100644 --- a/xbmc/video/Teletext.cpp +++ b/xbmc/video/Teletext.cpp @@ -447,6 +447,25 @@ CTeletextDecoder::CTeletextDecoder() CTeletextDecoder::~CTeletextDecoder() = default; +bool CTeletextDecoder::Changed() +{ + std::unique_lock lock(m_txtCache->m_critSection); + if (IsSubtitlePage(m_txtCache->Page)) + { + m_updateTexture = true; + return true; + } + + /* Update on every changed second */ + if (m_txtCache->TimeString[7] != prevTimeSec) + { + prevTimeSec = m_txtCache->TimeString[7]; + m_updateTexture = true; + return true; + } + return false; +} + bool CTeletextDecoder::HandleAction(const CAction &action) { if (m_txtCache == NULL) @@ -1321,20 +1340,6 @@ void CTeletextDecoder::RenderPage() SetPosX(33+i); } } - - if (!IsSubtitlePage(m_txtCache->Page)) - { - /* Update on every changed second */ - if (m_txtCache->TimeString[7] != prevTimeSec) - { - prevTimeSec = m_txtCache->TimeString[7]; - m_updateTexture = true; - } - } - else - { - m_updateTexture = true; - } } DoFlashing(StartRow); m_txtCache->NationalSubset = national_subset_bak; diff --git a/xbmc/video/Teletext.h b/xbmc/video/Teletext.h index 105d8424cc961..b0a8865a17c56 100644 --- a/xbmc/video/Teletext.h +++ b/xbmc/video/Teletext.h @@ -46,6 +46,13 @@ class CTeletextDecoder } int GetHeight() { return m_RenderInfo.Height; } int GetWidth() { return m_RenderInfo.Width; } + + /*! + \brief Checks if the data in the decoder has changed + \return true if the data in the decoder has changed, false otherwise + */ + bool Changed(); + bool InitDecoder(); void EndDecoder(); void RenderPage(); diff --git a/xbmc/video/dialogs/GUIDialogTeletext.cpp b/xbmc/video/dialogs/GUIDialogTeletext.cpp index 01e86cb57d5e1..13c706b12f93c 100644 --- a/xbmc/video/dialogs/GUIDialogTeletext.cpp +++ b/xbmc/video/dialogs/GUIDialogTeletext.cpp @@ -75,6 +75,10 @@ bool CGUIDialogTeletext::OnMessage(CGUIMessage& message) void CGUIDialogTeletext::Process(unsigned int currentTime, CDirtyRegionList &dirtyregions) { + if (m_TextDecoder.Changed()) + { + MarkDirtyRegion(); + } CGUIDialog::Process(currentTime, dirtyregions); m_renderRegion = m_vertCoords; } From f3b45b6f83709df49207425b8f37bcc69bbbad59 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 6 Oct 2023 20:28:58 +0200 Subject: [PATCH 374/811] [favourites] Add selected items of the target to favourite's context menu (play, resume, info, target's complete context menu). --- .../resources/strings.po | 6 + cmake/treedata/common/subdirs.txt | 1 + xbmc/ContextMenuManager.cpp | 36 +++++ xbmc/ContextMenuManager.h | 9 +- xbmc/ContextMenus.cpp | 3 + xbmc/FileItem.cpp | 52 +++++++ xbmc/favourites/ContextMenus.cpp | 139 +++++++++++++++++- xbmc/favourites/ContextMenus.h | 40 +++++ xbmc/favourites/FavouritesUtils.cpp | 31 ++++ xbmc/favourites/FavouritesUtils.h | 5 + xbmc/favourites/GUIWindowFavourites.cpp | 50 ++----- xbmc/listproviders/DirectoryProvider.cpp | 33 +---- xbmc/utils/guilib/CMakeLists.txt | 5 + xbmc/utils/guilib/GUIContentUtils.cpp | 57 +++++++ xbmc/utils/guilib/GUIContentUtils.h | 33 +++++ 15 files changed, 427 insertions(+), 73 deletions(-) create mode 100644 xbmc/utils/guilib/CMakeLists.txt create mode 100644 xbmc/utils/guilib/GUIContentUtils.cpp create mode 100644 xbmc/utils/guilib/GUIContentUtils.h diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index 5db113c3fde3a..f593cc8c1daca 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -953,6 +953,7 @@ msgstr "" #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml #: xbmc/dialogs/GUIDialogPlayEject.cpp +#: xbmc/favourites/ContextMenus.cpp #: xbmc/games/windows/GUIWindowGames.cpp #: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.cpp @@ -5722,6 +5723,7 @@ msgstr "" #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml #: xbmc/Autorun.cpp +#: xbmc/favourites/ContextMenus.cpp #: xbmc/pvr/PVRGUIActionsPlayback.cpp #: xbmc/video/ContextMenus.cpp #: xbmc/video/guilib/VideoSelectActionProcessor.cpp @@ -9637,6 +9639,7 @@ msgstr "" #. generic 'information' label used in different places, like labels for message box headers #: xbmc/event/windows/GUIWindowEventLog.cpp +#: xbmc/favourites/ContextMenus.cpp #: xbmc/pvr/dialogs/GUIDialogPVRChannelManager.cpp #: xbmc/pvr/dialogs/GUIDialogPVRGroupManager.cpp #: xbmc/pvr/dialogs/GUIDialogPVRGuideInfo.cpp @@ -15348,6 +15351,7 @@ msgid "Show information" msgstr "" #. Label to denote that there is something 'more' +#. xbmc/favourites/ContextMenus.h #: xbmc/listproviders/DirectoryProvider.cpp #: xbmc/video/guilib/VideoSelectActionProcessor.cpp #: system/settings/settings.xml @@ -21937,8 +21941,10 @@ msgctxt "#37014" msgid "Last used profile" msgstr "" +#. Label used in different places to denote the function to open a folder to list its content #: addons/skin.estuary/xml/SkinSettings.xml #: addons/skin.estuary/xml/Variables.xml +#: xbmc/favourites/ContextMenus.h #: xbmc/music/ContextMenus.h #: xbmc/windows/GUIMediaWindow.cpp msgctxt "#37015" diff --git a/cmake/treedata/common/subdirs.txt b/cmake/treedata/common/subdirs.txt index 3d6e6ea0b4249..67b44f92d12b2 100644 --- a/cmake/treedata/common/subdirs.txt +++ b/cmake/treedata/common/subdirs.txt @@ -41,6 +41,7 @@ xbmc/speech speech xbmc/storage storage xbmc/threads threads xbmc/utils utils +xbmc/utils/guilib utils_guilib xbmc/view view xbmc/weather weather xbmc/windowing windowing diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index 75fa2bb5140b9..950536e621d56 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -28,6 +28,7 @@ #include "utils/log.h" #include "video/ContextMenus.h" +#include #include #include @@ -89,12 +90,17 @@ void CContextMenuManager::Init() std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), + std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), }; ReloadAddonItems(); @@ -200,6 +206,15 @@ bool CContextMenuManager::IsVisible( return menuItem.IsVisible(fileItem); } +bool CContextMenuManager::HasItems(const CFileItem& fileItem) const +{ + std::unique_lock lock(m_criticalSection); + return std::any_of(m_items.cbegin(), m_items.cend(), + [&fileItem](const std::shared_ptr& menu) { + return menu->IsVisible(fileItem); + }); +} + ContextMenuView CContextMenuManager::GetItems(const CFileItem& fileItem, const CContextMenuItem& root /*= MAIN*/) const { ContextMenuView result; @@ -213,6 +228,14 @@ ContextMenuView CContextMenuManager::GetItems(const CFileItem& fileItem, const C return result; } +bool CContextMenuManager::HasAddonItems(const CFileItem& fileItem) const +{ + std::unique_lock lock(m_criticalSection); + return std::any_of( + m_addonItems.cbegin(), m_addonItems.cend(), + [&fileItem](const CContextMenuItem& menu) { return menu.IsVisible(fileItem); }); +} + ContextMenuView CContextMenuManager::GetAddonItems(const CFileItem& fileItem, const CContextMenuItem& root /*= MAIN*/) const { ContextMenuView result; @@ -235,6 +258,19 @@ ContextMenuView CContextMenuManager::GetAddonItems(const CFileItem& fileItem, co return result; } +bool CONTEXTMENU::HasAnyMenuItemsFor(const std::shared_ptr& fileItem) +{ + if (!fileItem) + return false; + + if (fileItem->HasProperty("contextmenulabel(0)")) + return true; + + const CContextMenuManager& contextMenuManager = CServiceBroker::GetContextMenuManager(); + + return (contextMenuManager.HasItems(*fileItem) || contextMenuManager.HasAddonItems(*fileItem)); +} + bool CONTEXTMENU::ShowFor(const std::shared_ptr& fileItem, const CContextMenuItem& root) { if (!fileItem) diff --git a/xbmc/ContextMenuManager.h b/xbmc/ContextMenuManager.h index 13d92bdf20ed5..2a4388caf2da4 100644 --- a/xbmc/ContextMenuManager.h +++ b/xbmc/ContextMenuManager.h @@ -40,8 +40,10 @@ class CContextMenuManager void Init(); void Deinit(); + bool HasItems(const CFileItem& fileItem) const; ContextMenuView GetItems(const CFileItem& item, const CContextMenuItem& root = MAIN) const; + bool HasAddonItems(const CFileItem& fileItem) const; ContextMenuView GetAddonItems(const CFileItem& item, const CContextMenuItem& root = MAIN) const; private: @@ -67,7 +69,12 @@ class CContextMenuManager namespace CONTEXTMENU { - /*! +/*! + * Checks whether any context menu items are available for a file item. + * */ +bool HasAnyMenuItemsFor(const std::shared_ptr& fileItem); + +/*! * Starts the context menu loop for a file item. * */ bool ShowFor(const std::shared_ptr& fileItem, diff --git a/xbmc/ContextMenus.cpp b/xbmc/ContextMenus.cpp index 0f6534cfb617a..215545eda33e4 100644 --- a/xbmc/ContextMenus.cpp +++ b/xbmc/ContextMenus.cpp @@ -78,6 +78,9 @@ std::string CAddRemoveFavourite::GetLabel(const CFileItem& item) const bool CAddRemoveFavourite::IsVisible(const CFileItem& item) const { + if (item.GetProperty("hide_add_remove_favourite").asBoolean()) + return false; + return (!item.GetPath().empty() && !item.IsParentFolder() && !item.IsPath("add") && !item.IsPath("newplaylist://") && !URIUtils::IsProtocol(item.GetPath(), "favourites") && !URIUtils::IsProtocol(item.GetPath(), "newsmartplaylist") && diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index ebe38fd340cec..30ef905c5f8da 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3791,6 +3791,58 @@ bool CFileItem::LoadDetails() return false; } + if (IsAudio()) + { + return LoadMusicTag(); + } + + if (IsMusicDb()) + { + if (HasMusicInfoTag()) + return true; + + CMusicDatabase db; + if (!db.Open()) + { + CLog::LogF(LOGERROR, "Error opening music database"); + return false; + } + + MUSICDATABASEDIRECTORY::CQueryParams params; + MUSICDATABASEDIRECTORY::CDirectoryNode::GetDatabaseInfo(GetPath(), params); + + if (params.GetSongId() >= 0) + { + CSong song; + if (db.GetSong(params.GetSongId(), song)) + { + GetMusicInfoTag()->SetSong(song); + return true; + } + } + else if (params.GetAlbumId() >= 0) + { + m_bIsFolder = true; + CAlbum album; + if (db.GetAlbum(params.GetAlbumId(), album, false)) + { + GetMusicInfoTag()->SetAlbum(album); + return true; + } + } + else if (params.GetArtistId() >= 0) + { + m_bIsFolder = true; + CArtist artist; + if (db.GetArtist(params.GetArtistId(), artist, false)) + { + GetMusicInfoTag()->SetArtist(artist); + return true; + } + } + return false; + } + //! @todo add support for other types on demand. CLog::LogF(LOGDEBUG, "Unsupported item type (path={})", GetPath()); return false; diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp index 32e71273fbbac..ee8a1aaa64f63 100644 --- a/xbmc/favourites/ContextMenus.cpp +++ b/xbmc/favourites/ContextMenus.cpp @@ -8,14 +8,20 @@ #include "ContextMenus.h" +#include "ContextMenuManager.h" #include "FileItem.h" #include "ServiceBroker.h" #include "favourites/FavouritesService.h" +#include "favourites/FavouritesURL.h" #include "favourites/FavouritesUtils.h" +#include "guilib/LocalizeStrings.h" #include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/guilib/GUIContentUtils.h" +#include "video/VideoUtils.h" + +using namespace CONTEXTMENU; -namespace CONTEXTMENU -{ bool CFavouriteContextMenuAction::IsVisible(const CFileItem& item) const { return URIUtils::IsProtocol(item.GetPath(), "favourites"); @@ -73,4 +79,131 @@ bool CChooseThumbnailForFavourite::DoExecute(CFileItemList&, return FAVOURITES_UTILS::ChooseAndSetNewThumbnail(*item); } -} // namespace CONTEXTMENU +namespace +{ +std::shared_ptr ResolveFavouriteItem(const CFileItem& item) +{ + const std::shared_ptr targetItem{ + CServiceBroker::GetFavouritesService().ResolveFavourite(item)}; + if (targetItem) + targetItem->SetProperty("hide_add_remove_favourite", CVariant{true}); + + return targetItem; +} + +bool IsPlayMediaFavourite(const CFileItem& item) +{ + if (item.IsFavourite()) + { + const CFavouritesURL favURL{item, -1}; + if (favURL.IsValid()) + return favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA; + } + return false; +} + +bool IsActivateWindowFavourite(const CFileItem& item) +{ + if (item.IsFavourite()) + { + const CFavouritesURL favURL{item, -1}; + if (favURL.IsValid()) + return favURL.GetAction() == CFavouritesURL::Action::ACTIVATE_WINDOW; + } + return false; +} +} // unnamed namespace + +bool CFavouritesTargetBrowse::IsVisible(const CFileItem& item) const +{ + return IsActivateWindowFavourite(item); +} + +bool CFavouritesTargetBrowse::Execute(const std::shared_ptr& item) const +{ + return FAVOURITES_UTILS::ExecuteAction({*item, -1}); +} + +std::string CFavouritesTargetResume::GetLabel(const CFileItem& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(item)}; + if (targetItem) + return VIDEO_UTILS::GetResumeString(*targetItem); + + return {}; +} + +bool CFavouritesTargetResume::IsVisible(const CFileItem& item) const +{ + if (IsPlayMediaFavourite(item)) + { + const std::shared_ptr targetItem{ResolveFavouriteItem(item)}; + if (targetItem) + return VIDEO_UTILS::GetItemResumeInformation(*targetItem).isResumable; + } + return false; +} + +bool CFavouritesTargetResume::Execute(const std::shared_ptr& item) const +{ + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *item, "resume"}); + return true; +} + +std::string CFavouritesTargetPlay::GetLabel(const CFileItem& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(item)}; + if (targetItem && VIDEO_UTILS::GetItemResumeInformation(*targetItem).isResumable) + return g_localizeStrings.Get(12021); // Play from beginning + + return g_localizeStrings.Get(208); // Play +} + +bool CFavouritesTargetPlay::IsVisible(const CFileItem& item) const +{ + return IsPlayMediaFavourite(item); +} + +bool CFavouritesTargetPlay::Execute(const std::shared_ptr& item) const +{ + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *item, "noresume"}); + return true; +} + +bool CFavouritesTargetInfo::IsVisible(const CFileItem& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(item)}; + if (targetItem) + return UTILS::GUILIB::CGUIContentUtils::HasInfoForItem(*targetItem); + + return false; +} + +bool CFavouritesTargetInfo::Execute(const std::shared_ptr& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(*item)}; + if (targetItem) + return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*targetItem); + + return false; +} + +bool CFavouritesTargetContextMenu::IsVisible(const CFileItem& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(item)}; + if (targetItem) + return CONTEXTMENU::HasAnyMenuItemsFor(targetItem); + + return false; +} + +bool CFavouritesTargetContextMenu::Execute(const std::shared_ptr& item) const +{ + const std::shared_ptr targetItem{ResolveFavouriteItem(*item)}; + if (targetItem) + { + CONTEXTMENU::ShowFor(targetItem); + return true; + } + return false; +} diff --git a/xbmc/favourites/ContextMenus.h b/xbmc/favourites/ContextMenus.h index c0f94953c792b..52f505f90dc73 100644 --- a/xbmc/favourites/ContextMenus.h +++ b/xbmc/favourites/ContextMenus.h @@ -73,4 +73,44 @@ class CChooseThumbnailForFavourite : public CFavouriteContextMenuAction bool DoExecute(CFileItemList& items, const std::shared_ptr& item) const override; }; +class CFavouritesTargetBrowse : public CStaticContextMenuAction +{ +public: + explicit CFavouritesTargetBrowse() : CStaticContextMenuAction(37015) {} // Browse into + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& item) const override; +}; + +class CFavouritesTargetResume : public IContextMenuItem +{ +public: + std::string GetLabel(const CFileItem& item) const override; + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& item) const override; +}; + +class CFavouritesTargetPlay : public IContextMenuItem +{ +public: + std::string GetLabel(const CFileItem& item) const override; + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& item) const override; +}; + +class CFavouritesTargetInfo : public CStaticContextMenuAction +{ +public: + explicit CFavouritesTargetInfo() : CStaticContextMenuAction(19033) {} // Information + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& item) const override; +}; + +class CFavouritesTargetContextMenu : public CStaticContextMenuAction +{ +public: + explicit CFavouritesTargetContextMenu() : CStaticContextMenuAction(22082) {} // More... + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& item) const override; +}; + } // namespace CONTEXTMENU diff --git a/xbmc/favourites/FavouritesUtils.cpp b/xbmc/favourites/FavouritesUtils.cpp index 8ccfca9d15842..5c2bdb3dd8bce 100644 --- a/xbmc/favourites/FavouritesUtils.cpp +++ b/xbmc/favourites/FavouritesUtils.cpp @@ -11,12 +11,15 @@ #include "FileItem.h" #include "ServiceBroker.h" #include "dialogs/GUIDialogFileBrowser.h" +#include "favourites/FavouritesURL.h" #include "favourites/GUIWindowFavourites.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" +#include "guilib/GUIMessage.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "storage/MediaManager.h" +#include "utils/ExecString.h" #include "utils/Variant.h" #include "view/GUIViewState.h" @@ -121,4 +124,32 @@ bool ShouldEnableMoveItems() return true; } +namespace +{ +bool ExecuteAction(const std::string& execString) +{ + if (!execString.empty()) + { + CGUIMessage message(GUI_MSG_EXECUTE, 0, 0); + message.SetStringParam(execString); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); + return true; + } + return false; +} +} // unnamed namespace + +bool ExecuteAction(const CExecString& execString) +{ + return ExecuteAction(execString.GetExecString()); +} + +bool ExecuteAction(const CFavouritesURL& favURL) +{ + if (favURL.IsValid()) + return ExecuteAction(favURL.GetExecString()); + + return false; +} + } // namespace FAVOURITES_UTILS diff --git a/xbmc/favourites/FavouritesUtils.h b/xbmc/favourites/FavouritesUtils.h index 0388b4de8598f..b57839dbbbc12 100644 --- a/xbmc/favourites/FavouritesUtils.h +++ b/xbmc/favourites/FavouritesUtils.h @@ -10,6 +10,8 @@ #include +class CExecString; +class CFavouritesURL; class CFileItem; class CFileItemList; @@ -21,4 +23,7 @@ bool MoveItem(CFileItemList& items, const std::shared_ptr& item, int bool RemoveItem(CFileItemList& items, const std::shared_ptr& item); bool ShouldEnableMoveItems(); +bool ExecuteAction(const CExecString& execString); +bool ExecuteAction(const CFavouritesURL& favURL); + } // namespace FAVOURITES_UTILS diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index 2715f32bd2f1a..15b8b23afa5b5 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -19,12 +19,10 @@ #include "input/actions/Action.h" #include "input/actions/ActionIDs.h" #include "messaging/ApplicationMessenger.h" -#include "pvr/PVRManager.h" -#include "pvr/guilib/PVRGUIActionsUtils.h" #include "utils/PlayerUtils.h" #include "utils/StringUtils.h" +#include "utils/guilib/GUIContentUtils.h" #include "video/VideoUtils.h" -#include "video/dialogs/GUIDialogVideoInfo.h" #include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" @@ -49,23 +47,6 @@ void CGUIWindowFavourites::OnFavouritesEvent(const CFavouritesService::Favourite namespace { -bool ExecuteAction(const std::string& execute) -{ - if (!execute.empty()) - { - CGUIMessage message(GUI_MSG_EXECUTE, 0, 0); - message.SetStringParam(execute); - CServiceBroker::GetGUI()->GetWindowManager().SendMessage(message); - return true; - } - return false; -} - -bool ExecuteAction(const CExecString& execute) -{ - return ExecuteAction(execute.GetExecString()); -} - class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProcessorBase { public: @@ -75,41 +56,32 @@ class CVideoSelectActionProcessor : public VIDEO::GUILIB::CVideoSelectActionProc bool OnPlayPartSelected(unsigned int part) override { // part numbers are 1-based - ExecuteAction({"PlayMedia", m_item, StringUtils::Format("playoffset={}", part - 1)}); + FAVOURITES_UTILS::ExecuteAction( + {"PlayMedia", m_item, StringUtils::Format("playoffset={}", part - 1)}); return true; } bool OnResumeSelected() override { - ExecuteAction({"PlayMedia", m_item, "resume"}); + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", m_item, "resume"}); return true; } bool OnPlaySelected() override { - ExecuteAction({"PlayMedia", m_item, "noresume"}); + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", m_item, "noresume"}); return true; } bool OnQueueSelected() override { - ExecuteAction({"QueueMedia", m_item, ""}); + FAVOURITES_UTILS::ExecuteAction({"QueueMedia", m_item, ""}); return true; } bool OnInfoSelected() override { - if (m_item.IsPVR()) - { - CServiceBroker::GetPVRManager().Get().OnInfo(m_item); - return true; - } - else if (m_item.HasVideoInfoTag()) - { - CGUIDialogVideoInfo::ShowFor(m_item); - return true; - } - return false; + return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(m_item); } bool OnMoreSelected() override @@ -127,13 +99,13 @@ class CVideoPlayActionProcessor : public VIDEO::GUILIB::CVideoPlayActionProcesso protected: bool OnResumeSelected() override { - ExecuteAction({"PlayMedia", m_item, "resume"}); + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", m_item, "resume"}); return true; } bool OnPlaySelected() override { - ExecuteAction({"PlayMedia", m_item, "noresume"}); + FAVOURITES_UTILS::ExecuteAction({"PlayMedia", m_item, "noresume"}); return true; } }; @@ -162,7 +134,7 @@ bool CGUIWindowFavourites::OnSelect(int item) } // exec the execute string for the original (!) item - return ExecuteAction(favURL.GetExecString()); + return FAVOURITES_UTILS::ExecuteAction(favURL); } bool CGUIWindowFavourites::OnAction(const CAction& action) @@ -197,7 +169,7 @@ bool CGUIWindowFavourites::OnAction(const CAction& action) target = CFavouritesURL{CFavouritesURL::Action::PLAY_MEDIA, {StringUtils::Paramify(item.GetPath())}}; } - return ExecuteAction(target.GetExecString()); + return FAVOURITES_UTILS::ExecuteAction(target); } return false; } diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 3f96e13cbc8b8..5f22353667494 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -12,7 +12,6 @@ #include "FileItem.h" #include "ServiceBroker.h" #include "addons/AddonManager.h" -#include "addons/gui/GUIDialogAddonInfo.h" #include "favourites/FavouritesService.h" #include "favourites/FavouritesURL.h" #include "filesystem/Directory.h" @@ -21,25 +20,24 @@ #include "guilib/LocalizeStrings.h" #include "interfaces/AnnouncementManager.h" #include "music/MusicThumbLoader.h" -#include "music/dialogs/GUIDialogMusicInfo.h" #include "pictures/PictureThumbLoader.h" #include "pvr/PVRManager.h" #include "pvr/PVRThumbLoader.h" -#include "pvr/guilib/PVRGUIActionsUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "utils/ExecString.h" #include "utils/JobManager.h" #include "utils/PlayerUtils.h" #include "utils/SortUtils.h" +#include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/Variant.h" #include "utils/XMLUtils.h" +#include "utils/guilib/GUIContentUtils.h" #include "utils/log.h" #include "video/VideoInfoTag.h" #include "video/VideoThumbLoader.h" #include "video/VideoUtils.h" -#include "video/dialogs/GUIDialogVideoInfo.h" #include "video/guilib/VideoPlayActionProcessor.h" #include "video/guilib/VideoSelectActionProcessor.h" @@ -628,32 +626,7 @@ bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) bool CDirectoryProvider::OnInfo(const std::shared_ptr& fileItem) { - if (fileItem->HasAddonInfo()) - { - return CGUIDialogAddonInfo::ShowForItem(fileItem); - } - else if (fileItem->IsPVR()) - { - return CServiceBroker::GetPVRManager().Get().OnInfo(*fileItem); - } - else if (fileItem->HasVideoInfoTag()) - { - auto mediaType = fileItem->GetVideoInfoTag()->m_type; - if (mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || - mediaType == MediaTypeSeason || mediaType == MediaTypeEpisode || - mediaType == MediaTypeVideo || mediaType == MediaTypeVideoCollection || - mediaType == MediaTypeMusicVideo) - { - CGUIDialogVideoInfo::ShowFor(*fileItem); - return true; - } - } - else if (fileItem->HasMusicInfoTag()) - { - CGUIDialogMusicInfo::ShowFor(fileItem.get()); - return true; - } - return false; + return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*fileItem); } bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) diff --git a/xbmc/utils/guilib/CMakeLists.txt b/xbmc/utils/guilib/CMakeLists.txt new file mode 100644 index 0000000000000..84e060b1fb3b2 --- /dev/null +++ b/xbmc/utils/guilib/CMakeLists.txt @@ -0,0 +1,5 @@ +set(SOURCES GUIContentUtils.cpp) + +set(HEADERS GUIContentUtils.h) + +core_add_library(utils_guilib) diff --git a/xbmc/utils/guilib/GUIContentUtils.cpp b/xbmc/utils/guilib/GUIContentUtils.cpp new file mode 100644 index 0000000000000..0c2c6ef12ea47 --- /dev/null +++ b/xbmc/utils/guilib/GUIContentUtils.cpp @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "GUIContentUtils.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "addons/gui/GUIDialogAddonInfo.h" +#include "music/dialogs/GUIDialogMusicInfo.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsUtils.h" +#include "video/VideoInfoTag.h" +#include "video/dialogs/GUIDialogVideoInfo.h" + +using namespace UTILS::GUILIB; + +bool CGUIContentUtils::HasInfoForItem(const CFileItem& item) +{ + if (item.HasVideoInfoTag() && !item.HasPVRRecordingInfoTag()) + { + auto mediaType = item.GetVideoInfoTag()->m_type; + return (mediaType == MediaTypeMovie || mediaType == MediaTypeTvShow || + mediaType == MediaTypeSeason || mediaType == MediaTypeEpisode || + mediaType == MediaTypeVideo || mediaType == MediaTypeVideoCollection || + mediaType == MediaTypeMusicVideo); + } + + return (item.HasMusicInfoTag() || item.IsPVR() || item.HasAddonInfo()); +} + +bool CGUIContentUtils::ShowInfoForItem(const CFileItem& item) +{ + if (item.HasAddonInfo()) + { + return CGUIDialogAddonInfo::ShowForItem(std::make_shared(item)); + } + else if (item.IsPVR()) + { + return CServiceBroker::GetPVRManager().Get().OnInfo(item); + } + else if (item.HasVideoInfoTag()) + { + CGUIDialogVideoInfo::ShowFor(item); + return true; + } + else if (item.HasMusicInfoTag()) + { + CGUIDialogMusicInfo::ShowFor(std::make_shared(item).get()); + return true; + } + return false; +} diff --git a/xbmc/utils/guilib/GUIContentUtils.h b/xbmc/utils/guilib/GUIContentUtils.h new file mode 100644 index 0000000000000..d395ecbb81ebe --- /dev/null +++ b/xbmc/utils/guilib/GUIContentUtils.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 CFileItem; + +namespace UTILS +{ +namespace GUILIB +{ +class CGUIContentUtils +{ +public: + /*! \brief Check whether an information dialog is available for the given item. + \param item The item to process + \return True if an information dialog is available, false otherwise + */ + static bool HasInfoForItem(const CFileItem& item); + + /*! \brief Show an information dialog for the given item. + \param item The item to process + \return True if an information dialog was displayed, false otherwise + */ + static bool ShowInfoForItem(const CFileItem& item); +}; +} // namespace GUILIB +} // namespace UTILS From 83763691b01151049ecf4c16118185527ed570c1 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:47:43 +0200 Subject: [PATCH 375/811] [video] CGUIWindowVideoBase: Fix CVideoSelectActionProcessor::OnResumeSelected to pass player to CGUIWindowVideoBase::OnClick. --- xbmc/video/windows/GUIWindowVideoBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index bd00fbf010565..fa747704b7102 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -562,7 +562,7 @@ class CVideoSelectActionProcessor : public CVideoSelectActionProcessorBase return true; } // resume playback of the video - return m_window.OnClick(m_itemIndex); + return m_window.OnClick(m_itemIndex, m_player); } bool OnPlaySelected() override From 09e0b30f415debdd52fd0db4ef17f0abee0b9fd9 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 10 Oct 2023 12:53:05 +0000 Subject: [PATCH 376/811] Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Spanish (Mexico) (es_mx)) Currently translated at 100.0% (171 of 171 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Finnish (fi_fi)) Currently translated at 100.0% (171 of 171 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Slovak (sk_sk)) Currently translated at 100.0% (97 of 97 strings) Translated using Weblate (Slovak (sk_sk)) Currently translated at 100.0% (171 of 171 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Hungarian (hu_hu)) Currently translated at 100.0% (97 of 97 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Czech (cs_cz)) Currently translated at 100.0% (171 of 171 strings) Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Update translation files Updated by "Update PO files to match POT (msgmerge)" hook in Weblate. Translated using Weblate (Portuguese (Brazil) (pt_br)) Currently translated at 100.0% (171 of 171 strings) Co-authored-by: Edson Armando Co-authored-by: Frodo19 Co-authored-by: HansCR Co-authored-by: Hosted Weblate Co-authored-by: Jose Riha Co-authored-by: Oskari Lavinto Co-authored-by: Wanilton Campos Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estouchy/hu_hu/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estouchy/sk_sk/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/cs_cz/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/es_mx/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/fi_fi/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/pt_br/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-add-ons-skins/skin-estuary/sk_sk/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-aac/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/audioencoder-kodi-builtin-wma/ Translate-URL: https://kodi.weblate.cloud/projects/kodi-core/screensaver-xbmc-builtin-dim/ Translation: Kodi add-ons: skins/skin.estouchy Translation: Kodi add-ons: skins/skin.estuary Translation: Kodi core/audioencoder.kodi.builtin.aac Translation: Kodi core/audioencoder.kodi.builtin.wma Translation: Kodi core/screensaver.xbmc.builtin.dim --- .../resource.language.hu_hu/strings.po | 4 +-- .../resource.language.sk_sk/strings.po | 12 ++++---- .../resource.language.cs_cz/strings.po | 28 +++++++++---------- .../resource.language.es_mx/strings.po | 20 ++++++------- .../resource.language.fi_fi/strings.po | 10 +++---- .../resource.language.pt_br/strings.po | 12 ++++---- .../resource.language.sk_sk/strings.po | 22 +++++++-------- 7 files changed, 54 insertions(+), 54 deletions(-) diff --git a/addons/skin.estouchy/language/resource.language.hu_hu/strings.po b/addons/skin.estouchy/language/resource.language.hu_hu/strings.po index 758f9020d6953..160be92857c69 100644 --- a/addons/skin.estouchy/language/resource.language.hu_hu/strings.po +++ b/addons/skin.estouchy/language/resource.language.hu_hu/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-07-03 15:42+0000\n" +"PO-Revision-Date: 2023-09-10 02:41+0000\n" "Last-Translator: Frodo19 \n" "Language-Team: Hungarian \n" "Language: hu_hu\n" @@ -164,7 +164,7 @@ msgstr "Játszási lista mentése" msgctxt "#31057" msgid "Close playlist" -msgstr "Lejátszási lista törlése" +msgstr "Lejátszási lista bezárása" msgctxt "#31058" msgid "System music files" diff --git a/addons/skin.estouchy/language/resource.language.sk_sk/strings.po b/addons/skin.estouchy/language/resource.language.sk_sk/strings.po index 579bc0d72254b..b941bd61a83ed 100644 --- a/addons/skin.estouchy/language/resource.language.sk_sk/strings.po +++ b/addons/skin.estouchy/language/resource.language.sk_sk/strings.po @@ -7,15 +7,15 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-07-01 08:26+0000\n" -"Last-Translator: Christian Gade \n" +"PO-Revision-Date: 2023-09-14 18:32+0000\n" +"Last-Translator: Jose Riha \n" "Language-Team: Slovak \n" "Language: sk_sk\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" -"X-Generator: Weblate 4.13\n" +"X-Generator: Weblate 5.0.1\n" msgctxt "Addon Summary" msgid "Skin for touchscreen devices" @@ -361,7 +361,7 @@ msgstr "Video aspekt" #. Label to show the video bitrate msgctxt "#31603" msgid "Video bitrate" -msgstr "" +msgstr "Prenosová rýchlosť videa" #. Label to show the audio codec name msgctxt "#31604" @@ -376,7 +376,7 @@ msgstr "Audio kanály" #. Label to show the audio bitrate msgctxt "#31606" msgid "Audio bitrate" -msgstr "" +msgstr "Prenosová rýchlosť zvuku" #. Label to show the screen resolution msgctxt "#31607" @@ -386,7 +386,7 @@ msgstr "Rozlíšenie obrazovky" #. Label to show the system rendering speed msgctxt "#31608" msgid "System rendering speed" -msgstr "" +msgstr "Rýchlosť vykresľovania systému" #. Label to show the system CPU usage msgctxt "#31609" diff --git a/addons/skin.estuary/language/resource.language.cs_cz/strings.po b/addons/skin.estuary/language/resource.language.cs_cz/strings.po index 6d7b8aa13b86a..29acc28a433ab 100644 --- a/addons/skin.estuary/language/resource.language.cs_cz/strings.po +++ b/addons/skin.estuary/language/resource.language.cs_cz/strings.po @@ -5,17 +5,17 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-03-21 11:09+0000\n" -"Last-Translator: Kryštof Černý \n" +"PO-Revision-Date: 2023-09-06 17:24+0000\n" +"Last-Translator: HansCR \n" "Language-Team: Czech \n" "Language: cs_cz\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n" -"X-Generator: Weblate 4.11.2\n" +"X-Generator: Weblate 4.18.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -231,11 +231,11 @@ msgstr "Založeno na Arial" msgctxt "#31054" msgid "Press [B]Left[/B] to step back, or [B]Right[/B] to step forward" -msgstr "" +msgstr "Stiskněte [B]Vlevo[/B] pro krok zpět nebo [B]Vpravo[/B] pro krok vpřed" msgctxt "#31055" msgid "Press [B]Right[/B] to frame advance" -msgstr "" +msgstr "Stiskněte [B]Vpravo[/B] pro posunutí o snímek vpřed" msgctxt "#31056" msgid "Go to playlist" @@ -496,7 +496,7 @@ msgstr "Zbývá" msgctxt "#31135" msgid "Binary" -msgstr "" +msgstr "Binární" msgctxt "#31136" msgid "Click here to see latest changes..." @@ -647,37 +647,37 @@ msgstr "Nastavení související s grafikou alb." #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Nabídka na obrazovce" #. Helper text for the label of OSD settings category msgctxt "#31171" msgid "On screen display (OSD) related settings" -msgstr "" +msgstr "Nastavení k nabídce na obrazovce (OSD)" #. Setting Automatically close video OSD msgctxt "#31172" msgid "Automatically close video OSD" -msgstr "" +msgstr "Automaticky zavřít OSD u videa" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "" +msgstr "Čas automatického uzavření OSD u videa (sekundy)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Výchozí akce pro alba na hlavní obrazovce" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Výchozí akce pro seriály na hlavní obrazovce" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Výchozí akce pro filmy na hlavní obrazovce" # empty strings from id 31170 to 31599 #. Label to show the video codec name diff --git a/addons/skin.estuary/language/resource.language.es_mx/strings.po b/addons/skin.estuary/language/resource.language.es_mx/strings.po index 8cd5d6daa45a9..217710cd3a403 100644 --- a/addons/skin.estuary/language/resource.language.es_mx/strings.po +++ b/addons/skin.estuary/language/resource.language.es_mx/strings.po @@ -5,9 +5,9 @@ msgid "" msgstr "" "Project-Id-Version: KODI Main\n" -"Report-Msgid-Bugs-To: https://github.com/xbmc/xbmc/issues/\n" +"Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2022-01-08 16:52+0000\n" +"PO-Revision-Date: 2023-10-09 04:57+0000\n" "Last-Translator: Edson Armando \n" "Language-Team: Spanish (Mexico) \n" "Language: es_mx\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.10.1\n" +"X-Generator: Weblate 5.0.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -647,37 +647,37 @@ msgstr "Configuración relacionada al arte." #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Información en pantalla" #. Helper text for the label of OSD settings category msgctxt "#31171" msgid "On screen display (OSD) related settings" -msgstr "" +msgstr "Configuración relacionada a la información en pantalla" #. Setting Automatically close video OSD msgctxt "#31172" msgid "Automatically close video OSD" -msgstr "" +msgstr "Ocultar info automáticamente" #. Setting auto close time for video osd msgctxt "#31173" msgid "Video OSD autoclose time (seconds)" -msgstr "" +msgstr "Tiempo para ocultar info (en segundos)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Acción predeterminada al seleccionar álbumes en la pantalla principal" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Acción predeterminada al seleccionar series en la pantalla principal" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Acción predeterminada al seleccionar conjuntos de películas en la pantalla principal" # empty strings from id 31170 to 31599 #. Label to show the video codec name diff --git a/addons/skin.estuary/language/resource.language.fi_fi/strings.po b/addons/skin.estuary/language/resource.language.fi_fi/strings.po index dcd4238f0d815..828e7a3dce326 100644 --- a/addons/skin.estuary/language/resource.language.fi_fi/strings.po +++ b/addons/skin.estuary/language/resource.language.fi_fi/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-07-24 11:49+0000\n" +"PO-Revision-Date: 2023-09-30 05:27+0000\n" "Last-Translator: Oskari Lavinto \n" "Language-Team: Finnish \n" "Language: fi_fi\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.18.2\n" +"X-Generator: Weblate 5.0.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -199,7 +199,7 @@ msgstr "Kameran valmistaja" msgctxt "#31042" msgid "Playlist options" -msgstr "Toistolistan valinnat" +msgstr "Toistolistavalinnat" msgctxt "#31043" msgid "Set the type and add rules to create a smart playlist. These playlists are dynamic and include all media items from your database which apply to your chosen rules." @@ -271,11 +271,11 @@ msgstr "Osiot" msgctxt "#31065" msgid "Video playlist" -msgstr "Videoiden toistolista" +msgstr "Videotoistolista" msgctxt "#31066" msgid "Music playlist" -msgstr "Musiikin toistolista" +msgstr "Musiikkitoistolista" msgctxt "#31067" msgid "Event log" diff --git a/addons/skin.estuary/language/resource.language.pt_br/strings.po b/addons/skin.estuary/language/resource.language.pt_br/strings.po index f034bb5f1c03d..74dc121da814d 100644 --- a/addons/skin.estuary/language/resource.language.pt_br/strings.po +++ b/addons/skin.estuary/language/resource.language.pt_br/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-06-25 09:43+0000\n" +"PO-Revision-Date: 2023-08-25 20:34+0000\n" "Last-Translator: Wanilton Campos \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_br\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.18\n" +"X-Generator: Weblate 4.18.2\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -647,7 +647,7 @@ msgstr "Ajustes relacionados as artes." #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Na tela OSD" #. Helper text for the label of OSD settings category msgctxt "#31171" @@ -667,17 +667,17 @@ msgstr "Tempo de fechamento automático do vídeo OSD (segundos)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Ação de seleção padrão para álbuns na tela inicial" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Ação de seleção padrão para seriados na tela inicial" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Ação de seleção padrão para coletâneas na tela inicial" # empty strings from id 31170 to 31599 #. Label to show the video codec name diff --git a/addons/skin.estuary/language/resource.language.sk_sk/strings.po b/addons/skin.estuary/language/resource.language.sk_sk/strings.po index f7e774f563d29..338e874b1f667 100644 --- a/addons/skin.estuary/language/resource.language.sk_sk/strings.po +++ b/addons/skin.estuary/language/resource.language.sk_sk/strings.po @@ -7,7 +7,7 @@ msgstr "" "Project-Id-Version: KODI Main\n" "Report-Msgid-Bugs-To: translations@kodi.tv\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" -"PO-Revision-Date: 2023-07-04 23:17+0000\n" +"PO-Revision-Date: 2023-09-14 18:32+0000\n" "Last-Translator: Jose Riha \n" "Language-Team: Slovak \n" "Language: sk_sk\n" @@ -15,7 +15,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n" -"X-Generator: Weblate 4.18.2\n" +"X-Generator: Weblate 5.0.1\n" msgctxt "Addon Summary" msgid "Estuary skin by phil65. (Kodi's default skin)" @@ -420,7 +420,7 @@ msgstr "Hľadať na YouTube" msgctxt "#31115" msgid "Toggle subtitle" -msgstr "" +msgstr "Prepnúť titulky" msgctxt "#31116" msgid "Remove this main menu item" @@ -647,12 +647,12 @@ msgstr "Nastavenia súvisiace s Artwork (umeleckou grafikou)." #. Label for OSD settings category msgctxt "#31170" msgid "On screen display" -msgstr "" +msgstr "Ponuka na obrazovke" #. Helper text for the label of OSD settings category msgctxt "#31171" msgid "On screen display (OSD) related settings" -msgstr "" +msgstr "Nastavenia ponuky na obrazovke (OSD)" #. Setting Automatically close video OSD msgctxt "#31172" @@ -667,17 +667,17 @@ msgstr "Čas automatického zatvárania OSD videa (sekundy)" #. Setting to control what happens when clicking a music album on the home screen msgctxt "#31174" msgid "Default select action for albums on the home screen" -msgstr "" +msgstr "Predvolená akcia pre albumy na hlavnej obrazovke" #. Setting to control what happens when clicking a TV show on the home screen msgctxt "#31175" msgid "Default select action for TV shows on the home screen" -msgstr "" +msgstr "Predvolená akcia pre seriály na hlavnej obrazovke" #. Setting to control what happens when clicking a movie set on the home screen msgctxt "#31176" msgid "Default select action for movie sets on the home screen" -msgstr "" +msgstr "Predvolená akcia pre filmy na hlavnej obrazovke" # empty strings from id 31170 to 31599 #. Label to show the video codec name @@ -698,7 +698,7 @@ msgstr "Video aspekt" #. Label to show the video bitrate msgctxt "#31603" msgid "Video bitrate" -msgstr "" +msgstr "Prenosová rýchlosť videa" #. Label to show the audio codec name msgctxt "#31604" @@ -713,7 +713,7 @@ msgstr "Audio kanály" #. Label to show the audio bitrate msgctxt "#31606" msgid "Audio bitrate" -msgstr "" +msgstr "Prenosová rýchlosť zvuku" #. Label to show the screen resolution msgctxt "#31607" @@ -723,7 +723,7 @@ msgstr "Rozlíšenie obrazovky" #. Label to show the system rendering speed msgctxt "#31608" msgid "System rendering speed" -msgstr "" +msgstr "Rýchlosť vykresľovania systému" #. Label to show the system CPU usage msgctxt "#31609" From 10d3b6fd7fd130ca6ba03fa1a30cc24ccffb2232 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Oct 2023 09:15:07 +0200 Subject: [PATCH 377/811] [fileitem] CFileItem::LoadDetails: Set dyn path for loaded CVideoInfoTag's. --- xbmc/FileItem.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 30ef905c5f8da..39a6e240fc161 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -3738,7 +3738,10 @@ bool CFileItem::LoadDetails() } if (ret) + { m_videoInfoTag = tag.release(); + m_strDynPath = m_videoInfoTag->m_strFileNameAndPath; + } return ret; } @@ -3784,6 +3787,7 @@ bool CFileItem::LoadDetails() if (db.LoadVideoInfo(GetDynPath(), *tag)) { m_videoInfoTag = tag.release(); + m_strDynPath = m_videoInfoTag->m_strFileNameAndPath; return true; } From 102a4b3d2b96cdbac3d711b9f59b5af28600b950 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:16:13 +0200 Subject: [PATCH 378/811] [interfaces] PlayerBuiltins: PlayOrQueueMedia must not set item's start offset; this will and must be done in CApplication::PlayFile later, otherwise not all player options are initialized properly. Fixes resume of BD and DVD images from Homescreen widgets. --- xbmc/interfaces/builtins/PlayerBuiltins.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/xbmc/interfaces/builtins/PlayerBuiltins.cpp b/xbmc/interfaces/builtins/PlayerBuiltins.cpp index bbd66d72f0f00..854443ebad119 100644 --- a/xbmc/interfaces/builtins/PlayerBuiltins.cpp +++ b/xbmc/interfaces/builtins/PlayerBuiltins.cpp @@ -608,13 +608,6 @@ int PlayOrQueueMedia(const std::vector& params, bool forcePlay) 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")) From 2984ea47e34f3393b47f3895d662741133948e39 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:18:17 +0200 Subject: [PATCH 379/811] [video] clang-format xbmc/video/windows/GUIWindowVideoPlaylist.cpp --- xbmc/video/windows/GUIWindowVideoPlaylist.cpp | 162 +++++++++--------- 1 file changed, 83 insertions(+), 79 deletions(-) diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp index 6b96fde98f282..d50d0f2ec2806 100644 --- a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp @@ -32,22 +32,22 @@ #include "utils/Variant.h" #include "utils/log.h" -#define CONTROL_BTNVIEWASICONS 2 -#define CONTROL_BTNSORTBY 3 -#define CONTROL_BTNSORTASC 4 -#define CONTROL_LABELFILES 12 +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 -#define CONTROL_BTNSHUFFLE 20 -#define CONTROL_BTNSAVE 21 -#define CONTROL_BTNCLEAR 22 +#define CONTROL_BTNSHUFFLE 20 +#define CONTROL_BTNSAVE 21 +#define CONTROL_BTNCLEAR 22 -#define CONTROL_BTNPLAY 23 -#define CONTROL_BTNNEXT 24 -#define CONTROL_BTNPREVIOUS 25 -#define CONTROL_BTNREPEAT 26 +#define CONTROL_BTNPLAY 23 +#define CONTROL_BTNNEXT 24 +#define CONTROL_BTNPREVIOUS 25 +#define CONTROL_BTNREPEAT 26 CGUIWindowVideoPlaylist::CGUIWindowVideoPlaylist() -: CGUIWindowVideoBase(WINDOW_VIDEO_PLAYLIST, "MyPlaylist.xml") + : CGUIWindowVideoBase(WINDOW_VIDEO_PLAYLIST, "MyPlaylist.xml") { m_movingFrom = -1; } @@ -75,16 +75,16 @@ void CGUIWindowVideoPlaylist::OnPrepareFileItems(CFileItemList& items) bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) { - switch ( message.GetMessage() ) + switch (message.GetMessage()) { - case GUI_MSG_PLAYLISTPLAYER_REPEAT: + case GUI_MSG_PLAYLISTPLAYER_REPEAT: { UpdateButtons(); } break; - case GUI_MSG_PLAYLISTPLAYER_RANDOM: - case GUI_MSG_PLAYLIST_CHANGED: + case GUI_MSG_PLAYLISTPLAYER_RANDOM: + case GUI_MSG_PLAYLIST_CHANGED: { // global playlist changed outside playlist window UpdateButtons(); @@ -95,17 +95,16 @@ bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) m_iLastControl = CONTROL_BTNVIEWASICONS; SET_CONTROL_FOCUS(m_iLastControl, 0); } - } break; - case GUI_MSG_WINDOW_DEINIT: + case GUI_MSG_WINDOW_DEINIT: { m_movingFrom = -1; } break; - case GUI_MSG_WINDOW_INIT: + case GUI_MSG_WINDOW_INIT: { m_vecItems->SetPath("playlistvideo://"); @@ -132,7 +131,7 @@ bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) } break; - case GUI_MSG_CLICKED: + case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_BTNSHUFFLE) @@ -197,7 +196,7 @@ bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) UpdateButtons(); } - else if (m_viewControl.HasControl(iControl)) // list/thumb control + else if (m_viewControl.HasControl(iControl)) // list/thumb control { int iAction = message.GetParam1(); int iItem = m_viewControl.GetSelectedItem(); @@ -213,7 +212,7 @@ bool CGUIWindowVideoPlaylist::OnMessage(CGUIMessage& message) return CGUIWindowVideoBase::OnMessage(message); } -bool CGUIWindowVideoPlaylist::OnAction(const CAction &action) +bool CGUIWindowVideoPlaylist::OnAction(const CAction& action) { if (action.GetID() == ACTION_PARENT_DIR) { @@ -244,7 +243,9 @@ bool CGUIWindowVideoPlaylist::OnBack(int actionID) return CGUIWindowVideoBase::OnBack(actionID); } -bool CGUIWindowVideoPlaylist::MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate /* = true */) +bool CGUIWindowVideoPlaylist::MoveCurrentPlayListItem(int iItem, + int iAction, + bool bUpdate /* = true */) { int iSelected = iItem; int iNew = iSelected; @@ -286,7 +287,6 @@ bool CGUIWindowVideoPlaylist::MoveCurrentPlayListItem(int iItem, int iAction, bo return false; } - void CGUIWindowVideoPlaylist::ClearPlayList() { ClearFileItems(); @@ -304,7 +304,7 @@ void CGUIWindowVideoPlaylist::ClearPlayList() void CGUIWindowVideoPlaylist::UpdateButtons() { // Update playlist buttons - if (m_vecItems->Size() ) + if (m_vecItems->Size()) { CONTROL_ENABLE(CONTROL_BTNCLEAR); CONTROL_ENABLE(CONTROL_BTNSAVE); @@ -360,9 +360,10 @@ void CGUIWindowVideoPlaylist::UpdateButtons() MarkPlaying(); } -bool CGUIWindowVideoPlaylist::OnPlayMedia(int iItem, const std::string &player) +bool CGUIWindowVideoPlaylist::OnPlayMedia(int iItem, const std::string& player) { - if ( iItem < 0 || iItem >= m_vecItems->Size() ) return false; + if (iItem < 0 || iItem >= m_vecItems->Size()) + return false; if (g_partyModeManager.IsEnabled()) g_partyModeManager.Play(iItem); else @@ -377,7 +378,8 @@ bool CGUIWindowVideoPlaylist::OnPlayMedia(int iItem, const std::string &player) CServiceBroker::GetPlaylistPlayer().GetPlaylist(PLAYLIST::TYPE_VIDEO)[iItem]; pPlaylistItem->SetStartOffset(pItem->GetStartOffset()); if (pPlaylistItem->HasVideoInfoTag() && pItem->HasVideoInfoTag()) - pPlaylistItem->GetVideoInfoTag()->SetResumePoint(pItem->GetVideoInfoTag()->GetResumePoint()); + pPlaylistItem->GetVideoInfoTag()->SetResumePoint( + pItem->GetVideoInfoTag()->GetResumePoint()); } // now play item CServiceBroker::GetPlaylistPlayer().Play(iItem, player); @@ -393,7 +395,7 @@ void CGUIWindowVideoPlaylist::RemovePlayListItem(int iItem) // The current playing song can't be removed if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_VIDEO && appPlayer->IsPlayingVideo() && CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iItem) - return ; + return; CServiceBroker::GetPlaylistPlayer().Remove(PLAYLIST::TYPE_VIDEO, iItem); @@ -415,15 +417,16 @@ void CGUIWindowVideoPlaylist::RemovePlayListItem(int iItem) void CGUIWindowVideoPlaylist::SavePlayList() { std::string strNewFileName; - if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) + if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, + false)) { // need 2 rename it strNewFileName = CUtil::MakeLegalFileName(std::move(strNewFileName)); strNewFileName += ".m3u8"; - std::string strPath = URIUtils::AddFileToFolder( - CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), - "video", - strNewFileName); + std::string strPath = + URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_SYSTEM_PLAYLISTSPATH), + "video", strNewFileName); PLAYLIST::CPlayListM3U playlist; playlist.Add(*m_vecItems); @@ -433,15 +436,14 @@ void CGUIWindowVideoPlaylist::SavePlayList() } } -void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons &buttons) +void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons& buttons) { int itemPlaying = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); if (m_movingFrom >= 0) { if (itemNumber != m_movingFrom && (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying)) - buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here + buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here buttons.Add(CONTEXT_BUTTON_CANCEL_MOVE, 13253); - } else { @@ -449,7 +451,7 @@ void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons { CFileItemPtr item = m_vecItems->Get(itemNumber); - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); // check what players we have, if we have multiple display play with option std::vector players; @@ -477,7 +479,7 @@ void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons if (g_partyModeManager.IsEnabled()) { buttons.Add(CONTEXT_BUTTON_EDIT_PARTYMODE, 21439); - buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode + buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode } } @@ -485,7 +487,7 @@ bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON but { switch (button) { - case CONTEXT_BUTTON_PLAY_WITH: + case CONTEXT_BUTTON_PLAY_WITH: { CFileItemPtr item; if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) @@ -493,7 +495,7 @@ bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON but if (!item) break; - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); std::vector players; if (item->IsVideoDb()) @@ -510,47 +512,47 @@ bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON but return true; } - case CONTEXT_BUTTON_MOVE_ITEM: - m_movingFrom = itemNumber; - return true; + case CONTEXT_BUTTON_MOVE_ITEM: + m_movingFrom = itemNumber; + return true; - case CONTEXT_BUTTON_MOVE_HERE: - if (m_movingFrom >= 0) - MoveItem(m_movingFrom, itemNumber); - m_movingFrom = -1; - return true; + case CONTEXT_BUTTON_MOVE_HERE: + if (m_movingFrom >= 0) + MoveItem(m_movingFrom, itemNumber); + m_movingFrom = -1; + return true; - case CONTEXT_BUTTON_CANCEL_MOVE: - m_movingFrom = -1; - return true; + case CONTEXT_BUTTON_CANCEL_MOVE: + m_movingFrom = -1; + return true; - case CONTEXT_BUTTON_MOVE_ITEM_UP: - OnMove(itemNumber, ACTION_MOVE_ITEM_UP); - return true; + case CONTEXT_BUTTON_MOVE_ITEM_UP: + OnMove(itemNumber, ACTION_MOVE_ITEM_UP); + return true; - case CONTEXT_BUTTON_MOVE_ITEM_DOWN: - OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); - return true; + case CONTEXT_BUTTON_MOVE_ITEM_DOWN: + OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); + return true; - case CONTEXT_BUTTON_DELETE: - RemovePlayListItem(itemNumber); - return true; - case CONTEXT_BUTTON_CANCEL_PARTYMODE: - g_partyModeManager.Disable(); - return true; - case CONTEXT_BUTTON_EDIT_PARTYMODE: - { - std::string playlist = "special://profile/PartyMode-Video.xsp"; - if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) - { - // apply new rules + case CONTEXT_BUTTON_DELETE: + RemovePlayListItem(itemNumber); + return true; + case CONTEXT_BUTTON_CANCEL_PARTYMODE: g_partyModeManager.Disable(); - g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO); + return true; + case CONTEXT_BUTTON_EDIT_PARTYMODE: + { + std::string playlist = "special://profile/PartyMode-Video.xsp"; + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) + { + // apply new rules + g_partyModeManager.Disable(); + g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO); + } + return true; } - return true; - } - default: - break; + default: + break; } return CGUIWindowVideoBase::OnContextButton(itemNumber, button); @@ -558,14 +560,17 @@ bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON but void CGUIWindowVideoPlaylist::OnMove(int iItem, int iAction) { - if (iItem < 0 || iItem >= m_vecItems->Size()) return; + if (iItem < 0 || iItem >= m_vecItems->Size()) + return; MoveCurrentPlayListItem(iItem, iAction); } void CGUIWindowVideoPlaylist::MoveItem(int iStart, int iDest) { - if (iStart < 0 || iStart >= m_vecItems->Size()) return; - if (iDest < 0 || iDest >= m_vecItems->Size()) return; + if (iStart < 0 || iStart >= m_vecItems->Size()) + return; + if (iDest < 0 || iDest >= m_vecItems->Size()) + return; // default to move up int iAction = ACTION_MOVE_ITEM_UP; @@ -606,4 +611,3 @@ void CGUIWindowVideoPlaylist::MarkPlaying() m_vecItems->Get(iSong)->Select(true); }*/ } - From 7bb14d08f91f7339d9572ecb691debb7e5c05d6b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 11 Oct 2023 08:30:58 +0200 Subject: [PATCH 380/811] [windows] CGUIMediaWindow::OnContextButton: Fix 'Browse into' (e.g. for BD and DVD disc images). --- xbmc/windows/GUIMediaWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 22e614b79024e..3776be36fe85c 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -1837,7 +1837,7 @@ bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button) case CONTEXT_BUTTON_BROWSE_INTO: { CFileItemPtr item = m_vecItems->Get(itemNumber); - Update(item->GetPath()); + Update(item->GetDynPath()); return true; } default: From 76368669d152339f3729a0004f2d948df0c18f00 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 11 Oct 2023 10:11:50 +0100 Subject: [PATCH 381/811] Revert "[upnp] Fix url extension for JPEG_TN dlna_profile" This reverts commit a2c7cea88c07b6d3ef12ba33cb8c7fea9e5a119e. --- xbmc/network/upnp/UPnPInternal.cpp | 31 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index fddc3608c00ed..c26311fae5fbe 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -652,22 +652,21 @@ BuildObject(CFileItem& item, thumb = ContentUtils::GetPreferredArtImage(item); if (!thumb.empty()) { - PLT_AlbumArtInfo art; - // Set DLNA profileID by extension, defaulting to JPEG. - if (URIUtils::HasExtension(thumb, ".png")) - { - art.dlna_profile = "PNG_TN"; - } - else - { - art.dlna_profile = "JPEG_TN"; - } - // append /thumb to the safe resource uri to avoid clients flagging the item with - // the incorrect mimetype (derived from the file extension) - art.uri = upnp_server->BuildSafeResourceUri( - rooturi, (*ips.GetFirstItem()).ToString(), - std::string(CTextureUtils::GetWrappedImageURL(thumb) + "/thumb").c_str()); - object->m_ExtraInfo.album_arts.Add(art); + PLT_AlbumArtInfo art; + art.uri = + upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), + CTextureUtils::GetWrappedImageURL(thumb).c_str()); + + // Set DLNA profileID by extension, defaulting to JPEG. + if (URIUtils::HasExtension(thumb, ".png")) + { + art.dlna_profile = "PNG_TN"; + } + else + { + art.dlna_profile = "JPEG_TN"; + } + object->m_ExtraInfo.album_arts.Add(art); } for (const auto& itArtwork : item.GetArt()) From 93943da7f5a72b406c42c764d9db08e2583972b7 Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Wed, 11 Oct 2023 12:10:47 +0100 Subject: [PATCH 382/811] [upnp] Fix upnp thumbs by pointing to the actual image in case the image:// filetype is not an actual image --- xbmc/network/upnp/UPnPInternal.cpp | 52 ++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index c26311fae5fbe..4a7173a9a521e 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -9,7 +9,7 @@ #include "FileItem.h" #include "ServiceBroker.h" -#include "TextureDatabase.h" +#include "TextureCache.h" #include "ThumbLoader.h" #include "UPnPServer.h" #include "URL.h" @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -39,6 +40,22 @@ using namespace MUSIC_INFO; using namespace XFILE; +namespace +{ +std::optional GetImageDLNAProfile(const std::string& imgPath) +{ + if (URIUtils::HasExtension(imgPath, ".png")) + { + return "PNG_TN"; + } + else if (URIUtils::HasExtension(imgPath, ".jpg") || URIUtils::HasExtension(imgPath, ".jpeg")) + { + return "JPEG_TN"; + } + return std::nullopt; +} +} // namespace + namespace UPNP { @@ -653,19 +670,40 @@ BuildObject(CFileItem& item, if (!thumb.empty()) { PLT_AlbumArtInfo art; - art.uri = - upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), - CTextureUtils::GetWrappedImageURL(thumb).c_str()); - // Set DLNA profileID by extension, defaulting to JPEG. - if (URIUtils::HasExtension(thumb, ".png")) + // Get DLNA profileId for the image + std::optional imageProfile = GetImageDLNAProfile(thumb); + if (imageProfile.has_value()) { - art.dlna_profile = "PNG_TN"; + art.dlna_profile = imageProfile.value().c_str(); } else + { + // unknown image format (might be a embed thumb previously extracted and cached - e.g. mp3, flac, mvk, etc) + bool needsRecaching; + const std::string cachedImagePath = + CServiceBroker::GetTextureCache()->CheckCachedImage(thumb, needsRecaching); + if (!cachedImagePath.empty()) + { + imageProfile = GetImageDLNAProfile(cachedImagePath); + if (imageProfile.has_value()) + { + thumb = cachedImagePath; + art.dlna_profile = imageProfile.value().c_str(); + } + } + } + + // Always default to JPEG if the profile could not be found + if (art.dlna_profile.IsEmpty()) { art.dlna_profile = "JPEG_TN"; } + + art.uri = + upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), + CTextureUtils::GetWrappedImageURL(thumb).c_str()); + object->m_ExtraInfo.album_arts.Add(art); } From e4a4b8ead4cee9bc4c9f54e7b6f57217ef4d31c3 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Tue, 10 Oct 2023 22:11:04 +0200 Subject: [PATCH 383/811] [webOS] Acb: Install empty dummy library --- cmake/modules/FindAcbAPI.cmake | 5 +++++ cmake/scripts/webos/Install.cmake | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindAcbAPI.cmake b/cmake/modules/FindAcbAPI.cmake index 89bdc38392a13..25575b4997cab 100644 --- a/cmake/modules/FindAcbAPI.cmake +++ b/cmake/modules/FindAcbAPI.cmake @@ -33,5 +33,10 @@ if(NOT TARGET ACBAPI::ACBAPI) IMPORTED_LOCATION "${ACBAPI_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${ACBAPI_INCLUDE_DIR}") set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ACBAPI::ACBAPI) + + # creates an empty library to install on webOS 5+ devices + file(TOUCH dummy.c) + add_library(AcbAPI SHARED dummy.c) + set_target_properties(AcbAPI PROPERTIES VERSION 1.0.0 SOVERSION 1) endif() endif() diff --git a/cmake/scripts/webos/Install.cmake b/cmake/scripts/webos/Install.cmake index fde84c14793d0..03dd7ce183399 100644 --- a/cmake/scripts/webos/Install.cmake +++ b/cmake/scripts/webos/Install.cmake @@ -30,7 +30,7 @@ set(APP_INSTALL_DIRS ${CMAKE_BINARY_DIR}/addons ${CMAKE_BINARY_DIR}/userdata) set(APP_TOOLCHAIN_FILES ${TOOLCHAIN}/${HOST}/sysroot/lib/libatomic.so.1 ${TOOLCHAIN}/${HOST}/sysroot/lib/libcrypt.so.1 - ${DEPENDS_PATH}/lib/libAcbAPI.so.1) + ${CMAKE_BINARY_DIR}/libAcbAPI.so.1) set(BIN_ADDONS_DIR ${DEPENDS_PATH}/addons) file(WRITE ${CMAKE_BINARY_DIR}/install.cmake " @@ -54,7 +54,7 @@ file(WRITE ${CMAKE_BINARY_DIR}/install.cmake " # Copy files to the location expected by the webOS packaging scripts. add_custom_target(bundle - DEPENDS ${APP_NAME_LC} ${CMAKE_BINARY_DIR}/missing_libs.txt + DEPENDS ${APP_NAME_LC} ${CMAKE_BINARY_DIR}/missing_libs.txt AcbAPI COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/install.cmake ) From 5fe3376ff45f2cddd0b7df3266d181b666f2bd52 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Sat, 23 Sep 2023 23:43:08 +0200 Subject: [PATCH 384/811] [webOS] Set required memory --- tools/webOS/packaging/appinfo.json.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/webOS/packaging/appinfo.json.in b/tools/webOS/packaging/appinfo.json.in index 300a52be6b814..8bedf6b640a54 100644 --- a/tools/webOS/packaging/appinfo.json.in +++ b/tools/webOS/packaging/appinfo.json.in @@ -10,5 +10,6 @@ "splashBackground": "media/splash_webOS.png", "spinnerOnLaunch": false, "handlesRelaunch": true, - "nativeLifeCycleInterfaceVersion": 2 + "nativeLifeCycleInterfaceVersion": 2, + "requiredMemory": 500 } From 16cc2399e5c0ce89f4d551d87a9a2f43608e5dd4 Mon Sep 17 00:00:00 2001 From: Stephan Sundermann Date: Wed, 11 Oct 2023 16:50:55 +0200 Subject: [PATCH 385/811] Revert "webos: Disable getauxval" This reverts commit b3e20806c1ecff1b819ba3f88f75f3b2e0cd5081. --- xbmc/platform/linux/CPUInfoLinux.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/xbmc/platform/linux/CPUInfoLinux.cpp b/xbmc/platform/linux/CPUInfoLinux.cpp index 728480ca2542d..a66f6ff7ddbab 100644 --- a/xbmc/platform/linux/CPUInfoLinux.cpp +++ b/xbmc/platform/linux/CPUInfoLinux.cpp @@ -279,17 +279,11 @@ CCPUInfoLinux::CCPUInfoLinux() m_cpuModel = m_cpuModel.substr(0, m_cpuModel.find(char(0))); // remove extra null terminations - // We exclude webOS here due to a bug in the toolchain leading to a SEGFAULT on webOS 6+ devices. -#if defined(HAS_NEON) && defined(__arm__) && !defined(TARGET_WEBOS) +#if defined(HAS_NEON) && defined(__arm__) if (getauxval(AT_HWCAP) & HWCAP_NEON) m_cpuFeatures |= CPU_FEATURE_NEON; #endif -#ifdef TARGET_WEBOS - // We can assume that any webOS TV has NEON support - m_cpuFeatures |= CPU_FEATURE_NEON; -#endif - #if defined(HAS_NEON) && defined(__aarch64__) if (getauxval(AT_HWCAP) & HWCAP_ASIMD) m_cpuFeatures |= CPU_FEATURE_NEON; From a7cc4a1d5a8b6b0b81fa0e107f9dd81c52f5e96b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:19:30 +0200 Subject: [PATCH 386/811] [music] clang-format xbmc/music/windows/GUIWindowMusicPlaylist.cpp --- xbmc/music/windows/GUIWindowMusicPlaylist.cpp | 170 +++++++++--------- 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp index fba83f4e8fd3c..6ae3abb9f0e3c 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp @@ -38,22 +38,22 @@ #include "utils/log.h" #include "view/GUIViewState.h" -#define CONTROL_BTNVIEWASICONS 2 -#define CONTROL_BTNSORTBY 3 -#define CONTROL_BTNSORTASC 4 -#define CONTROL_LABELFILES 12 +#define CONTROL_BTNVIEWASICONS 2 +#define CONTROL_BTNSORTBY 3 +#define CONTROL_BTNSORTASC 4 +#define CONTROL_LABELFILES 12 -#define CONTROL_BTNSHUFFLE 20 -#define CONTROL_BTNSAVE 21 -#define CONTROL_BTNCLEAR 22 +#define CONTROL_BTNSHUFFLE 20 +#define CONTROL_BTNSAVE 21 +#define CONTROL_BTNCLEAR 22 -#define CONTROL_BTNPLAY 23 -#define CONTROL_BTNNEXT 24 -#define CONTROL_BTNPREVIOUS 25 -#define CONTROL_BTNREPEAT 26 +#define CONTROL_BTNPLAY 23 +#define CONTROL_BTNNEXT 24 +#define CONTROL_BTNPREVIOUS 25 +#define CONTROL_BTNREPEAT 26 CGUIWindowMusicPlayList::CGUIWindowMusicPlayList(void) - : CGUIWindowMusicBase(WINDOW_MUSIC_PLAYLIST, "MyPlaylist.xml") + : CGUIWindowMusicBase(WINDOW_MUSIC_PLAYLIST, "MyPlaylist.xml") { m_musicInfoLoader.SetObserver(this); m_movingFrom = -1; @@ -63,16 +63,16 @@ CGUIWindowMusicPlayList::~CGUIWindowMusicPlayList(void) = default; bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) { - switch ( message.GetMessage() ) + switch (message.GetMessage()) { - case GUI_MSG_PLAYLISTPLAYER_REPEAT: + case GUI_MSG_PLAYLISTPLAYER_REPEAT: { UpdateButtons(); } break; - case GUI_MSG_PLAYLISTPLAYER_RANDOM: - case GUI_MSG_PLAYLIST_CHANGED: + case GUI_MSG_PLAYLISTPLAYER_RANDOM: + case GUI_MSG_PLAYLIST_CHANGED: { // global playlist changed outside playlist window UpdateButtons(); @@ -91,11 +91,10 @@ bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) m_iLastControl = CONTROL_BTNVIEWASICONS; SET_CONTROL_FOCUS(m_iLastControl, 0); } - } break; - case GUI_MSG_WINDOW_DEINIT: + case GUI_MSG_WINDOW_DEINIT: { if (m_musicInfoLoader.IsLoading()) m_musicInfoLoader.StopThread(); @@ -104,7 +103,7 @@ bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) } break; - case GUI_MSG_WINDOW_INIT: + case GUI_MSG_WINDOW_INIT: { // Setup item cache for tagloader m_musicInfoLoader.UseCacheOnHD("special://temp/archive_cache/MusicPlaylist.fi"); @@ -135,7 +134,7 @@ bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) } break; - case GUI_MSG_CLICKED: + case GUI_MSG_CLICKED: { int iControl = message.GetSenderId(); if (iControl == CONTROL_BTNSHUFFLE) @@ -219,12 +218,11 @@ bool CGUIWindowMusicPlayList::OnMessage(CGUIMessage& message) } } break; - } return CGUIWindowMusicBase::OnMessage(message); } -bool CGUIWindowMusicPlayList::OnAction(const CAction &action) +bool CGUIWindowMusicPlayList::OnAction(const CAction& action) { if (action.GetID() == ACTION_PARENT_DIR) { @@ -257,7 +255,9 @@ bool CGUIWindowMusicPlayList::OnBack(int actionID) return CGUIWindowMusicBase::OnBack(actionID); } -bool CGUIWindowMusicPlayList::MoveCurrentPlayListItem(int iItem, int iAction, bool bUpdate /* = true */) +bool CGUIWindowMusicPlayList::MoveCurrentPlayListItem(int iItem, + int iAction, + bool bUpdate /* = true */) { int iSelected = iItem; int iNew = iSelected; @@ -303,14 +303,16 @@ bool CGUIWindowMusicPlayList::MoveCurrentPlayListItem(int iItem, int iAction, bo void CGUIWindowMusicPlayList::SavePlayList() { std::string strNewFileName; - if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, false)) + if (CGUIKeyboardFactory::ShowAndGetInput(strNewFileName, CVariant{g_localizeStrings.Get(16012)}, + false)) { + // need 2 rename it strNewFileName = CUtil::MakeLegalFileName(std::move(strNewFileName)); strNewFileName += ".m3u8"; - std::string strPath = URIUtils::AddFileToFolder( - CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH), - "music", - strNewFileName); + std::string strPath = + URIUtils::AddFileToFolder(CServiceBroker::GetSettingsComponent()->GetSettings()->GetString( + CSettings::SETTING_SYSTEM_PLAYLISTSPATH), + "music", strNewFileName); // get selected item int iItem = m_viewControl.GetSelectedItem(); @@ -360,14 +362,15 @@ void CGUIWindowMusicPlayList::ClearPlayList() void CGUIWindowMusicPlayList::RemovePlayListItem(int iItem) { - if (iItem < 0 || iItem > m_vecItems->Size()) return; + if (iItem < 0 || iItem > m_vecItems->Size()) + return; const auto& components = CServiceBroker::GetAppComponents(); const auto appPlayer = components.GetComponent(); // The current playing song can't be removed if (CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist() == PLAYLIST::TYPE_MUSIC && appPlayer->IsPlayingAudio() && CServiceBroker::GetPlaylistPlayer().GetCurrentSong() == iItem) - return ; + return; CServiceBroker::GetPlaylistPlayer().Remove(PLAYLIST::TYPE_MUSIC, iItem); @@ -450,7 +453,7 @@ void CGUIWindowMusicPlayList::UpdateButtons() MarkPlaying(); } -bool CGUIWindowMusicPlayList::OnPlayMedia(int iItem, const std::string &player) +bool CGUIWindowMusicPlayList::OnPlayMedia(int iItem, const std::string& player) { if (g_partyModeManager.IsEnabled()) g_partyModeManager.Play(iItem); @@ -469,7 +472,7 @@ bool CGUIWindowMusicPlayList::OnPlayMedia(int iItem, const std::string &player) { // Reset Playlistplayer, playback started now does // not use the playlistplayer. - CFileItemPtr pItem=m_vecItems->Get(iItem); + CFileItemPtr pItem = m_vecItems->Get(iItem); CServiceBroker::GetPlaylistPlayer().Reset(); CServiceBroker::GetPlaylistPlayer().SetCurrentPlaylist(PLAYLIST::TYPE_NONE); g_application.PlayFile(*pItem, player); @@ -483,7 +486,8 @@ void CGUIWindowMusicPlayList::OnItemLoaded(CFileItem* pItem) { if (pItem->HasMusicInfoTag() && pItem->GetMusicInfoTag()->Loaded()) { // set label 1+2 from tags - const std::shared_ptr settings = CServiceBroker::GetSettingsComponent()->GetSettings(); + const std::shared_ptr settings = + CServiceBroker::GetSettingsComponent()->GetSettings(); std::string strTrack = settings->GetString(CSettings::SETTING_MUSICFILES_NOWPLAYINGTRACKFORMAT); if (strTrack.empty()) strTrack = settings->GetString(CSettings::SETTING_MUSICFILES_TRACKFORMAT); @@ -513,7 +517,8 @@ void CGUIWindowMusicPlayList::OnItemLoaded(CFileItem* pItem) } } -bool CGUIWindowMusicPlayList::Update(const std::string& strDirectory, bool updateFilterPath /* = true */) +bool CGUIWindowMusicPlayList::Update(const std::string& strDirectory, + bool updateFilterPath /* = true */) { if (m_musicInfoLoader.IsLoading()) m_musicInfoLoader.StopThread(); @@ -528,7 +533,7 @@ bool CGUIWindowMusicPlayList::Update(const std::string& strDirectory, bool updat return true; } -void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons &buttons) +void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons& buttons) { // is this playlist playing? int itemPlaying = CServiceBroker::GetPlaylistPlayer().GetCurrentSong(); @@ -542,13 +547,14 @@ void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons { // we can move the item to any position not where we are, and any position not above currently // playing item in party mode - if (itemNumber != m_movingFrom && (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying)) - buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here + if (itemNumber != m_movingFrom && + (!g_partyModeManager.IsEnabled() || itemNumber > itemPlaying)) + buttons.Add(CONTEXT_BUTTON_MOVE_HERE, 13252); // move item here buttons.Add(CONTEXT_BUTTON_CANCEL_MOVE, 13253); } else { - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); // aren't in a move // check what players we have, if we have multiple display play with option @@ -571,7 +577,7 @@ void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons if (g_partyModeManager.IsEnabled()) { buttons.Add(CONTEXT_BUTTON_EDIT_PARTYMODE, 21439); - buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode + buttons.Add(CONTEXT_BUTTON_CANCEL_PARTYMODE, 588); // cancel party mode } } @@ -579,7 +585,7 @@ bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON but { switch (button) { - case CONTEXT_BUTTON_PLAY_WITH: + case CONTEXT_BUTTON_PLAY_WITH: { CFileItemPtr item; if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) @@ -587,7 +593,7 @@ bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON but if (!item) break; - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); + const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); std::vector players; playerCoreFactory.GetPlayers(*item, players); @@ -596,59 +602,60 @@ bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON but OnClick(itemNumber, player); return true; } - case CONTEXT_BUTTON_MOVE_ITEM: - m_movingFrom = itemNumber; - return true; - - case CONTEXT_BUTTON_MOVE_HERE: - MoveItem(m_movingFrom, itemNumber); - m_movingFrom = -1; - return true; + case CONTEXT_BUTTON_MOVE_ITEM: + m_movingFrom = itemNumber; + return true; - case CONTEXT_BUTTON_CANCEL_MOVE: - m_movingFrom = -1; - return true; + case CONTEXT_BUTTON_MOVE_HERE: + MoveItem(m_movingFrom, itemNumber); + m_movingFrom = -1; + return true; - case CONTEXT_BUTTON_MOVE_ITEM_UP: - OnMove(itemNumber, ACTION_MOVE_ITEM_UP); - return true; + case CONTEXT_BUTTON_CANCEL_MOVE: + m_movingFrom = -1; + return true; - case CONTEXT_BUTTON_MOVE_ITEM_DOWN: - OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); - return true; + case CONTEXT_BUTTON_MOVE_ITEM_UP: + OnMove(itemNumber, ACTION_MOVE_ITEM_UP); + return true; - case CONTEXT_BUTTON_DELETE: - RemovePlayListItem(itemNumber); - return true; + case CONTEXT_BUTTON_MOVE_ITEM_DOWN: + OnMove(itemNumber, ACTION_MOVE_ITEM_DOWN); + return true; - case CONTEXT_BUTTON_CANCEL_PARTYMODE: - g_partyModeManager.Disable(); - return true; + case CONTEXT_BUTTON_DELETE: + RemovePlayListItem(itemNumber); + return true; - case CONTEXT_BUTTON_EDIT_PARTYMODE: - { - const std::shared_ptr profileManager = CServiceBroker::GetSettingsComponent()->GetProfileManager(); + case CONTEXT_BUTTON_CANCEL_PARTYMODE: + g_partyModeManager.Disable(); + return true; - std::string playlist = profileManager->GetUserDataItem("PartyMode.xsp"); - if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) + case CONTEXT_BUTTON_EDIT_PARTYMODE: { - // apply new rules - g_partyModeManager.Disable(); - g_partyModeManager.Enable(); + const std::shared_ptr profileManager = + CServiceBroker::GetSettingsComponent()->GetProfileManager(); + + std::string playlist = profileManager->GetUserDataItem("PartyMode.xsp"); + if (CGUIDialogSmartPlaylistEditor::EditPlaylist(playlist)) + { + // apply new rules + g_partyModeManager.Disable(); + g_partyModeManager.Enable(); + } + return true; } - return true; - } - default: - break; + default: + break; } return CGUIWindowMusicBase::OnContextButton(itemNumber, button); } - void CGUIWindowMusicPlayList::OnMove(int iItem, int iAction) { - if (iItem < 0 || iItem >= m_vecItems->Size()) return; + if (iItem < 0 || iItem >= m_vecItems->Size()) + return; bool bRestart = m_musicInfoLoader.IsLoading(); if (bRestart) @@ -662,8 +669,10 @@ void CGUIWindowMusicPlayList::OnMove(int iItem, int iAction) void CGUIWindowMusicPlayList::MoveItem(int iStart, int iDest) { - if (iStart < 0 || iStart >= m_vecItems->Size()) return; - if (iDest < 0 || iDest >= m_vecItems->Size()) return; + if (iStart < 0 || iStart >= m_vecItems->Size()) + return; + if (iDest < 0 || iDest >= m_vecItems->Size()) + return; // default to move up int iAction = ACTION_MOVE_ITEM_UP; @@ -711,4 +720,3 @@ void CGUIWindowMusicPlayList::MarkPlaying() m_vecItems->Get(iSong)->Select(true); }*/ } - From d468c643707ee310fdfead60b51f71486af5ba0d Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 9 Oct 2023 14:28:12 +0200 Subject: [PATCH 387/811] [video] Migrate 'Play using...' to "new" context menu system. --- .../resources/strings.po | 2 + xbmc/ContextMenuManager.cpp | 1 + xbmc/video/ContextMenus.cpp | 86 +++++++++++++------ xbmc/video/ContextMenus.h | 7 ++ xbmc/video/VideoUtils.cpp | 16 ++-- xbmc/video/VideoUtils.h | 2 + xbmc/video/dialogs/GUIDialogVideoInfo.cpp | 2 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 47 ---------- xbmc/video/windows/GUIWindowVideoPlaylist.cpp | 45 ---------- 9 files changed, 80 insertions(+), 128 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index f593cc8c1daca..f99fbe0d9620c 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -8772,6 +8772,8 @@ msgstr "" #empty strings from id 15209 to 15212 +#. Label for context menu action to choose a player and play +#: xbmc/video/ContextMenus.h #: xbmc/windows/GUIWindowFileManager.cpp msgctxt "#15213" msgid "Play using..." diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index 950536e621d56..75a5d057cb7b2 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -64,6 +64,7 @@ void CContextMenuManager::Init() std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index c72c321e6687d..25ae6f0526dac 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -13,6 +13,7 @@ #include "GUIUserMessages.h" #include "ServiceBroker.h" #include "application/Application.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" @@ -171,7 +172,7 @@ bool CVideoResume::IsVisible(const CFileItem& itemIn) const namespace { -void SetPathAndPlay(CFileItem& item) +void SetPathAndPlay(CFileItem& item, const std::string& player) { if (!item.m_bIsFolder && item.IsVideoDb()) { @@ -191,9 +192,24 @@ void SetPathAndPlay(CFileItem& item) const ContentUtils::PlayMode mode = item.GetProperty("CheckAutoPlayNextItem").asBoolean() ? ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM : ContentUtils::PlayMode::PLAY_ONLY_THIS; - VIDEO_UTILS::PlayItem(std::make_shared(item), mode); + VIDEO_UTILS::PlayItem(std::make_shared(item), player, mode); } } + +std::vector GetPlayers(const CPlayerCoreFactory& playerCoreFactory, + const CFileItem& item) +{ + std::vector players; + if (item.IsVideoDb()) + { + const CFileItem item2{item.GetVideoInfoTag()->m_strFileNameAndPath, false}; + playerCoreFactory.GetPlayers(item2, players); + } + else + playerCoreFactory.GetPlayers(item, players); + + return players; +} } // unnamed namespace bool CVideoResume::Execute(const std::shared_ptr& itemIn) const @@ -205,7 +221,7 @@ bool CVideoResume::Execute(const std::shared_ptr& itemIn) const #endif item.SetStartOffset(STARTOFFSET_RESUME); - SetPathAndPlay(item); + SetPathAndPlay(item, ""); return true; }; @@ -231,10 +247,31 @@ bool CVideoPlay::Execute(const std::shared_ptr& itemIn) const if (item.IsDVD() || item.IsCDDA()) return MEDIA_DETECT::CAutorun::PlayDisc(item.GetPath(), true, true); #endif - SetPathAndPlay(item); + SetPathAndPlay(item, ""); return true; }; +bool CVideoPlayUsing::IsVisible(const CFileItem& item) const +{ + const CPlayerCoreFactory& playerCoreFactory{CServiceBroker::GetPlayerCoreFactory()}; + return (GetPlayers(playerCoreFactory, item).size() > 1) && VIDEO_UTILS::IsItemPlayable(item); +} + +bool CVideoPlayUsing::Execute(const std::shared_ptr& itemIn) const +{ + CFileItem item{itemIn->GetItemToPlay()}; + + const CPlayerCoreFactory& playerCoreFactory{CServiceBroker::GetPlayerCoreFactory()}; + const std::vector players{GetPlayers(playerCoreFactory, item)}; + const std::string player{playerCoreFactory.SelectPlayerDialog(players)}; + if (!player.empty()) + { + SetPathAndPlay(item, player); + return true; + } + return false; +} + namespace { void SelectNextItem(int windowID) @@ -254,14 +291,23 @@ void SelectNextItem(int windowID) } } } -} // unnamed namespace -bool CVideoQueue::IsVisible(const CFileItem& item) const +bool CanQueue(const CFileItem& item) { - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) + if (!item.CanQueue()) + return false; + + const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (windowId == WINDOW_VIDEO_PLAYLIST) return false; // Already queued - if (!item.CanQueue()) + return true; +} +} // unnamed namespace + +bool CVideoQueue::IsVisible(const CFileItem& item) const +{ + if (!CanQueue(item)) return false; return VIDEO_UTILS::IsItemPlayable(item); @@ -269,13 +315,10 @@ bool CVideoQueue::IsVisible(const CFileItem& item) const bool CVideoQueue::Execute(const std::shared_ptr& item) const { - const int windowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); - if (windowID == WINDOW_VIDEO_PLAYLIST) - return false; // Already queued - VIDEO_UTILS::QueueItem(item, VIDEO_UTILS::QueuePosition::POSITION_END); // Set selection to next item in active window's view. + const int windowID = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); SelectNextItem(windowID); return true; @@ -283,10 +326,7 @@ bool CVideoQueue::Execute(const std::shared_ptr& item) const bool CVideoPlayNext::IsVisible(const CFileItem& item) const { - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) - return false; // Already queued - - if (!item.CanQueue()) + if (!CanQueue(item)) return false; return VIDEO_UTILS::IsItemPlayable(item); @@ -294,9 +334,6 @@ bool CVideoPlayNext::IsVisible(const CFileItem& item) const bool CVideoPlayNext::Execute(const std::shared_ptr& item) const { - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) - return false; // Already queued - VIDEO_UTILS::QueueItem(item, VIDEO_UTILS::QueuePosition::POSITION_BEGIN); return true; }; @@ -311,10 +348,10 @@ std::string CVideoPlayAndQueue::GetLabel(const CFileItem& item) const bool CVideoPlayAndQueue::IsVisible(const CFileItem& item) const { - const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); - if (windowId == WINDOW_VIDEO_PLAYLIST) - return false; // Already queued + if (!CanQueue(item)) + return false; + const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); if ((windowId == WINDOW_TV_RECORDINGS || windowId == WINDOW_RADIO_RECORDINGS) && item.IsUsablePVRRecording()) return true; @@ -325,16 +362,13 @@ bool CVideoPlayAndQueue::IsVisible(const CFileItem& item) const bool CVideoPlayAndQueue::Execute(const std::shared_ptr& item) const { const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); - if (windowId == WINDOW_VIDEO_PLAYLIST) - return false; // Already queued - if ((windowId == WINDOW_TV_RECORDINGS || windowId == WINDOW_RADIO_RECORDINGS) && item->IsUsablePVRRecording()) { const ContentUtils::PlayMode mode = VIDEO_UTILS::IsAutoPlayNextItem(*item) ? ContentUtils::PlayMode::PLAY_ONLY_THIS : ContentUtils::PlayMode::PLAY_FROM_HERE; - VIDEO_UTILS::PlayItem(item, mode); + VIDEO_UTILS::PlayItem(item, "", mode); return true; } diff --git a/xbmc/video/ContextMenus.h b/xbmc/video/ContextMenus.h index a53c7c2217fe7..e220670b02ffb 100644 --- a/xbmc/video/ContextMenus.h +++ b/xbmc/video/ContextMenus.h @@ -100,6 +100,13 @@ struct CVideoPlay : IContextMenuItem bool Execute(const std::shared_ptr& _item) const override; }; +struct CVideoPlayUsing : CStaticContextMenuAction +{ + CVideoPlayUsing() : CStaticContextMenuAction(15213) {} // Play using... + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& _item) const override; +}; + struct CVideoQueue : CStaticContextMenuAction { CVideoQueue() : CStaticContextMenuAction(13347) {} // Queue item diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index b10c0f6a25a5c..2b30b2e57221d 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -338,7 +338,8 @@ std::string GetVideoDbItemPath(const CFileItem& item) } void AddItemToPlayListAndPlay(const std::shared_ptr& itemToQueue, - const std::shared_ptr& itemToPlay) + const std::shared_ptr& itemToPlay, + const std::string& player) { // recursively add items to list CFileItemList queuedItems; @@ -370,7 +371,7 @@ void AddItemToPlayListAndPlay(const std::shared_ptr& itemToQueue, } playlistPlayer.SetCurrentPlaylist(PLAYLIST::TYPE_VIDEO); - playlistPlayer.Play(pos, ""); + playlistPlayer.Play(pos, player); } } // unnamed namespace @@ -406,6 +407,7 @@ bool IsAutoPlayNextItem(const std::string& content) void PlayItem( const std::shared_ptr& itemIn, + const std::string& player, ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_VIDEO */) { auto item = itemIn; @@ -421,7 +423,7 @@ void PlayItem( if (item->m_bIsFolder && !item->IsPlugin()) { - AddItemToPlayListAndPlay(item, nullptr); + AddItemToPlayListAndPlay(item, nullptr, player); } else if (item->HasVideoInfoTag()) { @@ -450,7 +452,7 @@ void PlayItem( if (item->GetStartOffset() == STARTOFFSET_RESUME) parentItem->SetStartOffset(STARTOFFSET_RESUME); - AddItemToPlayListAndPlay(parentItem, item); + AddItemToPlayListAndPlay(parentItem, item, player); } else // mode == PlayMode::PLAY_ONLY_THIS { @@ -458,7 +460,7 @@ void PlayItem( auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer(); playlistPlayer.Reset(); playlistPlayer.SetCurrentPlaylist(PLAYLIST::TYPE_NONE); - playlistPlayer.Play(item, ""); + playlistPlayer.Play(item, player); } } } @@ -540,10 +542,6 @@ bool IsItemPlayable(const CFileItem& item) if (item.IsPlugin() || item.IsScript() || item.IsAddonsPath()) return false; - // Exclude unwanted windows - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VIDEO_PLAYLIST) - return false; - // Exclude special items if (StringUtils::StartsWithNoCase(item.GetPath(), "newsmartplaylist://") || StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://") || diff --git a/xbmc/video/VideoUtils.h b/xbmc/video/VideoUtils.h index a55de50446b39..aebb637d9dbe3 100644 --- a/xbmc/video/VideoUtils.h +++ b/xbmc/video/VideoUtils.h @@ -33,9 +33,11 @@ bool IsAutoPlayNextItem(const std::string& content); all items contained in the folder and start playback of the playlist. If item is a single video item, start playback directly, without adding it to the video playlist first. \param item [in] the item to play + \param player [in] the player to use, empty for default player \param mode [in] queue all successors and play them after item */ void PlayItem(const std::shared_ptr& item, + const std::string& player, ContentUtils::PlayMode mode = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM); enum class QueuePosition diff --git a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp index cc0b4965e2f48..5f7dde3941d4b 100644 --- a/xbmc/video/dialogs/GUIDialogVideoInfo.cpp +++ b/xbmc/video/dialogs/GUIDialogVideoInfo.cpp @@ -771,7 +771,7 @@ void CGUIDialogVideoInfo::Play(bool resume) const ContentUtils::PlayMode mode = m_movieItem->GetProperty("CheckAutoPlayNextItem").asBoolean() ? ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM : ContentUtils::PlayMode::PLAY_ONLY_THIS; - VIDEO_UTILS::PlayItem(m_movieItem, mode); + VIDEO_UTILS::PlayItem(m_movieItem, "", mode); } namespace diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index fa747704b7102..53b9dcccd87cc 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -20,7 +20,6 @@ #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" -#include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogProgress.h" #include "dialogs/GUIDialogSelect.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" @@ -796,23 +795,6 @@ void CGUIWindowVideoBase::GetContextButtons(int itemNumber, CContextButtons &but } } - if (!item->m_bIsFolder && !(item->IsPlayList() && !CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) - { - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - // get players - std::vector players; - if (item->IsVideoDb()) - { - CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false); - playerCoreFactory.GetPlayers(item2, players); - } - else - playerCoreFactory.GetPlayers(*item, players); - - if (players.size() > 1) - buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); - } if (item->IsSmartPlayList()) { buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode @@ -904,35 +886,6 @@ bool CGUIWindowVideoBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) { return OnFileAction(itemNumber, SELECT_ACTION_PLAYPART, ""); } - case CONTEXT_BUTTON_PLAY_WITH: - { - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - std::vector players; - if (item->IsVideoDb()) - { - CFileItem item2(*item->GetVideoInfoTag()); - playerCoreFactory.GetPlayers(item2, players); - } - else - playerCoreFactory.GetPlayers(*item, players); - - std:: string player = playerCoreFactory.SelectPlayerDialog(players); - if (!player.empty()) - { - // any other select actions but play or resume, resume, play or playpart - // don't make any sense here since the user already decided that he'd - // like to play the item (just with a specific player) - SelectAction selectAction = CVideoSelectActionProcessorBase::GetDefaultSelectAction(); - if (selectAction != SELECT_ACTION_PLAY_OR_RESUME && - selectAction != SELECT_ACTION_RESUME && - selectAction != SELECT_ACTION_PLAY && - selectAction != SELECT_ACTION_PLAYPART) - selectAction = SELECT_ACTION_PLAY_OR_RESUME; - return OnFileAction(itemNumber, selectAction, player); - } - return true; - } case CONTEXT_BUTTON_PLAY_PARTYMODE: g_partyModeManager.Enable(PARTYMODECONTEXT_VIDEO, m_vecItems->Get(itemNumber)->GetPath()); diff --git a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp index d50d0f2ec2806..1af835c793aea 100644 --- a/xbmc/video/windows/GUIWindowVideoPlaylist.cpp +++ b/xbmc/video/windows/GUIWindowVideoPlaylist.cpp @@ -15,7 +15,6 @@ #include "Util.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" -#include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" @@ -447,25 +446,6 @@ void CGUIWindowVideoPlaylist::GetContextButtons(int itemNumber, CContextButtons& } else { - if (itemNumber > -1) - { - CFileItemPtr item = m_vecItems->Get(itemNumber); - - const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - // check what players we have, if we have multiple display play with option - std::vector players; - if (item->IsVideoDb()) - { - CFileItem item2(item->GetVideoInfoTag()->m_strFileNameAndPath, false); - playerCoreFactory.GetPlayers(item2, players); - } - else - playerCoreFactory.GetPlayers(*item, players); - - if (players.size() > 1) - buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... - } if (itemNumber > (g_partyModeManager.IsEnabled() ? 1 : 0)) buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332); if (itemNumber + 1 < m_vecItems->Size()) @@ -487,31 +467,6 @@ bool CGUIWindowVideoPlaylist::OnContextButton(int itemNumber, CONTEXT_BUTTON but { switch (button) { - case CONTEXT_BUTTON_PLAY_WITH: - { - CFileItemPtr item; - if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) - item = m_vecItems->Get(itemNumber); - if (!item) - break; - - const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - std::vector players; - if (item->IsVideoDb()) - { - CFileItem item2(*item->GetVideoInfoTag()); - playerCoreFactory.GetPlayers(item2, players); - } - else - playerCoreFactory.GetPlayers(*item, players); - - std::string player = playerCoreFactory.SelectPlayerDialog(players); - if (!player.empty()) - OnClick(itemNumber, player); - return true; - } - case CONTEXT_BUTTON_MOVE_ITEM: m_movingFrom = itemNumber; return true; From f7b244534ef4e623e53f21a24c7363a3d723d03e Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 9 Oct 2023 15:11:25 +0200 Subject: [PATCH 388/811] [music] Migrate 'Play using...' to "new" context menu system. --- .../resources/strings.po | 1 + xbmc/ContextMenuManager.cpp | 1 + xbmc/dialogs/GUIDialogContextMenu.h | 1 - xbmc/music/ContextMenus.cpp | 56 +++++++++++++++++-- xbmc/music/ContextMenus.h | 7 +++ xbmc/music/MusicUtils.cpp | 16 +++--- xbmc/music/MusicUtils.h | 2 + xbmc/music/dialogs/GUIDialogMusicInfo.cpp | 2 +- xbmc/music/dialogs/GUIDialogSongInfo.cpp | 2 +- xbmc/music/windows/GUIWindowMusicBase.cpp | 26 --------- xbmc/music/windows/GUIWindowMusicPlaylist.cpp | 27 --------- 11 files changed, 72 insertions(+), 69 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index f99fbe0d9620c..ecffd7de138f5 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -8773,6 +8773,7 @@ msgstr "" #empty strings from id 15209 to 15212 #. Label for context menu action to choose a player and play +#: xbmc/music/ContextMenus.h #: xbmc/video/ContextMenus.h #: xbmc/windows/GUIWindowFileManager.cpp msgctxt "#15213" diff --git a/xbmc/ContextMenuManager.cpp b/xbmc/ContextMenuManager.cpp index 75a5d057cb7b2..25e43342920c7 100644 --- a/xbmc/ContextMenuManager.cpp +++ b/xbmc/ContextMenuManager.cpp @@ -70,6 +70,7 @@ void CContextMenuManager::Init() std::make_shared(), std::make_shared(), std::make_shared(), + std::make_shared(), std::make_shared(), std::make_shared(), std::make_shared(), diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h index 8389449f94bb3..6527ab86f738d 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.h +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -50,7 +50,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_CLEAR, CONTEXT_BUTTON_QUEUE_ITEM, CONTEXT_BUTTON_PLAY_ITEM, - CONTEXT_BUTTON_PLAY_WITH, CONTEXT_BUTTON_PLAY_PARTYMODE, CONTEXT_BUTTON_PLAY_PART, CONTEXT_BUTTON_EDIT, diff --git a/xbmc/music/ContextMenus.cpp b/xbmc/music/ContextMenus.cpp index 76c5c69334284..c9b57d90a07de 100644 --- a/xbmc/music/ContextMenus.cpp +++ b/xbmc/music/ContextMenus.cpp @@ -11,6 +11,7 @@ #include "FileItem.h" #include "GUIUserMessages.h" #include "ServiceBroker.h" +#include "cores/playercorefactory/PlayerCoreFactory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "music/MusicUtils.h" @@ -70,20 +71,67 @@ bool CMusicPlay::IsVisible(const CFileItem& item) const return MUSIC_UTILS::IsItemPlayable(item); } -bool CMusicPlay::Execute(const std::shared_ptr& item) const +namespace +{ +void Play(const std::shared_ptr& item, const std::string& player) { item->SetProperty("playlist_type_hint", PLAYLIST::TYPE_MUSIC); const ContentUtils::PlayMode mode = item->GetProperty("CheckAutoPlayNextItem").asBoolean() ? ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM : ContentUtils::PlayMode::PLAY_ONLY_THIS; - MUSIC_UTILS::PlayItem(item, mode); + MUSIC_UTILS::PlayItem(item, player, mode); +} + +std::vector GetPlayers(const CPlayerCoreFactory& playerCoreFactory, + const CFileItem& item) +{ + std::vector players; + playerCoreFactory.GetPlayers(item, players); + return players; +} + +bool CanQueue(const CFileItem& item) +{ + if (!item.CanQueue()) + return false; + + const int windowId = CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow(); + if (windowId == WINDOW_MUSIC_PLAYLIST) + return false; // Already queued + return true; } +} // unnamed namespace + +bool CMusicPlay::Execute(const std::shared_ptr& item) const +{ + Play(item, ""); + return true; +} + +bool CMusicPlayUsing::IsVisible(const CFileItem& item) const +{ + const CPlayerCoreFactory& playerCoreFactory{CServiceBroker::GetPlayerCoreFactory()}; + return (GetPlayers(playerCoreFactory, item).size() > 1) && MUSIC_UTILS::IsItemPlayable(item); +} + +bool CMusicPlayUsing::Execute(const std::shared_ptr& item) const +{ + const CPlayerCoreFactory& playerCoreFactory{CServiceBroker::GetPlayerCoreFactory()}; + const std::vector players{GetPlayers(playerCoreFactory, *item)}; + const std::string player{playerCoreFactory.SelectPlayerDialog(players)}; + if (!player.empty()) + { + Play(item, player); + return true; + } + return false; +} bool CMusicPlayNext::IsVisible(const CFileItem& item) const { - if (!item.CanQueue()) + if (!CanQueue(item)) return false; return MUSIC_UTILS::IsItemPlayable(item); @@ -97,7 +145,7 @@ bool CMusicPlayNext::Execute(const std::shared_ptr& item) const bool CMusicQueue::IsVisible(const CFileItem& item) const { - if (!item.CanQueue()) + if (!CanQueue(item)) return false; return MUSIC_UTILS::IsItemPlayable(item); diff --git a/xbmc/music/ContextMenus.h b/xbmc/music/ContextMenus.h index 73275923fcc99..d96da33213668 100644 --- a/xbmc/music/ContextMenus.h +++ b/xbmc/music/ContextMenus.h @@ -57,6 +57,13 @@ struct CMusicPlay : CStaticContextMenuAction bool Execute(const std::shared_ptr& item) const override; }; +struct CMusicPlayUsing : CStaticContextMenuAction +{ + CMusicPlayUsing() : CStaticContextMenuAction(15213) {} // Play using... + bool IsVisible(const CFileItem& item) const override; + bool Execute(const std::shared_ptr& _item) const override; +}; + struct CMusicPlayNext : CStaticContextMenuAction { CMusicPlayNext() : CStaticContextMenuAction(10008) {} // Play next diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index f3318996c6763..0ebee30c01c65 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -653,7 +653,8 @@ std::string GetMusicDbItemPath(const CFileItem& item) } void AddItemToPlayListAndPlay(const std::shared_ptr& itemToQueue, - const std::shared_ptr& itemToPlay) + const std::shared_ptr& itemToPlay, + const std::string& player) { // recursively add items to list CFileItemList queuedItems; @@ -685,7 +686,7 @@ void AddItemToPlayListAndPlay(const std::shared_ptr& itemToQueue, } playlistPlayer.SetCurrentPlaylist(PLAYLIST::TYPE_MUSIC); - playlistPlayer.Play(pos, ""); + playlistPlayer.Play(pos, player); } } // unnamed namespace @@ -702,6 +703,7 @@ bool IsAutoPlayNextItem(const CFileItem& item) } void PlayItem(const std::shared_ptr& itemIn, + const std::string& player, ContentUtils::PlayMode mode /* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */) { auto item = itemIn; @@ -717,7 +719,7 @@ void PlayItem(const std::shared_ptr& itemIn, if (item->m_bIsFolder) { - AddItemToPlayListAndPlay(item, nullptr); + AddItemToPlayListAndPlay(item, nullptr, player); } else if (item->HasMusicInfoTag()) { @@ -746,7 +748,7 @@ void PlayItem(const std::shared_ptr& itemIn, if (item->GetStartOffset() == STARTOFFSET_RESUME) parentItem->SetStartOffset(STARTOFFSET_RESUME); - AddItemToPlayListAndPlay(parentItem, item); + AddItemToPlayListAndPlay(parentItem, item, player); } else // mode == PlayMode::PLAY_ONLY_THIS { @@ -754,7 +756,7 @@ void PlayItem(const std::shared_ptr& itemIn, auto& playlistPlayer = CServiceBroker::GetPlaylistPlayer(); playlistPlayer.Reset(); playlistPlayer.SetCurrentPlaylist(PLAYLIST::TYPE_NONE); - playlistPlayer.Play(item, ""); + playlistPlayer.Play(item, player); } } } @@ -868,10 +870,6 @@ bool IsItemPlayable(const CFileItem& item) StringUtils::StartsWithNoCase(item.GetPath(), "newplaylist://")) return false; - // Exclude unwanted windows - if (CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_MUSIC_PLAYLIST) - return false; - // Include playlists located at one of the possible music playlist locations if (item.IsPlayList()) { diff --git a/xbmc/music/MusicUtils.h b/xbmc/music/MusicUtils.h index 5505b61c6e36e..cd28ff5f27020 100644 --- a/xbmc/music/MusicUtils.h +++ b/xbmc/music/MusicUtils.h @@ -91,9 +91,11 @@ bool IsAutoPlayNextItem(const CFileItem& item); all items contained in the folder and start playback of the playlist. If item is a single music item, start playback directly, without adding it to the music playlist first. \param item [in] the item to play + \param player [in] the player to use, empty for default player \param mode [in] queue all successors and play them after item */ void PlayItem(const std::shared_ptr& item, + const std::string& player, ContentUtils::PlayMode mode = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM); enum class QueuePosition diff --git a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp index a4b282a5630ff..a7dca1fc3ef4c 100644 --- a/xbmc/music/dialogs/GUIDialogMusicInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogMusicInfo.cpp @@ -1039,5 +1039,5 @@ void CGUIDialogMusicInfo::ShowFor(CFileItem* pItem) void CGUIDialogMusicInfo::OnPlayItem(const std::shared_ptr& item) { Close(true); - MUSIC_UTILS::PlayItem(item); + MUSIC_UTILS::PlayItem(item, ""); } diff --git a/xbmc/music/dialogs/GUIDialogSongInfo.cpp b/xbmc/music/dialogs/GUIDialogSongInfo.cpp index 8efca94ab4e2c..70d02f96e8a22 100644 --- a/xbmc/music/dialogs/GUIDialogSongInfo.cpp +++ b/xbmc/music/dialogs/GUIDialogSongInfo.cpp @@ -519,5 +519,5 @@ void CGUIDialogSongInfo::ShowFor(CFileItem* pItem) void CGUIDialogSongInfo::OnPlaySong(const std::shared_ptr& item) { Close(true); - MUSIC_UTILS::PlayItem(item); + MUSIC_UTILS::PlayItem(item, ""); } diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index fc893beed321b..b3cff68c93002 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -35,7 +35,6 @@ #include "PartyModeManager.h" #include "URL.h" #include "addons/gui/GUIDialogAddonInfo.h" -#include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogProgress.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" #include "dialogs/GUIDialogYesNo.h" @@ -442,19 +441,6 @@ void CGUIWindowMusicBase::GetContextButtons(int itemNumber, CContextButtons &but //! @todo get rid of IsAddonsPath and IsScript check. CanQueue should be enough! if (item->CanQueue() && !item->IsAddonsPath() && !item->IsScript()) { - if (!item->m_bIsFolder && - (!item->IsPlayList() || - CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_playlistAsFolders)) - { - const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - // check what players we have, if we have multiple display play with option - std::vector players; - playerCoreFactory.GetPlayers(*item, players); - if (players.size() >= 1) - buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... - } - if (item->IsSmartPlayList()) buttons.Add(CONTEXT_BUTTON_PLAY_PARTYMODE, 15216); // Play in Partymode @@ -527,18 +513,6 @@ bool CGUIWindowMusicBase::OnContextButton(int itemNumber, CONTEXT_BUTTON button) return true; } - case CONTEXT_BUTTON_PLAY_WITH: - { - const CPlayerCoreFactory &playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - std::vector players; - playerCoreFactory.GetPlayers(*item, players); - std::string player = playerCoreFactory.SelectPlayerDialog(players); - if (!player.empty()) - OnClick(itemNumber, player); - return true; - } - case CONTEXT_BUTTON_PLAY_PARTYMODE: g_partyModeManager.Enable(PARTYMODECONTEXT_MUSIC, item->GetPath()); return true; diff --git a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp index 6ae3abb9f0e3c..f3ecb728479cb 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylist.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylist.cpp @@ -17,7 +17,6 @@ #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" -#include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" #include "guilib/GUIComponent.h" #include "guilib/GUIKeyboardFactory.h" @@ -554,15 +553,6 @@ void CGUIWindowMusicPlayList::GetContextButtons(int itemNumber, CContextButtons& } else { - const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - // aren't in a move - // check what players we have, if we have multiple display play with option - std::vector players; - playerCoreFactory.GetPlayers(*item, players); - if (players.size() > 1) - buttons.Add(CONTEXT_BUTTON_PLAY_WITH, 15213); // Play With... - if (itemNumber > (g_partyModeManager.IsEnabled() ? 1 : 0)) buttons.Add(CONTEXT_BUTTON_MOVE_ITEM_UP, 13332); if (itemNumber + 1 < m_vecItems->Size()) @@ -585,23 +575,6 @@ bool CGUIWindowMusicPlayList::OnContextButton(int itemNumber, CONTEXT_BUTTON but { switch (button) { - case CONTEXT_BUTTON_PLAY_WITH: - { - CFileItemPtr item; - if (itemNumber >= 0 && itemNumber < m_vecItems->Size()) - item = m_vecItems->Get(itemNumber); - if (!item) - break; - - const CPlayerCoreFactory& playerCoreFactory = CServiceBroker::GetPlayerCoreFactory(); - - std::vector players; - playerCoreFactory.GetPlayers(*item, players); - std::string player = playerCoreFactory.SelectPlayerDialog(players); - if (!player.empty()) - OnClick(itemNumber, player); - return true; - } case CONTEXT_BUTTON_MOVE_ITEM: m_movingFrom = itemNumber; return true; From 2236ff43fd3ccb798484f0712a8d43187d2e6d15 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 11 Oct 2023 00:06:35 +0200 Subject: [PATCH 389/811] [cores] Fix duplicate entries in player selection dialog on 'Play using...' context menu item selection. --- .../playercorefactory/PlayerCoreFactory.cpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp index 419d34fd18f59..0d01c13cf1343 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp @@ -146,10 +146,15 @@ void CPlayerCoreFactory::GetPlayers(const CFileItem& item, std::vector -1) { - std::string eVideoDefault = GetPlayerName(idx); + const std::string videoDefault = GetPlayerName(idx); + const auto it = std::find(players.cbegin(), players.cend(), videoDefault); + if (it != players.cend()) + players.erase(it); + CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding videodefaultplayer ({})", - eVideoDefault); - players.push_back(eVideoDefault); + videoDefault); + // default player must always be first vector entry + players.insert(players.begin(), videoDefault); } GetPlayers(players, false, true); // Video-only players GetPlayers(players, true, true); // Audio & video players @@ -163,10 +168,15 @@ void CPlayerCoreFactory::GetPlayers(const CFileItem& item, std::vector -1) { - std::string eAudioDefault = GetPlayerName(idx); + const std::string audioDefault = GetPlayerName(idx); + const auto it = std::find(players.cbegin(), players.cend(), audioDefault); + if (it != players.cend()) + players.erase(it); + CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding audiodefaultplayer ({})", - eAudioDefault); - players.push_back(eAudioDefault); + audioDefault); + // default player must always be first vector entry + players.insert(players.begin(), audioDefault); } GetPlayers(players, true, false); // Audio-only players GetPlayers(players, true, true); // Audio & video players From 066f7460dd11eea86812975665befd0eb0298b09 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 12 Oct 2023 09:00:24 +1000 Subject: [PATCH 390/811] [cmake] FindIconv dont add target if libc is the detected provider if iconv --- cmake/modules/FindIconv.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmake/modules/FindIconv.cmake b/cmake/modules/FindIconv.cmake index 67230c897b3d3..1e9b54fd5ad77 100644 --- a/cmake/modules/FindIconv.cmake +++ b/cmake/modules/FindIconv.cmake @@ -29,10 +29,14 @@ if(NOT TARGET ICONV::ICONV) REQUIRED_VARS ICONV_LIBRARY ICONV_INCLUDE_DIR HAVE_ICONV_FUNCTION) if(ICONV_FOUND) - add_library(ICONV::ICONV UNKNOWN IMPORTED) - set_target_properties(ICONV::ICONV PROPERTIES - IMPORTED_LOCATION "${ICONV_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${ICONV_INCLUDE_DIR}") - set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ICONV::ICONV) + # Libc causes grief for linux, so search if found library is libc.* and only + # create imported TARGET if its not + if(NOT ${ICONV_LIBRARY} MATCHES ".*libc\..*") + add_library(ICONV::ICONV UNKNOWN IMPORTED) + set_target_properties(ICONV::ICONV PROPERTIES + IMPORTED_LOCATION "${ICONV_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${ICONV_INCLUDE_DIR}") + set_property(GLOBAL APPEND PROPERTY INTERNAL_DEPS_PROP ICONV::ICONV) + endif() endif() endif() From 38e81ee32bc65d9e12975487451e9f411805ec85 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:20:04 +0200 Subject: [PATCH 391/811] [video][music] Migrate last pieces of 'Browse into' to new context menu system. --- .../resources/strings.po | 2 +- xbmc/dialogs/GUIDialogContextMenu.h | 2 -- xbmc/music/ContextMenus.cpp | 14 ++++++---- .../windows/GUIWindowMusicPlaylistEditor.cpp | 3 ++ xbmc/video/ContextMenus.cpp | 14 ++++++---- xbmc/windows/GUIMediaWindow.cpp | 28 ------------------- xbmc/windows/GUIMediaWindow.h | 4 +-- 7 files changed, 22 insertions(+), 45 deletions(-) diff --git a/addons/resource.language.en_gb/resources/strings.po b/addons/resource.language.en_gb/resources/strings.po index ecffd7de138f5..557d2f7f04c41 100644 --- a/addons/resource.language.en_gb/resources/strings.po +++ b/addons/resource.language.en_gb/resources/strings.po @@ -21949,7 +21949,7 @@ msgstr "" #: addons/skin.estuary/xml/Variables.xml #: xbmc/favourites/ContextMenus.h #: xbmc/music/ContextMenus.h -#: xbmc/windows/GUIMediaWindow.cpp +#: xbmc/video/ContextMenus.h msgctxt "#37015" msgid "Browse into" msgstr "" diff --git a/xbmc/dialogs/GUIDialogContextMenu.h b/xbmc/dialogs/GUIDialogContextMenu.h index 6527ab86f738d..cb4c8b0980c8e 100644 --- a/xbmc/dialogs/GUIDialogContextMenu.h +++ b/xbmc/dialogs/GUIDialogContextMenu.h @@ -48,7 +48,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_MOVE_ITEM_UP, CONTEXT_BUTTON_MOVE_ITEM_DOWN, CONTEXT_BUTTON_CLEAR, - CONTEXT_BUTTON_QUEUE_ITEM, CONTEXT_BUTTON_PLAY_ITEM, CONTEXT_BUTTON_PLAY_PARTYMODE, CONTEXT_BUTTON_PLAY_PART, @@ -83,7 +82,6 @@ enum CONTEXT_BUTTON CONTEXT_BUTTON_TAGS_REMOVE_ITEMS, CONTEXT_BUTTON_SET_MOVIESET, CONTEXT_BUTTON_MOVIESET_ADD_REMOVE_ITEMS, - CONTEXT_BUTTON_BROWSE_INTO, CONTEXT_BUTTON_EDIT_SORTTITLE, CONTEXT_BUTTON_DELETE_ALL, CONTEXT_BUTTON_HELP, diff --git a/xbmc/music/ContextMenus.cpp b/xbmc/music/ContextMenus.cpp index c9b57d90a07de..9d146083bce93 100644 --- a/xbmc/music/ContextMenus.cpp +++ b/xbmc/music/ContextMenus.cpp @@ -44,24 +44,26 @@ bool CMusicInfo::Execute(const std::shared_ptr& item) const bool CMusicBrowse::IsVisible(const CFileItem& item) const { - if (item.IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) - return false; // handled by CMediaWindow - - return item.m_bIsFolder && MUSIC_UTILS::IsItemPlayable(item); + return ((item.m_bIsFolder || item.IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) && + MUSIC_UTILS::IsItemPlayable(item)); } bool CMusicBrowse::Execute(const std::shared_ptr& item) const { + // For file directory browsing, we need item's dyn path, for everything else the path. + const std::string path{item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE) ? item->GetDynPath() + : item->GetPath()}; + auto& windowMgr = CServiceBroker::GetGUI()->GetWindowManager(); if (windowMgr.GetActiveWindow() == WINDOW_MUSIC_NAV) { CGUIMessage msg(GUI_MSG_NOTIFY_ALL, WINDOW_MUSIC_NAV, 0, GUI_MSG_UPDATE); - msg.SetStringParam(item->GetPath()); + msg.SetStringParam(path); windowMgr.SendMessage(msg); } else { - windowMgr.ActivateWindow(WINDOW_MUSIC_NAV, {item->GetPath(), "return"}); + windowMgr.ActivateWindow(WINDOW_MUSIC_NAV, {path, "return"}); } return true; } diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp index a6ceec2577aef..aeed68e059c8c 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp @@ -404,6 +404,9 @@ void CGUIWindowMusicPlaylistEditor::AppendToPlaylist(CFileItemList &newItems) void CGUIWindowMusicPlaylistEditor::OnSourcesContext() { + static constexpr int CONTEXT_BUTTON_QUEUE_ITEM = 0; + static constexpr int CONTEXT_BUTTON_BROWSE_INTO = 1; + CFileItemPtr item = GetCurrentListItem(); CContextButtons buttons; if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) diff --git a/xbmc/video/ContextMenus.cpp b/xbmc/video/ContextMenus.cpp index 25ae6f0526dac..9143baa9dd2d8 100644 --- a/xbmc/video/ContextMenus.cpp +++ b/xbmc/video/ContextMenus.cpp @@ -125,10 +125,8 @@ bool CVideoMarkUnWatched::Execute(const std::shared_ptr& item) const bool CVideoBrowse::IsVisible(const CFileItem& item) const { - if (item.IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) - return false; // handled by CMediaWindow - - return item.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(item); + return ((item.m_bIsFolder || item.IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) && + VIDEO_UTILS::IsItemPlayable(item)); } bool CVideoBrowse::Execute(const std::shared_ptr& item) const @@ -143,15 +141,19 @@ bool CVideoBrowse::Execute(const std::shared_ptr& item) const auto& windowMgr = CServiceBroker::GetGUI()->GetWindowManager(); + // For file directory browsing, we need item's dyn path, for everything else the path. + const std::string path{item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE) ? item->GetDynPath() + : item->GetPath()}; + if (target == windowMgr.GetActiveWindow()) { CGUIMessage msg(GUI_MSG_NOTIFY_ALL, target, 0, GUI_MSG_UPDATE); - msg.SetStringParam(item->GetPath()); + msg.SetStringParam(path); windowMgr.SendMessage(msg); } else { - windowMgr.ActivateWindow(target, {item->GetPath(), "return"}); + windowMgr.ActivateWindow(target, {path, "return"}); } return true; } diff --git a/xbmc/windows/GUIMediaWindow.cpp b/xbmc/windows/GUIMediaWindow.cpp index 3776be36fe85c..b70a7b59eb39e 100644 --- a/xbmc/windows/GUIMediaWindow.cpp +++ b/xbmc/windows/GUIMediaWindow.cpp @@ -1818,34 +1818,6 @@ bool CGUIMediaWindow::OnPopupMenu(int itemIdx) return CONTEXTMENU::LoopFrom(*addonMenu[idx - addonMenuRange.first], item); } -void CGUIMediaWindow::GetContextButtons(int itemNumber, CContextButtons &buttons) -{ - CFileItemPtr item = (itemNumber >= 0 && itemNumber < m_vecItems->Size()) ? m_vecItems->Get(itemNumber) : CFileItemPtr(); - - if (!item || item->IsParentFolder()) - return; - - if (item->IsFileFolder(EFILEFOLDER_MASK_ONBROWSE)) - buttons.Add(CONTEXT_BUTTON_BROWSE_INTO, 37015); - -} - -bool CGUIMediaWindow::OnContextButton(int itemNumber, CONTEXT_BUTTON button) -{ - switch (button) - { - case CONTEXT_BUTTON_BROWSE_INTO: - { - CFileItemPtr item = m_vecItems->Get(itemNumber); - Update(item->GetDynPath()); - return true; - } - default: - break; - } - return false; -} - const CGUIViewState *CGUIMediaWindow::GetViewState() const { return m_guiState.get(); diff --git a/xbmc/windows/GUIMediaWindow.h b/xbmc/windows/GUIMediaWindow.h index 1fc1dfc1650be..6de5e2b320679 100644 --- a/xbmc/windows/GUIMediaWindow.h +++ b/xbmc/windows/GUIMediaWindow.h @@ -75,8 +75,8 @@ class CGUIMediaWindow : public CGUIWindow virtual bool OnSelect(int item); virtual bool OnPopupMenu(int iItem); - virtual void GetContextButtons(int itemNumber, CContextButtons &buttons); - virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button); + virtual void GetContextButtons(int itemNumber, CContextButtons& buttons) {} + virtual bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) { return false; } virtual bool OnAddMediaSource() { return false; } virtual void FormatItemLabels(CFileItemList &items, const LABEL_MASKS &labelMasks); From 95dcedfc8811280243c9bf3a5d3d17611e256e57 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Wed, 11 Oct 2023 14:25:28 +0200 Subject: [PATCH 392/811] [music] CGUIWindowMusicPlaylistEditor: Remove dead code. --- .../windows/GUIWindowMusicPlaylistEditor.cpp | 15 --------------- xbmc/music/windows/GUIWindowMusicPlaylistEditor.h | 3 +-- 2 files changed, 1 insertion(+), 17 deletions(-) diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp index aeed68e059c8c..abece9c660adc 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.cpp @@ -440,18 +440,3 @@ void CGUIWindowMusicPlaylistEditor::OnPlaylistContext() else if (btnid == CONTEXT_BUTTON_DELETE) OnDeletePlaylistItem(item); } - -bool CGUIWindowMusicPlaylistEditor::OnContextButton(int itemNumber, CONTEXT_BUTTON button) -{ - switch (button) - { - case CONTEXT_BUTTON_QUEUE_ITEM: - OnQueueItem(itemNumber); - return true; - - default: - break; - } - - return CGUIWindowMusicBase::OnContextButton(itemNumber, button); -} diff --git a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h index 3859b556e8c93..8e7185921dcbf 100644 --- a/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h +++ b/xbmc/music/windows/GUIWindowMusicPlaylistEditor.h @@ -27,8 +27,7 @@ class CGUIWindowMusicPlaylistEditor : public CGUIWindowMusicBase bool GetDirectory(const std::string &strDirectory, CFileItemList &items) override; void UpdateButtons() override; bool Update(const std::string &strDirectory, bool updateFilterPath = true) override; - void OnPrepareFileItems(CFileItemList &items) override; - bool OnContextButton(int itemNumber, CONTEXT_BUTTON button) override; + void OnPrepareFileItems(CFileItemList& items) override; void OnQueueItem(int iItem, bool first = false) override; std::string GetStartFolder(const std::string& dir) override { return ""; } From c340bbd6fba5584488c07fe0069694e072fc608f Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Thu, 12 Oct 2023 19:37:15 +0100 Subject: [PATCH 393/811] [upnp] clang-format UPnPInternal.cpp --- xbmc/network/upnp/UPnPInternal.cpp | 1758 +++++++++++++++------------- 1 file changed, 947 insertions(+), 811 deletions(-) diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index 4a7173a9a521e..19f1cfbe03ad1 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -88,25 +88,27 @@ constexpr NPT_HttpFileRequestHandler_DefaultFileTypeMapEntry kodiPlatinumMimeTyp +---------------------------------------------------------------------*/ EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context) { - if(context == NULL) - return ECLIENTQUIRKS_NONE; + if (context == NULL) + return ECLIENTQUIRKS_NONE; unsigned int quirks = 0; - const NPT_String* user_agent = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT); - const NPT_String* server = context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER); + const NPT_String* user_agent = + context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_USER_AGENT); + const NPT_String* server = + context->GetRequest().GetHeaders().GetHeaderValue(NPT_HTTP_HEADER_SERVER); - if (user_agent) { - if (user_agent->Find("XBox", 0, true) >= 0 || - user_agent->Find("Xenon", 0, true) >= 0) - quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS; - - if (user_agent->Find("Windows-Media-Player", 0, true) >= 0) - quirks |= ECLIENTQUIRKS_UNKNOWNSERIES; + if (user_agent) + { + if (user_agent->Find("XBox", 0, true) >= 0 || user_agent->Find("Xenon", 0, true) >= 0) + quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS; + if (user_agent->Find("Windows-Media-Player", 0, true) >= 0) + quirks |= ECLIENTQUIRKS_UNKNOWNSERIES; } - if (server) { - if (server->Find("Xbox", 0, true) >= 0) - quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS; + if (server) + { + if (server->Find("Xbox", 0, true) >= 0) + quirks |= ECLIENTQUIRKS_ONLYSTORAGEFOLDER | ECLIENTQUIRKS_BASICVIDEOCLASS; } return (EClientQuirks)quirks; @@ -115,327 +117,361 @@ EClientQuirks GetClientQuirks(const PLT_HttpRequestContext* context) /*---------------------------------------------------------------------- | GetMediaControllerQuirks +---------------------------------------------------------------------*/ -EMediaControllerQuirks GetMediaControllerQuirks(const PLT_DeviceData *device) +EMediaControllerQuirks GetMediaControllerQuirks(const PLT_DeviceData* device) { - if (device == NULL) - return EMEDIACONTROLLERQUIRKS_NONE; + if (device == NULL) + return EMEDIACONTROLLERQUIRKS_NONE; - unsigned int quirks = 0; + unsigned int quirks = 0; - if (device->m_Manufacturer.Find("Samsung Electronics") >= 0) - quirks |= EMEDIACONTROLLERQUIRKS_X_MKV; + if (device->m_Manufacturer.Find("Samsung Electronics") >= 0) + quirks |= EMEDIACONTROLLERQUIRKS_X_MKV; - return (EMediaControllerQuirks)quirks; + return (EMediaControllerQuirks)quirks; } /*---------------------------------------------------------------------- | GetMimeType +---------------------------------------------------------------------*/ -NPT_String -GetMimeType(const char* filename, - const PLT_HttpRequestContext* context /* = NULL */) +NPT_String GetMimeType(const char* filename, const PLT_HttpRequestContext* context /* = NULL */) { - NPT_String ext = URIUtils::GetExtension(filename).c_str(); - ext.TrimLeft('.'); - ext = ext.ToLowercase(); + NPT_String ext = URIUtils::GetExtension(filename).c_str(); + ext.TrimLeft('.'); + ext = ext.ToLowercase(); - return PLT_MimeType::GetMimeTypeFromExtension(ext, context); + return PLT_MimeType::GetMimeTypeFromExtension(ext, context); } /*---------------------------------------------------------------------- | GetMimeType +---------------------------------------------------------------------*/ -NPT_String -GetMimeType(const CFileItem& item, - const PLT_HttpRequestContext* context /* = NULL */) +NPT_String GetMimeType(const CFileItem& item, const PLT_HttpRequestContext* context /* = NULL */) { - std::string path = item.GetPath(); - if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().empty()) { - path = item.GetVideoInfoTag()->GetPath(); - } else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().empty()) { - path = item.GetMusicInfoTag()->GetURL(); - } + std::string path = item.GetPath(); + if (item.HasVideoInfoTag() && !item.GetVideoInfoTag()->GetPath().empty()) + { + path = item.GetVideoInfoTag()->GetPath(); + } + else if (item.HasMusicInfoTag() && !item.GetMusicInfoTag()->GetURL().empty()) + { + path = item.GetMusicInfoTag()->GetURL(); + } - if (URIUtils::IsStack(path)) - path = XFILE::CStackDirectory::GetFirstStackedFile(path); + if (URIUtils::IsStack(path)) + path = XFILE::CStackDirectory::GetFirstStackedFile(path); - NPT_String ext = URIUtils::GetExtension(path).c_str(); - ext.TrimLeft('.'); - ext = ext.ToLowercase(); + NPT_String ext = URIUtils::GetExtension(path).c_str(); + ext.TrimLeft('.'); + ext = ext.ToLowercase(); - NPT_String mime; + NPT_String mime; - if (!ext.IsEmpty()) - { - /* We look first to our extensions/overrides of libplatinum mimetypes. If not found, fallback to + if (!ext.IsEmpty()) + { + /* We look first to our extensions/overrides of libplatinum mimetypes. If not found, fallback to Platinum definitions. */ - const auto kodiOverrideMimeType = std::find_if( - std::begin(kodiPlatinumMimeTypeExtensions), std::end(kodiPlatinumMimeTypeExtensions), - [&](const auto& mimeTypeEntry) { return mimeTypeEntry.extension == ext; }); - if (kodiOverrideMimeType != std::end(kodiPlatinumMimeTypeExtensions)) - { - mime = kodiOverrideMimeType->mime_type; - } - else - { - /* Give priority to Platinum mime types as they are defined to map extension to DLNA compliant mime types + const auto kodiOverrideMimeType = std::find_if( + std::begin(kodiPlatinumMimeTypeExtensions), std::end(kodiPlatinumMimeTypeExtensions), + [&](const auto& mimeTypeEntry) { return mimeTypeEntry.extension == ext; }); + if (kodiOverrideMimeType != std::end(kodiPlatinumMimeTypeExtensions)) + { + mime = kodiOverrideMimeType->mime_type; + } + else + { + /* Give priority to Platinum mime types as they are defined to map extension to DLNA compliant mime types or custom types according to context (who asked for it) */ - mime = PLT_MimeType::GetMimeTypeFromExtension(ext, context); - if (mime == "application/octet-stream") - { - mime = ""; - } + mime = PLT_MimeType::GetMimeTypeFromExtension(ext, context); + if (mime == "application/octet-stream") + { + mime = ""; } } + } - /* if Platinum couldn't map it, default to Kodi internal mapping */ - if (mime.IsEmpty()) { - NPT_String mime = item.GetMimeType().c_str(); - if (mime == "application/octet-stream") mime = ""; - } + /* if Platinum couldn't map it, default to Kodi internal mapping */ + if (mime.IsEmpty()) + { + NPT_String mime = item.GetMimeType().c_str(); + if (mime == "application/octet-stream") + mime = ""; + } - /* fallback to generic mime type if not found */ - if (mime.IsEmpty()) { - if (item.IsVideo() || item.IsVideoDb() ) - mime = "video/" + ext; - else if (item.IsAudio() || item.IsMusicDb() ) - mime = "audio/" + ext; - else if (item.IsPicture() ) - mime = "image/" + ext; - else if (item.IsSubtitle()) - mime = "text/" + ext; - } + /* fallback to generic mime type if not found */ + if (mime.IsEmpty()) + { + if (item.IsVideo() || item.IsVideoDb()) + mime = "video/" + ext; + else if (item.IsAudio() || item.IsMusicDb()) + mime = "audio/" + ext; + else if (item.IsPicture()) + mime = "image/" + ext; + else if (item.IsSubtitle()) + mime = "text/" + ext; + } - /* nothing we can figure out */ - if (mime.IsEmpty()) { - mime = "application/octet-stream"; - } + /* nothing we can figure out */ + if (mime.IsEmpty()) + { + mime = "application/octet-stream"; + } - return mime; + return mime; } /*---------------------------------------------------------------------- | GetProtocolInfo +---------------------------------------------------------------------*/ -const NPT_String -GetProtocolInfo(const CFileItem& item, - const char* protocol, - const PLT_HttpRequestContext* context /* = NULL */) +const NPT_String GetProtocolInfo(const CFileItem& item, + const char* protocol, + const PLT_HttpRequestContext* context /* = NULL */) { - NPT_String proto = protocol; + NPT_String proto = protocol; - //! @todo fixup the protocol just in case nothing was passed - if (proto.IsEmpty()) { - proto = item.GetURL().GetProtocol().c_str(); - } + //! @todo fixup the protocol just in case nothing was passed + if (proto.IsEmpty()) + { + proto = item.GetURL().GetProtocol().c_str(); + } - /** + /** * map protocol to right prefix and use xbmc-get for * unsupported UPnP protocols for other xbmc clients * @todo add rtsp ? */ - if (proto == "http") { - proto = "http-get"; - } else { - proto = "xbmc-get"; - } + if (proto == "http") + { + proto = "http-get"; + } + else + { + proto = "xbmc-get"; + } - /* we need a valid extension to retrieve the mimetype for the protocol info */ - NPT_String mime = GetMimeType(item, context); - proto += ":*:" + mime + ":" + PLT_ProtocolInfo::GetDlnaExtension(mime, context); - return proto; + /* we need a valid extension to retrieve the mimetype for the protocol info */ + NPT_String mime = GetMimeType(item, context); + proto += ":*:" + mime + ":" + PLT_ProtocolInfo::GetDlnaExtension(mime, context); + return proto; } - /*---------------------------------------------------------------------- +/*---------------------------------------------------------------------- | CResourceFinder +---------------------------------------------------------------------*/ CResourceFinder::CResourceFinder(const char* protocol, const char* content) - : m_Protocol(protocol) - , m_Content(content) + : m_Protocol(protocol), m_Content(content) { } -bool CResourceFinder::operator()(const PLT_MediaItemResource& resource) const { - if (m_Content.IsEmpty()) - return (resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0); - else - return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0) - && resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true)); +bool CResourceFinder::operator()(const PLT_MediaItemResource& resource) const +{ + if (m_Content.IsEmpty()) + return (resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0); + else + return ((resource.m_ProtocolInfo.GetProtocol().Compare(m_Protocol, true) == 0) && + resource.m_ProtocolInfo.GetContentType().StartsWith(m_Content, true)); } /*---------------------------------------------------------------------- | PopulateObjectFromTag +---------------------------------------------------------------------*/ -NPT_Result -PopulateObjectFromTag(CMusicInfoTag& tag, - PLT_MediaObject& object, - NPT_String* file_path, - PLT_MediaItemResource* resource, - EClientQuirks quirks, - UPnPService service /* = UPnPServiceNone */) +NPT_Result PopulateObjectFromTag(CMusicInfoTag& tag, + PLT_MediaObject& object, + NPT_String* file_path, + PLT_MediaItemResource* resource, + EClientQuirks quirks, + UPnPService service /* = UPnPServiceNone */) { - if (!tag.GetURL().empty() && file_path) - *file_path = tag.GetURL().c_str(); - - std::vector genres = tag.GetGenre(); - for (unsigned int index = 0; index < genres.size(); index++) - object.m_Affiliation.genres.Add(genres.at(index).c_str()); - object.m_Title = tag.GetTitle().c_str(); - object.m_Affiliation.album = tag.GetAlbum().c_str(); - for (unsigned int index = 0; index < tag.GetArtist().size(); index++) - { - object.m_People.artists.Add(tag.GetArtist().at(index).c_str()); - object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer"); - } - object.m_People.artists.Add((!tag.GetAlbumArtistString().empty() ? tag.GetAlbumArtistString() : tag.GetArtistString()).c_str(), "AlbumArtist"); - if(tag.GetAlbumArtistString().empty()) - object.m_Creator = tag.GetArtistString().c_str(); - else - object.m_Creator = tag.GetAlbumArtistString().c_str(); - object.m_MiscInfo.original_track_number = tag.GetTrackNumber(); - if(tag.GetDatabaseId() >= 0) { - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("musicdb://songs/{}{}", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()))); - } - if (object.m_ReferenceID == object.m_ObjectID) - object.m_ReferenceID = ""; + if (!tag.GetURL().empty() && file_path) + *file_path = tag.GetURL().c_str(); + + std::vector genres = tag.GetGenre(); + for (unsigned int index = 0; index < genres.size(); index++) + object.m_Affiliation.genres.Add(genres.at(index).c_str()); + object.m_Title = tag.GetTitle().c_str(); + object.m_Affiliation.album = tag.GetAlbum().c_str(); + for (unsigned int index = 0; index < tag.GetArtist().size(); index++) + { + object.m_People.artists.Add(tag.GetArtist().at(index).c_str()); + object.m_People.artists.Add(tag.GetArtist().at(index).c_str(), "Performer"); + } + object.m_People.artists.Add( + (!tag.GetAlbumArtistString().empty() ? tag.GetAlbumArtistString() : tag.GetArtistString()) + .c_str(), + "AlbumArtist"); + if (tag.GetAlbumArtistString().empty()) + object.m_Creator = tag.GetArtistString().c_str(); + else + object.m_Creator = tag.GetAlbumArtistString().c_str(); + object.m_MiscInfo.original_track_number = tag.GetTrackNumber(); + if (tag.GetDatabaseId() >= 0) + { + object.m_ReferenceID = EncodeObjectId(StringUtils::Format( + "musicdb://songs/{}{}", tag.GetDatabaseId(), URIUtils::GetExtension(tag.GetURL()))); + } + if (object.m_ReferenceID == object.m_ObjectID) + object.m_ReferenceID = ""; - object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsW3CDateTime().c_str(); - object.m_MiscInfo.play_count = tag.GetPlayCount(); + object.m_MiscInfo.last_time = tag.GetLastPlayed().GetAsW3CDateTime().c_str(); + object.m_MiscInfo.play_count = tag.GetPlayCount(); - if (resource) resource->m_Duration = tag.GetDuration(); + if (resource) + resource->m_Duration = tag.GetDuration(); - return NPT_SUCCESS; + return NPT_SUCCESS; } /*---------------------------------------------------------------------- | PopulateObjectFromTag +---------------------------------------------------------------------*/ -NPT_Result -PopulateObjectFromTag(CVideoInfoTag& tag, - PLT_MediaObject& object, - NPT_String* file_path, - PLT_MediaItemResource* resource, - EClientQuirks quirks, - UPnPService service /* = UPnPServiceNone */) +NPT_Result PopulateObjectFromTag(CVideoInfoTag& tag, + PLT_MediaObject& object, + NPT_String* file_path, + PLT_MediaItemResource* resource, + EClientQuirks quirks, + UPnPService service /* = UPnPServiceNone */) { - if (!tag.m_strFileNameAndPath.empty() && file_path) - *file_path = tag.m_strFileNameAndPath.c_str(); - - if (tag.m_iDbId != -1 ) { - if (tag.m_type == MediaTypeMusicVideo) { - object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip"; - object.m_Creator = StringUtils::Join(tag.m_artist, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator).c_str(); - for (const auto& itArtist : tag.m_artist) - object.m_People.artists.Add(itArtist.c_str()); - object.m_Affiliation.album = tag.m_strAlbum.c_str(); - object.m_Title = tag.m_strTitle.c_str(); - object.m_Date = tag.GetPremiered().GetAsW3CDate().c_str(); - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("videodb://musicvideos/titles/{}", tag.m_iDbId)); - } else if (tag.m_type == MediaTypeMovie) { - object.m_ObjectClass.type = "object.item.videoItem.movie"; - object.m_Title = tag.m_strTitle.c_str(); - object.m_Date = tag.GetPremiered().GetAsW3CDate().c_str(); - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("videodb://movies/titles/{}", tag.m_iDbId)); - } else { - object.m_Recorded.series_title = tag.m_strShowTitle.c_str(); - - if (tag.m_type == MediaTypeTvShow) { - object.m_ObjectClass.type = "object.container.album.videoAlbum.videoBroadcastShow"; - object.m_Title = tag.m_strTitle.c_str(); - object.m_Recorded.episode_number = tag.m_iEpisode; - object.m_Recorded.episode_count = tag.m_iEpisode; - if (!tag.m_premiered.IsValid() && tag.GetYear() > 0) - object.m_Date = CDateTime(tag.GetYear(), 1, 1, 0, 0, 0).GetAsW3CDate().c_str(); - else - object.m_Date = tag.m_premiered.GetAsW3CDate().c_str(); - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("videodb://tvshows/titles/{}", tag.m_iDbId)); - } else if (tag.m_type == MediaTypeSeason) { - object.m_ObjectClass.type = "object.container.album.videoAlbum.videoBroadcastSeason"; - object.m_Title = tag.m_strTitle.c_str(); - object.m_Recorded.episode_season = tag.m_iSeason; - object.m_Recorded.episode_count = tag.m_iEpisode; - if (!tag.m_premiered.IsValid() && tag.GetYear() > 0) - object.m_Date = CDateTime(tag.GetYear(), 1, 1, 0, 0, 0).GetAsW3CDate().c_str(); - else - object.m_Date = tag.m_premiered.GetAsW3CDate().c_str(); - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("videodb://tvshows/titles/{}/{}", tag.m_iIdShow, tag.m_iSeason)); - } else { - object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast"; - object.m_Recorded.program_title = "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2); - object.m_Recorded.program_title += "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2); - object.m_Recorded.program_title += (" : " + tag.m_strTitle).c_str(); - object.m_Recorded.episode_number = tag.m_iEpisode; - object.m_Recorded.episode_season = tag.m_iSeason; - object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title; - object.m_ReferenceID = EncodeObjectId(StringUtils::Format("videodb://tvshows/titles/{}/{}/{}", tag.m_iIdShow, tag.m_iSeason, tag.m_iDbId)); - object.m_Date = tag.m_firstAired.GetAsW3CDate().c_str(); - } - } + if (!tag.m_strFileNameAndPath.empty() && file_path) + *file_path = tag.m_strFileNameAndPath.c_str(); + + if (tag.m_iDbId != -1) + { + if (tag.m_type == MediaTypeMusicVideo) + { + object.m_ObjectClass.type = "object.item.videoItem.musicVideoClip"; + object.m_Creator = + StringUtils::Join( + tag.m_artist, + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator) + .c_str(); + for (const auto& itArtist : tag.m_artist) + object.m_People.artists.Add(itArtist.c_str()); + object.m_Affiliation.album = tag.m_strAlbum.c_str(); + object.m_Title = tag.m_strTitle.c_str(); + object.m_Date = tag.GetPremiered().GetAsW3CDate().c_str(); + object.m_ReferenceID = + EncodeObjectId(StringUtils::Format("videodb://musicvideos/titles/{}", tag.m_iDbId)); + } + else if (tag.m_type == MediaTypeMovie) + { + object.m_ObjectClass.type = "object.item.videoItem.movie"; + object.m_Title = tag.m_strTitle.c_str(); + object.m_Date = tag.GetPremiered().GetAsW3CDate().c_str(); + object.m_ReferenceID = + EncodeObjectId(StringUtils::Format("videodb://movies/titles/{}", tag.m_iDbId)); + } + else + { + object.m_Recorded.series_title = tag.m_strShowTitle.c_str(); + + if (tag.m_type == MediaTypeTvShow) + { + object.m_ObjectClass.type = "object.container.album.videoAlbum.videoBroadcastShow"; + object.m_Title = tag.m_strTitle.c_str(); + object.m_Recorded.episode_number = tag.m_iEpisode; + object.m_Recorded.episode_count = tag.m_iEpisode; + if (!tag.m_premiered.IsValid() && tag.GetYear() > 0) + object.m_Date = CDateTime(tag.GetYear(), 1, 1, 0, 0, 0).GetAsW3CDate().c_str(); + else + object.m_Date = tag.m_premiered.GetAsW3CDate().c_str(); + object.m_ReferenceID = + EncodeObjectId(StringUtils::Format("videodb://tvshows/titles/{}", tag.m_iDbId)); + } + else if (tag.m_type == MediaTypeSeason) + { + object.m_ObjectClass.type = "object.container.album.videoAlbum.videoBroadcastSeason"; + object.m_Title = tag.m_strTitle.c_str(); + object.m_Recorded.episode_season = tag.m_iSeason; + object.m_Recorded.episode_count = tag.m_iEpisode; + if (!tag.m_premiered.IsValid() && tag.GetYear() > 0) + object.m_Date = CDateTime(tag.GetYear(), 1, 1, 0, 0, 0).GetAsW3CDate().c_str(); + else + object.m_Date = tag.m_premiered.GetAsW3CDate().c_str(); + object.m_ReferenceID = EncodeObjectId( + StringUtils::Format("videodb://tvshows/titles/{}/{}", tag.m_iIdShow, tag.m_iSeason)); + } + else + { + object.m_ObjectClass.type = "object.item.videoItem.videoBroadcast"; + object.m_Recorded.program_title = + "S" + ("0" + NPT_String::FromInteger(tag.m_iSeason)).Right(2); + object.m_Recorded.program_title += + "E" + ("0" + NPT_String::FromInteger(tag.m_iEpisode)).Right(2); + object.m_Recorded.program_title += (" : " + tag.m_strTitle).c_str(); + object.m_Recorded.episode_number = tag.m_iEpisode; + object.m_Recorded.episode_season = tag.m_iSeason; + object.m_Title = object.m_Recorded.series_title + " - " + object.m_Recorded.program_title; + object.m_ReferenceID = EncodeObjectId(StringUtils::Format( + "videodb://tvshows/titles/{}/{}/{}", tag.m_iIdShow, tag.m_iSeason, tag.m_iDbId)); + object.m_Date = tag.m_firstAired.GetAsW3CDate().c_str(); + } } + } - if(quirks & ECLIENTQUIRKS_BASICVIDEOCLASS) - object.m_ObjectClass.type = "object.item.videoItem"; + if (quirks & ECLIENTQUIRKS_BASICVIDEOCLASS) + object.m_ObjectClass.type = "object.item.videoItem"; - if(object.m_ReferenceID == object.m_ObjectID) - object.m_ReferenceID = ""; + if (object.m_ReferenceID == object.m_ObjectID) + object.m_ReferenceID = ""; - for (unsigned int index = 0; index < tag.m_studio.size(); index++) - object.m_People.publisher.Add(tag.m_studio[index].c_str()); + for (unsigned int index = 0; index < tag.m_studio.size(); index++) + object.m_People.publisher.Add(tag.m_studio[index].c_str()); - object.m_XbmcInfo.date_added = tag.m_dateAdded.GetAsW3CDate().c_str(); - object.m_XbmcInfo.rating = tag.GetRating().rating; - object.m_XbmcInfo.votes = tag.GetRating().votes; - object.m_XbmcInfo.unique_identifier = tag.GetUniqueID().c_str(); - for (const auto& country : tag.m_country) - object.m_XbmcInfo.countries.Add(country.c_str()); - object.m_XbmcInfo.user_rating = tag.m_iUserRating; + object.m_XbmcInfo.date_added = tag.m_dateAdded.GetAsW3CDate().c_str(); + object.m_XbmcInfo.rating = tag.GetRating().rating; + object.m_XbmcInfo.votes = tag.GetRating().votes; + object.m_XbmcInfo.unique_identifier = tag.GetUniqueID().c_str(); + for (const auto& country : tag.m_country) + object.m_XbmcInfo.countries.Add(country.c_str()); + object.m_XbmcInfo.user_rating = tag.m_iUserRating; - for (unsigned int index = 0; index < tag.m_genre.size(); index++) - object.m_Affiliation.genres.Add(tag.m_genre.at(index).c_str()); + for (unsigned int index = 0; index < tag.m_genre.size(); index++) + object.m_Affiliation.genres.Add(tag.m_genre.at(index).c_str()); - for (CVideoInfoTag::iCast it = tag.m_cast.begin(); it != tag.m_cast.end(); ++it) - { - object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str()); - } + for (CVideoInfoTag::iCast it = tag.m_cast.begin(); it != tag.m_cast.end(); ++it) + { + object.m_People.actors.Add(it->strName.c_str(), it->strRole.c_str()); + } - for (unsigned int index = 0; index < tag.m_director.size(); index++) - object.m_People.directors.Add(tag.m_director[index].c_str()); - - for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++) - object.m_People.authors.Add(tag.m_writingCredits[index].c_str()); - - object.m_Description.description = tag.m_strTagLine.c_str(); - object.m_Description.long_description = tag.m_strPlot.c_str(); - object.m_Description.rating = tag.m_strMPAARating.c_str(); - object.m_MiscInfo.last_position = (NPT_UInt32)tag.GetResumePoint().timeInSeconds; - object.m_XbmcInfo.last_playerstate = tag.GetResumePoint().playerState.c_str(); - object.m_MiscInfo.last_time = tag.m_lastPlayed.GetAsW3CDateTime().c_str(); - object.m_MiscInfo.play_count = tag.GetPlayCount(); - if (resource) { - resource->m_Duration = tag.GetDuration(); - if (tag.HasStreamDetails()) { - const CStreamDetails &details = tag.m_streamDetails; - resource->m_Resolution = NPT_String::FromInteger(details.GetVideoWidth()) + "x" + NPT_String::FromInteger(details.GetVideoHeight()); - resource->m_NbAudioChannels = details.GetAudioChannels(); - } + for (unsigned int index = 0; index < tag.m_director.size(); index++) + object.m_People.directors.Add(tag.m_director[index].c_str()); + + for (unsigned int index = 0; index < tag.m_writingCredits.size(); index++) + object.m_People.authors.Add(tag.m_writingCredits[index].c_str()); + + object.m_Description.description = tag.m_strTagLine.c_str(); + object.m_Description.long_description = tag.m_strPlot.c_str(); + object.m_Description.rating = tag.m_strMPAARating.c_str(); + object.m_MiscInfo.last_position = (NPT_UInt32)tag.GetResumePoint().timeInSeconds; + object.m_XbmcInfo.last_playerstate = tag.GetResumePoint().playerState.c_str(); + object.m_MiscInfo.last_time = tag.m_lastPlayed.GetAsW3CDateTime().c_str(); + object.m_MiscInfo.play_count = tag.GetPlayCount(); + if (resource) + { + resource->m_Duration = tag.GetDuration(); + if (tag.HasStreamDetails()) + { + const CStreamDetails& details = tag.m_streamDetails; + resource->m_Resolution = NPT_String::FromInteger(details.GetVideoWidth()) + "x" + + NPT_String::FromInteger(details.GetVideoHeight()); + resource->m_NbAudioChannels = details.GetAudioChannels(); } + } - return NPT_SUCCESS; + return NPT_SUCCESS; } /*---------------------------------------------------------------------- | BuildObject +---------------------------------------------------------------------*/ -PLT_MediaObject* -BuildObject(CFileItem& item, - NPT_String& file_path, - bool with_count, - NPT_Reference& thumb_loader, - const PLT_HttpRequestContext* context /* = NULL */, - CUPnPServer* upnp_server /* = NULL */, - UPnPService upnp_service /* = UPnPServiceNone */) +PLT_MediaObject* BuildObject(CFileItem& item, + NPT_String& file_path, + bool with_count, + NPT_Reference& thumb_loader, + const PLT_HttpRequestContext* context /* = NULL */, + CUPnPServer* upnp_server /* = NULL */, + UPnPService upnp_service /* = UPnPServiceNone */) { static Logger logger = CServiceBroker::GetLogging().GetLogger("UPNP::BuildObject"); @@ -443,7 +479,8 @@ BuildObject(CFileItem& item, PLT_MediaObject* object = NULL; std::string thumb; - logger->debug("Building didl for plain object '{}' (encoded value: '{}')", item.GetPath(), EncodeObjectId(item.GetPath()).GetChars()); + logger->debug("Building didl for plain object '{}' (encoded value: '{}')", item.GetPath(), + EncodeObjectId(item.GetPath()).GetChars()); auto settingsComponent = CServiceBroker::GetSettingsComponent(); if (!settingsComponent) @@ -468,567 +505,642 @@ BuildObject(CFileItem& item, context->GetLocalAddress().GetPort(), "/"); ips.Remove(context->GetLocalAddress().GetIpAddress()); ips.Insert(ips.GetFirstItem(), context->GetLocalAddress().GetIpAddress()); - } else if(upnp_server) { - rooturi = NPT_HttpUrl("localhost", upnp_server->GetPort(), "/"); + } + else if (upnp_server) + { + rooturi = NPT_HttpUrl("localhost", upnp_server->GetPort(), "/"); + } + + if (!item.m_bIsFolder) + { + object = new PLT_MediaItem(); + object->m_ObjectID = EncodeObjectId(item.GetPath()); + + /* Setup object type */ + if (item.IsMusicDb() || item.IsAudio()) + { + object->m_ObjectClass.type = "object.item.audioItem.musicTrack"; + + if (item.HasMusicInfoTag()) + { + CMusicInfoTag* tag = item.GetMusicInfoTag(); + PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks, upnp_service); + } } + else if (item.IsVideoDb() || item.IsVideo()) + { + object->m_ObjectClass.type = "object.item.videoItem"; - if (!item.m_bIsFolder) { - object = new PLT_MediaItem(); - object->m_ObjectID = EncodeObjectId(item.GetPath()); - - /* Setup object type */ - if (item.IsMusicDb() || item.IsAudio()) { - object->m_ObjectClass.type = "object.item.audioItem.musicTrack"; - - if (item.HasMusicInfoTag()) { - CMusicInfoTag *tag = item.GetMusicInfoTag(); - PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks, upnp_service); - } - } else if (item.IsVideoDb() || item.IsVideo()) { - object->m_ObjectClass.type = "object.item.videoItem"; - - if(quirks & ECLIENTQUIRKS_UNKNOWNSERIES) - object->m_Affiliation.album = "[Unknown Series]"; - - if (item.HasVideoInfoTag()) { - CVideoInfoTag *tag = item.GetVideoInfoTag(); - PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks, upnp_service); - } - } else if (item.IsPicture()) { - object->m_ObjectClass.type = "object.item.imageItem.photo"; - } else { - object->m_ObjectClass.type = "object.item"; - } + if (quirks & ECLIENTQUIRKS_UNKNOWNSERIES) + object->m_Affiliation.album = "[Unknown Series]"; + + if (item.HasVideoInfoTag()) + { + CVideoInfoTag* tag = item.GetVideoInfoTag(); + PopulateObjectFromTag(*tag, *object, &file_path, &resource, quirks, upnp_service); + } + } + else if (item.IsPicture()) + { + object->m_ObjectClass.type = "object.item.imageItem.photo"; + } + else + { + object->m_ObjectClass.type = "object.item"; + } - // duration of zero is invalid - if (resource.m_Duration == 0) resource.m_Duration = -1; + // duration of zero is invalid + if (resource.m_Duration == 0) + resource.m_Duration = -1; - // Set the resource file size - resource.m_Size = item.m_dwSize; - if(resource.m_Size == 0) - resource.m_Size = (NPT_LargeSize)-1; + // Set the resource file size + resource.m_Size = item.m_dwSize; + if (resource.m_Size == 0) + resource.m_Size = (NPT_LargeSize)-1; - // set date - if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) { - object->m_Date = item.m_dateTime.GetAsW3CDate().c_str(); - } + // set date + if (object->m_Date.IsEmpty() && item.m_dateTime.IsValid()) + { + object->m_Date = item.m_dateTime.GetAsW3CDate().c_str(); + } - if (upnp_server) { - upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, GetProtocolInfo(item, "http", context)); - } + if (upnp_server) + { + upnp_server->AddSafeResourceUri(object, rooturi, ips, file_path, + GetProtocolInfo(item, "http", context)); + } - // if the item is remote, add a direct link to the item - if (URIUtils::IsRemote((const char*)file_path)) { - resource.m_ProtocolInfo = PLT_ProtocolInfo(GetProtocolInfo(item, item.GetURL().GetProtocol().c_str(), context)); - resource.m_Uri = file_path; - - // if the direct link can be served directly using http, then push it in front - // otherwise keep the xbmc-get resource last and let a compatible client look for it - if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) { - object->m_Resources.Add(resource); - } else { - object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource); - } - } + // if the item is remote, add a direct link to the item + if (URIUtils::IsRemote((const char*)file_path)) + { + resource.m_ProtocolInfo = + PLT_ProtocolInfo(GetProtocolInfo(item, item.GetURL().GetProtocol().c_str(), context)); + resource.m_Uri = file_path; - // copy across the known metadata - for(unsigned i=0; im_Resources.GetItemCount(); i++) { - object->m_Resources[i].m_Size = resource.m_Size; - object->m_Resources[i].m_Duration = resource.m_Duration; - object->m_Resources[i].m_Resolution = resource.m_Resolution; - } + // if the direct link can be served directly using http, then push it in front + // otherwise keep the xbmc-get resource last and let a compatible client look for it + if (resource.m_ProtocolInfo.ToString().StartsWith("xbmc", true)) + { + object->m_Resources.Add(resource); + } + else + { + object->m_Resources.Insert(object->m_Resources.GetFirstItem(), resource); + } + } + + // copy across the known metadata + for (unsigned i = 0; i < object->m_Resources.GetItemCount(); i++) + { + object->m_Resources[i].m_Size = resource.m_Size; + object->m_Resources[i].m_Duration = resource.m_Duration; + object->m_Resources[i].m_Resolution = resource.m_Resolution; + } - // Some upnp clients expect all audio items to have parent root id 4 + // Some upnp clients expect all audio items to have parent root id 4 #ifdef WMP_ID_MAPPING - object->m_ParentID = EncodeObjectId("4"); + object->m_ParentID = EncodeObjectId("4"); #endif - } else { - PLT_MediaContainer* container = new PLT_MediaContainer; - object = container; - - /* Assign a title and id for this container */ - container->m_ObjectID = EncodeObjectId(item.GetPath()); - container->m_ObjectClass.type = "object.container"; - container->m_ChildrenCount = -1; - - /* this might be overkill, but hey */ - if (item.IsMusicDb()) { - MUSICDATABASEDIRECTORY::NODE_TYPE node = CMusicDatabaseDirectory::GetDirectoryType(item.GetPath()); - switch(node) { - case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: { - container->m_ObjectClass.type += ".person.musicArtist"; - CMusicInfoTag *tag = item.GetMusicInfoTag(); - if (tag) { - container->m_People.artists.Add( - CorrectAllItemsSortHack(tag->GetArtistString()).c_str(), "Performer"); - container->m_People.artists.Add( - CorrectAllItemsSortHack((!tag->GetAlbumArtistString().empty() ? tag->GetAlbumArtistString() : tag->GetArtistString())).c_str(), "AlbumArtist"); - } + } + else + { + PLT_MediaContainer* container = new PLT_MediaContainer; + object = container; + + /* Assign a title and id for this container */ + container->m_ObjectID = EncodeObjectId(item.GetPath()); + container->m_ObjectClass.type = "object.container"; + container->m_ChildrenCount = -1; + + /* this might be overkill, but hey */ + if (item.IsMusicDb()) + { + MUSICDATABASEDIRECTORY::NODE_TYPE node = + CMusicDatabaseDirectory::GetDirectoryType(item.GetPath()); + switch (node) + { + case MUSICDATABASEDIRECTORY::NODE_TYPE_ARTIST: + { + container->m_ObjectClass.type += ".person.musicArtist"; + CMusicInfoTag* tag = item.GetMusicInfoTag(); + if (tag) + { + container->m_People.artists.Add(CorrectAllItemsSortHack(tag->GetArtistString()).c_str(), + "Performer"); + container->m_People.artists.Add( + CorrectAllItemsSortHack((!tag->GetAlbumArtistString().empty() + ? tag->GetAlbumArtistString() + : tag->GetArtistString())) + .c_str(), + "AlbumArtist"); + } #ifdef WMP_ID_MAPPING - // Some upnp clients expect all artists to have parent root id 107 - container->m_ParentID = EncodeObjectId("107"); + // Some upnp clients expect all artists to have parent root id 107 + container->m_ParentID = EncodeObjectId("107"); #endif - } - break; - case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM: - case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED: { - container->m_ObjectClass.type += ".album.musicAlbum"; - // for Sonos to be happy - CMusicInfoTag *tag = item.GetMusicInfoTag(); - if (tag) { - container->m_People.artists.Add( - CorrectAllItemsSortHack(tag->GetArtistString()).c_str(), "Performer"); - container->m_People.artists.Add( - CorrectAllItemsSortHack(!tag->GetAlbumArtistString().empty() ? tag->GetAlbumArtistString() : tag->GetArtistString()).c_str(), "AlbumArtist"); - container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str(); - } + } + break; + case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM: + case MUSICDATABASEDIRECTORY::NODE_TYPE_ALBUM_RECENTLY_ADDED: + { + container->m_ObjectClass.type += ".album.musicAlbum"; + // for Sonos to be happy + CMusicInfoTag* tag = item.GetMusicInfoTag(); + if (tag) + { + container->m_People.artists.Add(CorrectAllItemsSortHack(tag->GetArtistString()).c_str(), + "Performer"); + container->m_People.artists.Add( + CorrectAllItemsSortHack(!tag->GetAlbumArtistString().empty() + ? tag->GetAlbumArtistString() + : tag->GetArtistString()) + .c_str(), + "AlbumArtist"); + container->m_Affiliation.album = CorrectAllItemsSortHack(tag->GetAlbum()).c_str(); + } #ifdef WMP_ID_MAPPING - // Some upnp clients expect all albums to have parent root id 7 - container->m_ParentID = EncodeObjectId("7"); + // Some upnp clients expect all albums to have parent root id 7 + container->m_ParentID = EncodeObjectId("7"); #endif - } - break; - case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE: - container->m_ObjectClass.type += ".genre.musicGenre"; - break; - default: - break; - } - } else if (item.IsVideoDb()) { - VIDEODATABASEDIRECTORY::NODE_TYPE node = CVideoDatabaseDirectory::GetDirectoryType(item.GetPath()); - CVideoInfoTag &tag = *item.GetVideoInfoTag(); - switch(node) { - case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE: - container->m_ObjectClass.type += ".genre.movieGenre"; - break; - case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR: - container->m_ObjectClass.type += ".person.videoArtist"; - container->m_Creator = - StringUtils::Join( - tag.m_artist, - settingsComponent->GetAdvancedSettings()->m_videoItemSeparator) - .c_str(); - container->m_Title = tag.m_strTitle.c_str(); - break; - case VIDEODATABASEDIRECTORY::NODE_TYPE_SEASONS: - container->m_ObjectClass.type += ".album.videoAlbum.videoBroadcastSeason"; - if (item.HasVideoInfoTag()) { - CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag(); - PopulateObjectFromTag(*tag, *container, &file_path, &resource, quirks); - } - break; - case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS: - container->m_ObjectClass.type += ".album.videoAlbum.videoBroadcastShow"; - if (item.HasVideoInfoTag()) { - CVideoInfoTag *tag = (CVideoInfoTag*)item.GetVideoInfoTag(); - PopulateObjectFromTag(*tag, *container, &file_path, &resource, quirks); - } - break; - default: - container->m_ObjectClass.type += ".storageFolder"; - break; - } - } else if (item.IsPlayList() || item.IsSmartPlayList()) { - container->m_ObjectClass.type += ".playlistContainer"; } + break; + case MUSICDATABASEDIRECTORY::NODE_TYPE_GENRE: + container->m_ObjectClass.type += ".genre.musicGenre"; + break; + default: + break; + } + } + else if (item.IsVideoDb()) + { + VIDEODATABASEDIRECTORY::NODE_TYPE node = + CVideoDatabaseDirectory::GetDirectoryType(item.GetPath()); + CVideoInfoTag& tag = *item.GetVideoInfoTag(); + switch (node) + { + case VIDEODATABASEDIRECTORY::NODE_TYPE_GENRE: + container->m_ObjectClass.type += ".genre.movieGenre"; + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_ACTOR: + container->m_ObjectClass.type += ".person.videoArtist"; + container->m_Creator = + StringUtils::Join(tag.m_artist, + settingsComponent->GetAdvancedSettings()->m_videoItemSeparator) + .c_str(); + container->m_Title = tag.m_strTitle.c_str(); + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_SEASONS: + container->m_ObjectClass.type += ".album.videoAlbum.videoBroadcastSeason"; + if (item.HasVideoInfoTag()) + { + CVideoInfoTag* tag = (CVideoInfoTag*)item.GetVideoInfoTag(); + PopulateObjectFromTag(*tag, *container, &file_path, &resource, quirks); + } + break; + case VIDEODATABASEDIRECTORY::NODE_TYPE_TITLE_TVSHOWS: + container->m_ObjectClass.type += ".album.videoAlbum.videoBroadcastShow"; + if (item.HasVideoInfoTag()) + { + CVideoInfoTag* tag = (CVideoInfoTag*)item.GetVideoInfoTag(); + PopulateObjectFromTag(*tag, *container, &file_path, &resource, quirks); + } + break; + default: + container->m_ObjectClass.type += ".storageFolder"; + break; + } + } + else if (item.IsPlayList() || item.IsSmartPlayList()) + { + container->m_ObjectClass.type += ".playlistContainer"; + } - if(quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) { - container->m_ObjectClass.type = "object.container.storageFolder"; - } + if (quirks & ECLIENTQUIRKS_ONLYSTORAGEFOLDER) + { + container->m_ObjectClass.type = "object.container.storageFolder"; + } - /* Get the number of children for this container */ - if (with_count && upnp_server) { - const NPT_String decodedObjectId = DecodeObjectId(object->m_ObjectID.GetChars()); - if (StringUtils::StartsWithNoCase(decodedObjectId, "virtualpath://")) { - NPT_LargeSize count = 0; - NPT_CHECK_LABEL(NPT_File::GetSize(file_path, count), failure); - container->m_ChildrenCount = (NPT_Int32)count; - } else { - /* this should be a standard path */ - //! @todo - get file count of this directory - } - } + /* Get the number of children for this container */ + if (with_count && upnp_server) + { + const NPT_String decodedObjectId = DecodeObjectId(object->m_ObjectID.GetChars()); + if (StringUtils::StartsWithNoCase(decodedObjectId, "virtualpath://")) + { + NPT_LargeSize count = 0; + NPT_CHECK_LABEL(NPT_File::GetSize(file_path, count), failure); + container->m_ChildrenCount = (NPT_Int32)count; + } + else + { + /* this should be a standard path */ + //! @todo - get file count of this directory + } } + } - // set a title for the object - if (object->m_Title.IsEmpty()) { - if (!item.GetLabel().empty()) { - std::string title = item.GetLabel(); - if (item.IsPlayList() || !item.m_bIsFolder) URIUtils::RemoveExtension(title); - object->m_Title = title.c_str(); - } + // set a title for the object + if (object->m_Title.IsEmpty()) + { + if (!item.GetLabel().empty()) + { + std::string title = item.GetLabel(); + if (item.IsPlayList() || !item.m_bIsFolder) + URIUtils::RemoveExtension(title); + object->m_Title = title.c_str(); } + } - if (upnp_server) { - // determine the correct artwork for this item - if (!thumb_loader.IsNull()) - thumb_loader->LoadItem(&item); - - // we have to decide the best art type to serve to the client - use ContentUtils - // to get it since it depends on the mediatype of the item being served - thumb = ContentUtils::GetPreferredArtImage(item); - - if (!thumb.empty()) { - PLT_AlbumArtInfo art; - - // Get DLNA profileId for the image - std::optional imageProfile = GetImageDLNAProfile(thumb); - if (imageProfile.has_value()) - { - art.dlna_profile = imageProfile.value().c_str(); - } - else - { - // unknown image format (might be a embed thumb previously extracted and cached - e.g. mp3, flac, mvk, etc) - bool needsRecaching; - const std::string cachedImagePath = - CServiceBroker::GetTextureCache()->CheckCachedImage(thumb, needsRecaching); - if (!cachedImagePath.empty()) - { - imageProfile = GetImageDLNAProfile(cachedImagePath); - if (imageProfile.has_value()) - { - thumb = cachedImagePath; - art.dlna_profile = imageProfile.value().c_str(); - } - } - } - - // Always default to JPEG if the profile could not be found - if (art.dlna_profile.IsEmpty()) - { - art.dlna_profile = "JPEG_TN"; - } - - art.uri = - upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), - CTextureUtils::GetWrappedImageURL(thumb).c_str()); + if (upnp_server) + { + // determine the correct artwork for this item + if (!thumb_loader.IsNull()) + thumb_loader->LoadItem(&item); - object->m_ExtraInfo.album_arts.Add(art); - } + // we have to decide the best art type to serve to the client - use ContentUtils + // to get it since it depends on the mediatype of the item being served + thumb = ContentUtils::GetPreferredArtImage(item); + + if (!thumb.empty()) + { + PLT_AlbumArtInfo art; - for (const auto& itArtwork : item.GetArt()) + // Get DLNA profileId for the image + std::optional imageProfile = GetImageDLNAProfile(thumb); + if (imageProfile.has_value()) + { + art.dlna_profile = imageProfile.value().c_str(); + } + else + { + // unknown image format (might be a embed thumb previously extracted and cached - e.g. mp3, flac, mvk, etc) + bool needsRecaching; + const std::string cachedImagePath = + CServiceBroker::GetTextureCache()->CheckCachedImage(thumb, needsRecaching); + if (!cachedImagePath.empty()) { - if (!itArtwork.first.empty() && !itArtwork.second.empty()) + imageProfile = GetImageDLNAProfile(cachedImagePath); + if (imageProfile.has_value()) { - std::string wrappedUrl = CTextureUtils::GetWrappedImageURL(itArtwork.second); - object->m_XbmcInfo.artwork.Add( - itArtwork.first.c_str(), - upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), - wrappedUrl.c_str())); - upnp_server->AddSafeResourceUri(object, rooturi, ips, wrappedUrl.c_str(), - ("xbmc.org:*:" + itArtwork.first + ":*").c_str()); + thumb = cachedImagePath; + art.dlna_profile = imageProfile.value().c_str(); } } + } + + // Always default to JPEG if the profile could not be found + if (art.dlna_profile.IsEmpty()) + { + art.dlna_profile = "JPEG_TN"; + } + + art.uri = upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), + CTextureUtils::GetWrappedImageURL(thumb).c_str()); + + object->m_ExtraInfo.album_arts.Add(art); } - // look for and add external subtitle if we are processing a video file and - // we are being called by a UPnP player or renderer or the user has chosen - // to look for external subtitles - if (upnp_server != NULL && item.IsVideo() && - (upnp_service == UPnPPlayer || upnp_service == UPnPRenderer || - settings->GetBool(CSettings::SETTING_SERVICES_UPNPLOOKFOREXTERNALSUBTITLES))) + for (const auto& itArtwork : item.GetArt()) { - // find any available external subtitles - std::vector filenames; - std::vector subtitles; - CUtil::ScanForExternalSubtitles(file_path.GetChars(), filenames); + if (!itArtwork.first.empty() && !itArtwork.second.empty()) + { + std::string wrappedUrl = CTextureUtils::GetWrappedImageURL(itArtwork.second); + object->m_XbmcInfo.artwork.Add( + itArtwork.first.c_str(), + upnp_server->BuildSafeResourceUri(rooturi, (*ips.GetFirstItem()).ToString(), + wrappedUrl.c_str())); + upnp_server->AddSafeResourceUri(object, rooturi, ips, wrappedUrl.c_str(), + ("xbmc.org:*:" + itArtwork.first + ":*").c_str()); + } + } + } - std::string ext; - for (unsigned int i = 0; i < filenames.size(); i++) - { - ext = URIUtils::GetExtension(filenames[i]).c_str(); - ext = ext.substr(1); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - /* Hardcoded check for extension is not the best way, but it can't be allowed to pass all + // look for and add external subtitle if we are processing a video file and + // we are being called by a UPnP player or renderer or the user has chosen + // to look for external subtitles + if (upnp_server != NULL && item.IsVideo() && + (upnp_service == UPnPPlayer || upnp_service == UPnPRenderer || + settings->GetBool(CSettings::SETTING_SERVICES_UPNPLOOKFOREXTERNALSUBTITLES))) + { + // find any available external subtitles + std::vector filenames; + std::vector subtitles; + CUtil::ScanForExternalSubtitles(file_path.GetChars(), filenames); + + std::string ext; + for (unsigned int i = 0; i < filenames.size(); i++) + { + ext = URIUtils::GetExtension(filenames[i]).c_str(); + ext = ext.substr(1); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + /* Hardcoded check for extension is not the best way, but it can't be allowed to pass all subtitle extension (ex. rar or zip). There are the most popular extensions support by UPnP devices.*/ - for (std::string_view type : SupportedSubFormats) - { - if (type == ext) - { - subtitles.push_back(filenames[i]); - } - } + for (std::string_view type : SupportedSubFormats) + { + if (type == ext) + { + subtitles.push_back(filenames[i]); } + } + } - std::string subtitlePath; + std::string subtitlePath; - if (subtitles.size() == 1) - { - subtitlePath = subtitles[0]; - } - else if (!subtitles.empty()) - { - std::string preferredLanguage{"en"}; + if (subtitles.size() == 1) + { + subtitlePath = subtitles[0]; + } + else if (!subtitles.empty()) + { + std::string preferredLanguage{"en"}; - /* trying to find subtitle with preferred language settings */ - auto setting = settings->GetSetting("locale.subtitlelanguage"); - if (!setting) - CLog::Log(LOGERROR, "Failed to load setting for: {}", "locale.subtitlelanguage"); - else - preferredLanguage = setting->ToString(); + /* trying to find subtitle with preferred language settings */ + auto setting = settings->GetSetting("locale.subtitlelanguage"); + if (!setting) + CLog::Log(LOGERROR, "Failed to load setting for: {}", "locale.subtitlelanguage"); + else + preferredLanguage = setting->ToString(); - std::string preferredLanguageCode; - g_LangCodeExpander.ConvertToISO6392B(preferredLanguage, preferredLanguageCode); + std::string preferredLanguageCode; + g_LangCodeExpander.ConvertToISO6392B(preferredLanguage, preferredLanguageCode); - for (unsigned int i = 0; i < subtitles.size(); i++) - { - ExternalStreamInfo info = - CUtil::GetExternalStreamDetailsFromFilename(file_path.GetChars(), subtitles[i]); - - if (preferredLanguageCode == info.language) - { - subtitlePath = subtitles[i]; - break; - } - } - /* if not found subtitle with preferred language, get the first one */ - if (subtitlePath.empty()) - { - subtitlePath = subtitles[0]; - } - } + for (unsigned int i = 0; i < subtitles.size(); i++) + { + ExternalStreamInfo info = + CUtil::GetExternalStreamDetailsFromFilename(file_path.GetChars(), subtitles[i]); - if (!subtitlePath.empty()) + if (preferredLanguageCode == info.language) { - /* subtitles are added as 2 resources, 2 sec resources and 1 addon to video resource, to be compatible with + subtitlePath = subtitles[i]; + break; + } + } + /* if not found subtitle with preferred language, get the first one */ + if (subtitlePath.empty()) + { + subtitlePath = subtitles[0]; + } + } + + if (!subtitlePath.empty()) + { + /* subtitles are added as 2 resources, 2 sec resources and 1 addon to video resource, to be compatible with the most of the devices; all UPnP devices take the last one it could handle, and skip ones it doesn't "understand" */ - // add subtitle resource with standard protocolInfo - NPT_String protocolInfo = GetProtocolInfo(CFileItem(subtitlePath, false), "http", context); - upnp_server->AddSafeResourceUri(object, rooturi, ips, NPT_String(subtitlePath.c_str()), protocolInfo); - // add subtitle resource with smi/caption protocol info (some devices) - PLT_ProtocolInfo protInfo = PLT_ProtocolInfo(protocolInfo); - protocolInfo = protInfo.GetProtocol() + ":" + protInfo.GetMask() + ":smi/caption:" + protInfo.GetExtra(); - upnp_server->AddSafeResourceUri(object, rooturi, ips, NPT_String(subtitlePath.c_str()), protocolInfo); - - ext = URIUtils::GetExtension(subtitlePath).c_str(); - ext = ext.substr(1); - std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); - - NPT_String subtitle_uri = object->m_Resources[object->m_Resources.GetItemCount() - 1].m_Uri; - - // add subtitle to video resource (the first one) (for some devices) - object->m_Resources[0].m_CustomData["xmlns:pv"] = "http://www.pv.com/pvns/"; - object->m_Resources[0].m_CustomData["pv:subtitleFileUri"] = subtitle_uri; - object->m_Resources[0].m_CustomData["pv:subtitleFileType"] = ext.c_str(); - - // for samsung devices - PLT_SecResource sec_res; - sec_res.name = "CaptionInfoEx"; - sec_res.value = subtitle_uri; - sec_res.attributes["type"] = ext.c_str(); - object->m_SecResources.Add(sec_res); - sec_res.name = "CaptionInfo"; - object->m_SecResources.Add(sec_res); - - // adding subtitle uri for movie md5, for later use in http response - NPT_String movie_md5 = object->m_Resources[0].m_Uri; - movie_md5 = movie_md5.Right(movie_md5.GetLength() - movie_md5.Find("/%25/") - 5); - upnp_server->AddSubtitleUriForSecResponse(movie_md5, subtitle_uri); - } + // add subtitle resource with standard protocolInfo + NPT_String protocolInfo = GetProtocolInfo(CFileItem(subtitlePath, false), "http", context); + upnp_server->AddSafeResourceUri(object, rooturi, ips, NPT_String(subtitlePath.c_str()), + protocolInfo); + // add subtitle resource with smi/caption protocol info (some devices) + PLT_ProtocolInfo protInfo = PLT_ProtocolInfo(protocolInfo); + protocolInfo = + protInfo.GetProtocol() + ":" + protInfo.GetMask() + ":smi/caption:" + protInfo.GetExtra(); + upnp_server->AddSafeResourceUri(object, rooturi, ips, NPT_String(subtitlePath.c_str()), + protocolInfo); + + ext = URIUtils::GetExtension(subtitlePath).c_str(); + ext = ext.substr(1); + std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); + + NPT_String subtitle_uri = object->m_Resources[object->m_Resources.GetItemCount() - 1].m_Uri; + + // add subtitle to video resource (the first one) (for some devices) + object->m_Resources[0].m_CustomData["xmlns:pv"] = "http://www.pv.com/pvns/"; + object->m_Resources[0].m_CustomData["pv:subtitleFileUri"] = subtitle_uri; + object->m_Resources[0].m_CustomData["pv:subtitleFileType"] = ext.c_str(); + + // for samsung devices + PLT_SecResource sec_res; + sec_res.name = "CaptionInfoEx"; + sec_res.value = subtitle_uri; + sec_res.attributes["type"] = ext.c_str(); + object->m_SecResources.Add(sec_res); + sec_res.name = "CaptionInfo"; + object->m_SecResources.Add(sec_res); + + // adding subtitle uri for movie md5, for later use in http response + NPT_String movie_md5 = object->m_Resources[0].m_Uri; + movie_md5 = movie_md5.Right(movie_md5.GetLength() - movie_md5.Find("/%25/") - 5); + upnp_server->AddSubtitleUriForSecResponse(movie_md5, subtitle_uri); } + } - return object; + return object; failure: - delete object; - return NULL; + delete object; + return NULL; } /*---------------------------------------------------------------------- | CUPnPServer::CorrectAllItemsSortHack +---------------------------------------------------------------------*/ -const std::string& -CorrectAllItemsSortHack(const std::string &item) +const std::string& CorrectAllItemsSortHack(const std::string& item) { - // This is required as in order for the "* All Albums" etc. items to sort - // correctly, they must have fake artist/album etc. information generated. - // This looks nasty if we attempt to render it to the GUI, thus this (further) - // workaround - if ((item.size() == 1 && item[0] == 0x01) || (item.size() > 1 && ((unsigned char) item[1]) == 0xff)) - return StringUtils::Empty; - - return item; + // This is required as in order for the "* All Albums" etc. items to sort + // correctly, they must have fake artist/album etc. information generated. + // This looks nasty if we attempt to render it to the GUI, thus this (further) + // workaround + if ((item.size() == 1 && item[0] == 0x01) || + (item.size() > 1 && ((unsigned char)item[1]) == 0xff)) + return StringUtils::Empty; + + return item; } -int -PopulateTagFromObject(CMusicInfoTag& tag, - PLT_MediaObject& object, - PLT_MediaItemResource* resource /* = NULL */, - UPnPService service /* = UPnPServiceNone */) +int PopulateTagFromObject(CMusicInfoTag& tag, + PLT_MediaObject& object, + PLT_MediaItemResource* resource /* = NULL */, + UPnPService service /* = UPnPServiceNone */) { - tag.SetTitle((const char*)object.m_Title); - tag.SetArtist((const char*)object.m_Creator); - for(PLT_PersonRoles::Iterator it = object.m_People.artists.GetFirstItem(); it; it++) { - if (it->role == "") tag.SetArtist((const char*)it->name); - else if(it->role == "Performer") tag.SetArtist((const char*)it->name); - else if(it->role == "AlbumArtist") tag.SetAlbumArtist((const char*)it->name); - } - tag.SetTrackNumber(object.m_MiscInfo.original_track_number); + tag.SetTitle((const char*)object.m_Title); + tag.SetArtist((const char*)object.m_Creator); + for (PLT_PersonRoles::Iterator it = object.m_People.artists.GetFirstItem(); it; it++) + { + if (it->role == "") + tag.SetArtist((const char*)it->name); + else if (it->role == "Performer") + tag.SetArtist((const char*)it->name); + else if (it->role == "AlbumArtist") + tag.SetAlbumArtist((const char*)it->name); + } + tag.SetTrackNumber(object.m_MiscInfo.original_track_number); - for (NPT_List::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++) { - // ignore single "Unknown" genre inserted by Platinum - if (it == object.m_Affiliation.genres.GetFirstItem() && object.m_Affiliation.genres.GetItemCount() == 1 && - *it == "Unknown") - break; + for (NPT_List::Iterator it = object.m_Affiliation.genres.GetFirstItem(); it; it++) + { + // ignore single "Unknown" genre inserted by Platinum + if (it == object.m_Affiliation.genres.GetFirstItem() && + object.m_Affiliation.genres.GetItemCount() == 1 && *it == "Unknown") + break; - tag.SetGenre((const char*) *it); - } + tag.SetGenre((const char*)*it); + } - tag.SetAlbum((const char*)object.m_Affiliation.album); - CDateTime last; - last.SetFromW3CDateTime((const char*)object.m_MiscInfo.last_time); - tag.SetLastPlayed(last); - tag.SetPlayCount(object.m_MiscInfo.play_count); - if(resource) - tag.SetDuration(resource->m_Duration); - tag.SetLoaded(); - return NPT_SUCCESS; + tag.SetAlbum((const char*)object.m_Affiliation.album); + CDateTime last; + last.SetFromW3CDateTime((const char*)object.m_MiscInfo.last_time); + tag.SetLastPlayed(last); + tag.SetPlayCount(object.m_MiscInfo.play_count); + if (resource) + tag.SetDuration(resource->m_Duration); + tag.SetLoaded(); + return NPT_SUCCESS; } -int -PopulateTagFromObject(CVideoInfoTag& tag, - PLT_MediaObject& object, - PLT_MediaItemResource* resource /* = NULL */, - UPnPService service /* = UPnPServiceNone */) +int PopulateTagFromObject(CVideoInfoTag& tag, + PLT_MediaObject& object, + PLT_MediaItemResource* resource /* = NULL */, + UPnPService service /* = UPnPServiceNone */) { - CDateTime date; - date.SetFromW3CDate((const char*)object.m_Date); + CDateTime date; + date.SetFromW3CDate((const char*)object.m_Date); - if(!object.m_Recorded.program_title.IsEmpty() || object.m_ObjectClass.type == "object.item.videoItem.videoBroadcast") - { - tag.m_type = MediaTypeEpisode; - tag.m_strShowTitle = object.m_Recorded.series_title; - if (date.IsValid()) - tag.m_firstAired = date; + if (!object.m_Recorded.program_title.IsEmpty() || + object.m_ObjectClass.type == "object.item.videoItem.videoBroadcast") + { + tag.m_type = MediaTypeEpisode; + tag.m_strShowTitle = object.m_Recorded.series_title; + if (date.IsValid()) + tag.m_firstAired = date; - int title = object.m_Recorded.program_title.Find(" : "); - if (title >= 0) - tag.m_strTitle = object.m_Recorded.program_title.SubString(title + 3); - else - tag.m_strTitle = object.m_Recorded.program_title; - - int episode; - int season; - if (object.m_Recorded.episode_number > 0 && object.m_Recorded.episode_season < (NPT_UInt32)-1) { - tag.m_iEpisode = object.m_Recorded.episode_number; - tag.m_iSeason = object.m_Recorded.episode_season; - } else if(sscanf(object.m_Recorded.program_title, "S%2dE%2d", &season, &episode) == 2 && title >= 0) { - tag.m_iEpisode = episode; - tag.m_iSeason = season; - } else { - tag.m_iSeason = object.m_Recorded.episode_number / 100; - tag.m_iEpisode = object.m_Recorded.episode_number % 100; - } - } - else { - tag.m_strTitle = object.m_Title; - if (date.IsValid()) - tag.m_premiered = date; - - if (!object.m_Recorded.series_title.IsEmpty()) { - if (object.m_ObjectClass.type == "object.container.album.videoAlbum.videoBroadcastSeason") { - tag.m_type = MediaTypeSeason; - tag.m_iSeason = object.m_Recorded.episode_season; - tag.m_strShowTitle = object.m_Recorded.series_title; - } - else { - tag.m_type = MediaTypeTvShow; - tag.m_strShowTitle = object.m_Title; - } - - if (object.m_Recorded.episode_count > 0) - tag.m_iEpisode = object.m_Recorded.episode_count; - else - tag.m_iEpisode = object.m_Recorded.episode_number; - } - else if(object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") { - tag.m_type = MediaTypeMusicVideo; - - if (object.m_People.artists.GetItemCount() > 0) { - for (unsigned int index = 0; index < object.m_People.artists.GetItemCount(); index++) - tag.m_artist.emplace_back(object.m_People.artists.GetItem(index)->name.GetChars()); - } - else if (!object.m_Creator.IsEmpty() && object.m_Creator != "Unknown") - tag.m_artist = StringUtils::Split(object.m_Creator.GetChars(), CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); - tag.m_strAlbum = object.m_Affiliation.album; - } - else - tag.m_type = MediaTypeMovie; + int title = object.m_Recorded.program_title.Find(" : "); + if (title >= 0) + tag.m_strTitle = object.m_Recorded.program_title.SubString(title + 3); + else + tag.m_strTitle = object.m_Recorded.program_title; - tag.m_strTitle = object.m_Title; - if (date.IsValid()) - tag.SetPremiered(date); + int episode; + int season; + if (object.m_Recorded.episode_number > 0 && object.m_Recorded.episode_season < (NPT_UInt32)-1) + { + tag.m_iEpisode = object.m_Recorded.episode_number; + tag.m_iSeason = object.m_Recorded.episode_season; } - - for (unsigned int index = 0; index < object.m_People.publisher.GetItemCount(); index++) - tag.m_studio.emplace_back(object.m_People.publisher.GetItem(index)->GetChars()); - - tag.m_dateAdded.SetFromW3CDate((const char*)object.m_XbmcInfo.date_added); - tag.SetRating(object.m_XbmcInfo.rating, object.m_XbmcInfo.votes); - tag.SetUniqueID(object.m_XbmcInfo.unique_identifier.GetChars()); - for (unsigned int index = 0; index < object.m_XbmcInfo.countries.GetItemCount(); index++) - tag.m_country.emplace_back(object.m_XbmcInfo.countries.GetItem(index)->GetChars()); - tag.m_iUserRating = object.m_XbmcInfo.user_rating; - - for (unsigned int index = 0; index < object.m_Affiliation.genres.GetItemCount(); index++) + else if (sscanf(object.m_Recorded.program_title, "S%2dE%2d", &season, &episode) == 2 && + title >= 0) { - // ignore single "Unknown" genre inserted by Platinum - if (index == 0 && object.m_Affiliation.genres.GetItemCount() == 1 && - *object.m_Affiliation.genres.GetItem(index) == "Unknown") - break; - - tag.m_genre.emplace_back(object.m_Affiliation.genres.GetItem(index)->GetChars()); + tag.m_iEpisode = episode; + tag.m_iSeason = season; } - for (unsigned int index = 0; index < object.m_People.directors.GetItemCount(); index++) - tag.m_director.emplace_back(object.m_People.directors.GetItem(index)->name.GetChars()); - for (unsigned int index = 0; index < object.m_People.authors.GetItemCount(); index++) - tag.m_writingCredits.emplace_back(object.m_People.authors.GetItem(index)->name.GetChars()); - for (unsigned int index = 0; index < object.m_People.actors.GetItemCount(); index++) + else { - SActorInfo info; - info.strName = object.m_People.actors.GetItem(index)->name; - info.strRole = object.m_People.actors.GetItem(index)->role; - tag.m_cast.push_back(info); + tag.m_iSeason = object.m_Recorded.episode_number / 100; + tag.m_iEpisode = object.m_Recorded.episode_number % 100; } - tag.m_strTagLine = object.m_Description.description; - tag.m_strPlot = object.m_Description.long_description; - tag.m_strMPAARating = object.m_Description.rating; - tag.m_strShowTitle = object.m_Recorded.series_title; - tag.m_lastPlayed.SetFromW3CDateTime((const char*)object.m_MiscInfo.last_time); - tag.SetPlayCount(object.m_MiscInfo.play_count); + } + else + { + tag.m_strTitle = object.m_Title; + if (date.IsValid()) + tag.m_premiered = date; - if(resource) + if (!object.m_Recorded.series_title.IsEmpty()) { - if (resource->m_Duration) - tag.SetDuration(resource->m_Duration); - if (object.m_MiscInfo.last_position > 0 ) + if (object.m_ObjectClass.type == "object.container.album.videoAlbum.videoBroadcastSeason") { - tag.SetResumePoint(object.m_MiscInfo.last_position, - resource->m_Duration, - object.m_XbmcInfo.last_playerstate.GetChars()); + tag.m_type = MediaTypeSeason; + tag.m_iSeason = object.m_Recorded.episode_season; + tag.m_strShowTitle = object.m_Recorded.series_title; } - if (!resource->m_Resolution.IsEmpty()) + else { - int width, height; - if (sscanf(resource->m_Resolution, "%dx%d", &width, &height) == 2) - { - CStreamDetailVideo* detail = new CStreamDetailVideo; - detail->m_iWidth = width; - detail->m_iHeight = height; - detail->m_iDuration = tag.GetDuration(); - tag.m_streamDetails.AddStream(detail); - } + tag.m_type = MediaTypeTvShow; + tag.m_strShowTitle = object.m_Title; + } + + if (object.m_Recorded.episode_count > 0) + tag.m_iEpisode = object.m_Recorded.episode_count; + else + tag.m_iEpisode = object.m_Recorded.episode_number; + } + else if (object.m_ObjectClass.type == "object.item.videoItem.musicVideoClip") + { + tag.m_type = MediaTypeMusicVideo; + + if (object.m_People.artists.GetItemCount() > 0) + { + for (unsigned int index = 0; index < object.m_People.artists.GetItemCount(); index++) + tag.m_artist.emplace_back(object.m_People.artists.GetItem(index)->name.GetChars()); } - if (resource->m_NbAudioChannels > 0) + else if (!object.m_Creator.IsEmpty() && object.m_Creator != "Unknown") + tag.m_artist = StringUtils::Split( + object.m_Creator.GetChars(), + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_videoItemSeparator); + tag.m_strAlbum = object.m_Affiliation.album; + } + else + tag.m_type = MediaTypeMovie; + + tag.m_strTitle = object.m_Title; + if (date.IsValid()) + tag.SetPremiered(date); + } + + for (unsigned int index = 0; index < object.m_People.publisher.GetItemCount(); index++) + tag.m_studio.emplace_back(object.m_People.publisher.GetItem(index)->GetChars()); + + tag.m_dateAdded.SetFromW3CDate((const char*)object.m_XbmcInfo.date_added); + tag.SetRating(object.m_XbmcInfo.rating, object.m_XbmcInfo.votes); + tag.SetUniqueID(object.m_XbmcInfo.unique_identifier.GetChars()); + for (unsigned int index = 0; index < object.m_XbmcInfo.countries.GetItemCount(); index++) + tag.m_country.emplace_back(object.m_XbmcInfo.countries.GetItem(index)->GetChars()); + tag.m_iUserRating = object.m_XbmcInfo.user_rating; + + for (unsigned int index = 0; index < object.m_Affiliation.genres.GetItemCount(); index++) + { + // ignore single "Unknown" genre inserted by Platinum + if (index == 0 && object.m_Affiliation.genres.GetItemCount() == 1 && + *object.m_Affiliation.genres.GetItem(index) == "Unknown") + break; + + tag.m_genre.emplace_back(object.m_Affiliation.genres.GetItem(index)->GetChars()); + } + for (unsigned int index = 0; index < object.m_People.directors.GetItemCount(); index++) + tag.m_director.emplace_back(object.m_People.directors.GetItem(index)->name.GetChars()); + for (unsigned int index = 0; index < object.m_People.authors.GetItemCount(); index++) + tag.m_writingCredits.emplace_back(object.m_People.authors.GetItem(index)->name.GetChars()); + for (unsigned int index = 0; index < object.m_People.actors.GetItemCount(); index++) + { + SActorInfo info; + info.strName = object.m_People.actors.GetItem(index)->name; + info.strRole = object.m_People.actors.GetItem(index)->role; + tag.m_cast.push_back(info); + } + tag.m_strTagLine = object.m_Description.description; + tag.m_strPlot = object.m_Description.long_description; + tag.m_strMPAARating = object.m_Description.rating; + tag.m_strShowTitle = object.m_Recorded.series_title; + tag.m_lastPlayed.SetFromW3CDateTime((const char*)object.m_MiscInfo.last_time); + tag.SetPlayCount(object.m_MiscInfo.play_count); + + if (resource) + { + if (resource->m_Duration) + tag.SetDuration(resource->m_Duration); + if (object.m_MiscInfo.last_position > 0) + { + tag.SetResumePoint(object.m_MiscInfo.last_position, resource->m_Duration, + object.m_XbmcInfo.last_playerstate.GetChars()); + } + if (!resource->m_Resolution.IsEmpty()) + { + int width, height; + if (sscanf(resource->m_Resolution, "%dx%d", &width, &height) == 2) { - CStreamDetailAudio* detail = new CStreamDetailAudio; - detail->m_iChannels = resource->m_NbAudioChannels; + CStreamDetailVideo* detail = new CStreamDetailVideo; + detail->m_iWidth = width; + detail->m_iHeight = height; + detail->m_iDuration = tag.GetDuration(); tag.m_streamDetails.AddStream(detail); } } - return NPT_SUCCESS; + if (resource->m_NbAudioChannels > 0) + { + CStreamDetailAudio* detail = new CStreamDetailAudio; + detail->m_iChannels = resource->m_NbAudioChannels; + tag.m_streamDetails.AddStream(detail); + } + } + return NPT_SUCCESS; } std::shared_ptr BuildObject(PLT_MediaObject* entry, @@ -1042,38 +1154,44 @@ std::shared_ptr BuildObject(PLT_MediaObject* entry, pItem->m_bIsFolder = entry->IsContainer(); // if it's a container, format a string as upnp://uuid/object_id - if (pItem->m_bIsFolder) { + if (pItem->m_bIsFolder) + { // look for metadata - if( ObjectClass.StartsWith("object.container.album.videoalbum") ) { + if (ObjectClass.StartsWith("object.container.album.videoalbum")) + { pItem->SetLabelPreformatted(false); UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, NULL, upnp_service); - - } else if( ObjectClass.StartsWith("object.container.album.photoalbum")) { + } + else if (ObjectClass.StartsWith("object.container.album.photoalbum")) + { //CPictureInfoTag* tag = pItem->GetPictureInfoTag(); - - } else if( ObjectClass.StartsWith("object.container.album") ) { + } + else if (ObjectClass.StartsWith("object.container.album")) + { pItem->SetLabelPreformatted(false); UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, NULL, upnp_service); } - - } else { - bool audio = false - , image = false - , video = false; + } + else + { + bool audio = false, image = false, video = false; // set a general content type const char* content = NULL; - if (ObjectClass.StartsWith("object.item.videoitem")) { + if (ObjectClass.StartsWith("object.item.videoitem")) + { pItem->SetMimeType("video/octet-stream"); content = "video"; video = true; } - else if(ObjectClass.StartsWith("object.item.audioitem")) { + else if (ObjectClass.StartsWith("object.item.audioitem")) + { pItem->SetMimeType("audio/octet-stream"); content = "audio"; audio = true; } - else if(ObjectClass.StartsWith("object.item.imageitem")) { + else if (ObjectClass.StartsWith("object.item.imageitem")) + { pItem->SetMimeType("image/octet-stream"); content = "image"; image = true; @@ -1081,32 +1199,38 @@ std::shared_ptr BuildObject(PLT_MediaObject* entry, // attempt to find a valid resource (may be multiple) PLT_MediaItemResource resource, *res = NULL; - if(NPT_SUCCEEDED(NPT_ContainerFind(entry->m_Resources, - CResourceFinder("http-get", content), resource))) { + if (NPT_SUCCEEDED( + NPT_ContainerFind(entry->m_Resources, CResourceFinder("http-get", content), resource))) + { // set metadata - if (resource.m_Size != (NPT_LargeSize)-1) { - pItem->m_dwSize = resource.m_Size; + if (resource.m_Size != (NPT_LargeSize)-1) + { + pItem->m_dwSize = resource.m_Size; } res = &resource; } // look for metadata - if(video) { - pItem->SetLabelPreformatted(false); - UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res, upnp_service); - - } else if(audio) { - pItem->SetLabelPreformatted(false); - UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res, upnp_service); - - } else if(image) { + if (video) + { + pItem->SetLabelPreformatted(false); + UPNP::PopulateTagFromObject(*pItem->GetVideoInfoTag(), *entry, res, upnp_service); + } + else if (audio) + { + pItem->SetLabelPreformatted(false); + UPNP::PopulateTagFromObject(*pItem->GetMusicInfoTag(), *entry, res, upnp_service); + } + else if (image) + { //! @todo fill pictureinfotag? GetResource(entry, *pItem); } } // look for date? - if(entry->m_Description.date.GetLength()) { + if (entry->m_Description.date.GetLength()) + { KODI::TIME::SystemTime time = {}; sscanf(entry->m_Description.date, "%hu-%hu-%huT%hu:%hu:%hu", &time.year, &time.month, &time.day, &time.hour, &time.minute, &time.second); @@ -1114,24 +1238,26 @@ std::shared_ptr BuildObject(PLT_MediaObject* entry, } // if there is a thumbnail available set it here - if(entry->m_ExtraInfo.album_arts.GetItem(0)) + if (entry->m_ExtraInfo.album_arts.GetItem(0)) // only considers first album art - pItem->SetArt("thumb", (const char*) entry->m_ExtraInfo.album_arts.GetItem(0)->uri); - else if(entry->m_Description.icon_uri.GetLength()) - pItem->SetArt("thumb", (const char*) entry->m_Description.icon_uri); + pItem->SetArt("thumb", (const char*)entry->m_ExtraInfo.album_arts.GetItem(0)->uri); + else if (entry->m_Description.icon_uri.GetLength()) + pItem->SetArt("thumb", (const char*)entry->m_Description.icon_uri); for (unsigned int index = 0; index < entry->m_XbmcInfo.artwork.GetItemCount(); index++) - pItem->SetArt(entry->m_XbmcInfo.artwork.GetItem(index)->type.GetChars(), - entry->m_XbmcInfo.artwork.GetItem(index)->url.GetChars()); + pItem->SetArt(entry->m_XbmcInfo.artwork.GetItem(index)->type.GetChars(), + entry->m_XbmcInfo.artwork.GetItem(index)->url.GetChars()); // set the watched overlay, as this will not be set later due to // content set on file item list - if (pItem->HasVideoInfoTag()) { + if (pItem->HasVideoInfoTag()) + { int episodes = pItem->GetVideoInfoTag()->m_iEpisode; - int played = pItem->GetVideoInfoTag()->GetPlayCount(); + int played = pItem->GetVideoInfoTag()->GetPlayCount(); const std::string& type = pItem->GetVideoInfoTag()->m_type; bool watched(false); - if (type == MediaTypeTvShow || type == MediaTypeSeason) { + if (type == MediaTypeTvShow || type == MediaTypeSeason) + { pItem->SetProperty("totalepisodes", episodes); pItem->SetProperty("numepisodes", episodes); pItem->SetProperty("watchedepisodes", played); @@ -1152,38 +1278,38 @@ struct ResourcePrioritySort explicit ResourcePrioritySort(const PLT_MediaObject* entry) { if (entry->m_ObjectClass.type.StartsWith("object.item.audioItem")) - m_content = "audio"; + m_content = "audio"; else if (entry->m_ObjectClass.type.StartsWith("object.item.imageItem")) - m_content = "image"; + m_content = "image"; else if (entry->m_ObjectClass.type.StartsWith("object.item.videoItem")) - m_content = "video"; + m_content = "video"; } - int GetPriority(const PLT_MediaItemResource& res) const + int GetPriority(const PLT_MediaItemResource& res) const { int prio = 0; if (m_content != "" && res.m_ProtocolInfo.GetContentType().StartsWith(m_content)) - prio += 400; + prio += 400; NPT_Url url(res.m_Uri); if (URIUtils::IsHostOnLAN(url.GetHost().GetChars(), LanCheckMode::ONLY_LOCAL_SUBNET)) prio += 300; if (res.m_ProtocolInfo.GetProtocol() == "xbmc-get") - prio += 200; + prio += 200; else if (res.m_ProtocolInfo.GetProtocol() == "http-get") - prio += 100; + prio += 100; return prio; } int operator()(const PLT_MediaItemResource& lh, const PLT_MediaItemResource& rh) const { - if(GetPriority(lh) < GetPriority(rh)) - return 1; + if (GetPriority(lh) < GetPriority(rh)) + return 1; else - return 0; + return 0; } NPT_String m_content; @@ -1196,17 +1322,18 @@ bool GetResource(const PLT_MediaObject* entry, CFileItem& item) PLT_MediaItemResource resource; // store original path so we remember it - item.SetProperty("original_listitem_url", item.GetPath()); + item.SetProperty("original_listitem_url", item.GetPath()); item.SetProperty("original_listitem_mime", item.GetMimeType()); // get a sorted list based on our preference NPT_List sorted; - for (NPT_Cardinal i = 0; i < entry->m_Resources.GetItemCount(); ++i) { - sorted.Add(entry->m_Resources[i]); + for (NPT_Cardinal i = 0; i < entry->m_Resources.GetItemCount(); ++i) + { + sorted.Add(entry->m_Resources[i]); } sorted.Sort(ResourcePrioritySort(entry)); - if(sorted.GetItemCount() == 0) + if (sorted.GetItemCount() == 0) return false; resource = *sorted.GetFirstItem(); @@ -1214,13 +1341,15 @@ bool GetResource(const PLT_MediaObject* entry, CFileItem& item) // if it's an item, path is the first url to the item // we hope the server made the first one reachable for us // (it could be a format we dont know how to play however) - item.SetDynPath((const char*) resource.m_Uri); + item.SetDynPath((const char*)resource.m_Uri); // look for content type in protocol info - if (resource.m_ProtocolInfo.IsValid()) { + if (resource.m_ProtocolInfo.IsValid()) + { logger->debug("resource protocol info '{}'", (const char*)(resource.m_ProtocolInfo.ToString())); - if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) { + if (resource.m_ProtocolInfo.GetContentType().Compare("application/octet-stream") != 0) + { item.SetMimeType((const char*)resource.m_ProtocolInfo.GetContentType()); } @@ -1229,17 +1358,19 @@ bool GetResource(const PLT_MediaObject* entry, CFileItem& item) { item.SetArt("thumb", std::string(resource.m_Uri)); } - } else { + } + else + { logger->error("invalid protocol info '{}'", (const char*)(resource.m_ProtocolInfo.ToString())); } // look for subtitles unsigned subIdx = 0; - for(unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++) + for (unsigned r = 0; r < entry->m_Resources.GetItemCount(); r++) { - const PLT_MediaItemResource& res = entry->m_Resources[r]; - const PLT_ProtocolInfo& info = res.m_ProtocolInfo; + const PLT_MediaItemResource& res = entry->m_Resources[r]; + const PLT_ProtocolInfo& info = res.m_ProtocolInfo; for (std::string_view type : SupportedSubFormats) { @@ -1259,25 +1390,30 @@ bool GetResource(const PLT_MediaObject* entry, CFileItem& item) std::shared_ptr GetFileItem(const NPT_String& uri, const NPT_String& meta) { - PLT_MediaObjectListReference list; - PLT_MediaObject* object = NULL; - CFileItemPtr item; + PLT_MediaObjectListReference list; + PLT_MediaObject* object = NULL; + CFileItemPtr item; - if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) { - list->Get(0, object); - } + if (NPT_SUCCEEDED(PLT_Didl::FromDidl(meta, list))) + { + list->Get(0, object); + } - if (object) { - item = BuildObject(object); - } + if (object) + { + item = BuildObject(object); + } - if (item) { - item->SetPath((const char*)uri); - GetResource(object, *item); - } else { - item.reset(new CFileItem((const char*)uri, false)); - } - return item; + if (item) + { + item->SetPath((const char*)uri); + GetResource(object, *item); + } + else + { + item.reset(new CFileItem((const char*)uri, false)); + } + return item; } NPT_String EncodeObjectId(const std::string& id) @@ -1329,11 +1465,11 @@ NPT_String DecodeObjectId(const std::string& id) const std::string decodedObjectId = Base64::Decode(id); if (decodedObjectId.empty()) { - CLog::LogF(LOGERROR, "Failed to decode object id {}, not properly Base64 encoded", decodedObjectId); + CLog::LogF(LOGERROR, "Failed to decode object id {}, not properly Base64 encoded", + decodedObjectId); } return decodedObjectId.c_str(); } } /* namespace UPNP */ - From 4443998e27e78c7189af419272df47be6495bdae Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 13 Oct 2023 14:52:11 +1000 Subject: [PATCH 394/811] [clang-tidy] modernize-make-shared --- .clang-tidy | 1 + xbmc/FileItem.cpp | 9 +- xbmc/addons/AddonInstaller.cpp | 9 +- xbmc/addons/gui/GUIDialogAddonInfo.cpp | 4 +- xbmc/addons/settings/AddonSettings.cpp | 3 +- xbmc/application/Application.cpp | 189 ++++++++---------- .../application/ApplicationPlayerCallback.cpp | 6 +- .../DVDDemuxers/DVDDemuxClient.cpp | 10 +- .../DVDInputStreams/DVDFactoryInputStream.cpp | 39 ++-- .../DVDInputStreams/InputStreamAddon.cpp | 18 +- xbmc/dialogs/GUIDialogColorPicker.cpp | 3 +- xbmc/dialogs/GUIDialogSelect.cpp | 6 +- xbmc/filesystem/BlurayDirectory.cpp | 5 +- xbmc/filesystem/ShoutcastFile.cpp | 3 +- xbmc/filesystem/SmartPlaylistDirectory.cpp | 3 +- xbmc/games/addons/input/GameClientInput.cpp | 4 +- xbmc/input/IRTranslator.cpp | 3 +- xbmc/input/JoystickMapper.cpp | 3 +- xbmc/interfaces/AnnouncementManager.cpp | 3 +- .../generic/ScriptInvocationManager.cpp | 4 +- xbmc/interfaces/json-rpc/AudioLibrary.cpp | 8 +- xbmc/interfaces/json-rpc/FileItemHandler.cpp | 3 +- xbmc/interfaces/json-rpc/FileOperations.cpp | 6 +- .../json-rpc/JSONServiceDescription.cpp | 22 +- xbmc/interfaces/json-rpc/PVROperations.cpp | 8 +- xbmc/interfaces/json-rpc/VideoLibrary.cpp | 29 ++- xbmc/interfaces/legacy/ListItem.cpp | 3 +- xbmc/interfaces/legacy/ListItem.h | 3 +- .../python/ContextItemAddonInvoker.cpp | 10 +- xbmc/messaging/ApplicationMessenger.cpp | 2 +- xbmc/music/MusicUtils.cpp | 4 +- xbmc/music/windows/GUIWindowMusicBase.cpp | 39 ++-- .../windows/MusicFileItemListModifier.cpp | 8 +- xbmc/network/upnp/UPnPInternal.cpp | 3 +- xbmc/network/upnp/UPnPServer.cpp | 10 +- xbmc/pictures/GUIWindowSlideShow.cpp | 3 +- xbmc/profiles/ProfileManager.cpp | 37 ++-- xbmc/pvr/PVRManager.cpp | 10 +- xbmc/pvr/addons/PVRClient.cpp | 2 +- .../pvr/dialogs/GUIDialogPVRTimerSettings.cpp | 4 +- xbmc/pvr/epg/EpgContainer.cpp | 4 +- xbmc/pvr/epg/EpgInfoTag.cpp | 2 +- xbmc/pvr/filesystem/PVRGUIDirectory.cpp | 40 ++-- xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp | 2 +- xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp | 5 +- xbmc/pvr/timers/PVRTimerInfoTag.cpp | 2 +- xbmc/pvr/timers/PVRTimers.cpp | 5 +- .../dialogs/GUIDialogContentSettings.cpp | 25 ++- .../dialogs/GUIDialogSettingsBase.cpp | 37 ++-- xbmc/threads/test/TestEvent.cpp | 6 +- xbmc/utils/BooleanLogic.cpp | 4 +- xbmc/utils/FileOperationJob.cpp | 4 +- xbmc/video/VideoDatabase.cpp | 2 +- xbmc/video/VideoInfoScanner.cpp | 5 +- xbmc/video/dialogs/GUIDialogAudioSettings.cpp | 8 +- xbmc/video/dialogs/GUIDialogCMSSettings.cpp | 21 +- .../video/jobs/VideoLibraryMarkWatchedJob.cpp | 12 +- xbmc/video/jobs/VideoLibraryRefreshingJob.cpp | 5 +- xbmc/video/windows/GUIWindowVideoBase.cpp | 4 +- .../windows/VideoFileItemListModifier.cpp | 6 +- 60 files changed, 402 insertions(+), 336 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index b5865a5de9243..2a0e381fa28f2 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,4 +1,5 @@ Checks: "\ + modernize-make-shared,\ modernize-use-default-member-init,\ performance-faster-string-find,\ performance-for-range-copy,\ diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 39a6e240fc161..476018cf6b3f7 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -61,6 +61,7 @@ #include #include +#include #include using namespace KODI; @@ -2105,11 +2106,11 @@ bool CFileItem::LoadTracksFromCueDocument(CFileItemList& scannedItems) if ( tag.Loaded() && oneFilePerTrack && ! ( tag.GetAlbum().empty() || tag.GetArtist().empty() || tag.GetTitle().empty() ) ) { // If there are multiple files in a cue file, the tags from the files should be preferred if they exist. - scannedItems.Add(CFileItemPtr(new CFileItem(song, tag))); + scannedItems.Add(std::make_shared(song, tag)); } else { - scannedItems.Add(CFileItemPtr(new CFileItem(song))); + scannedItems.Add(std::make_shared(song)); } ++tracksFound; } @@ -2460,7 +2461,7 @@ void CFileItemList::Sort(SortDescription sortDescription) SortItems sortItems((size_t)Size()); for (int index = 0; index < Size(); index++) { - sortItems[index] = std::shared_ptr(new SortItem); + sortItems[index] = std::make_shared(); m_items[index]->ToSortable(*sortItems[index], fields); (*sortItems[index])[FieldId] = index; } @@ -2542,7 +2543,7 @@ void CFileItemList::Archive(CArchive& ar) { CFileItemPtr pItem=m_items[0]; if (pItem->IsParentFolder()) - pParent.reset(new CFileItem(*pItem)); + pParent = std::make_shared(*pItem); } SetIgnoreURLOptions(false); diff --git a/xbmc/addons/AddonInstaller.cpp b/xbmc/addons/AddonInstaller.cpp index 0a0d1694dda90..3343bf1170e85 100644 --- a/xbmc/addons/AddonInstaller.cpp +++ b/xbmc/addons/AddonInstaller.cpp @@ -46,6 +46,7 @@ #include "utils/log.h" #include +#include #include using namespace XFILE; @@ -553,7 +554,7 @@ void CAddonInstaller::PrunePackageCache() { it->second->Sort(SortByLabel, SortOrderDescending); for (int j = 2; j < it->second->Size(); j++) - items.Add(CFileItemPtr(new CFileItem(*it->second->Get(j)))); + items.Add(std::make_shared(*it->second->Get(j))); } items.Sort(SortBySize, SortOrderDescending); @@ -572,7 +573,7 @@ void CAddonInstaller::PrunePackageCache() for (auto it = packs.begin(); it != packs.end(); ++it) { if (it->second->Size() > 1) - items.Add(CFileItemPtr(new CFileItem(*it->second->Get(1)))); + items.Add(std::make_shared(*it->second->Get(1))); } items.Sort(SortByDate, SortOrderAscending); @@ -626,7 +627,7 @@ int64_t CAddonInstaller::EnumeratePackageFolder( CAddonVersion::SplitFileName(pack, dummy, items[i]->GetLabel()); if (result.find(pack) == result.end()) result[pack] = std::make_unique(); - result[pack]->Add(CFileItemPtr(new CFileItem(*items[i]))); + result[pack]->Add(std::make_shared(*items[i])); } return size; @@ -979,7 +980,7 @@ bool CAddonInstallJob::DownloadPackage(const std::string &path, const std::strin // need to download/copy the package first CFileItemList list; - list.Add(CFileItemPtr(new CFileItem(path, false))); + list.Add(std::make_shared(path, false)); list[0]->Select(true); return DoFileOperation(CFileOperationJob::ActionReplace, list, dest, true); diff --git a/xbmc/addons/gui/GUIDialogAddonInfo.cpp b/xbmc/addons/gui/GUIDialogAddonInfo.cpp index b1a24a9687de6..671e55dcf4479 100644 --- a/xbmc/addons/gui/GUIDialogAddonInfo.cpp +++ b/xbmc/addons/gui/GUIDialogAddonInfo.cpp @@ -45,6 +45,7 @@ #include "utils/log.h" #include +#include #include #include @@ -65,7 +66,8 @@ using namespace XFILE; using namespace KODI::MESSAGING; CGUIDialogAddonInfo::CGUIDialogAddonInfo(void) - : CGUIDialog(WINDOW_DIALOG_ADDON_INFO, "DialogAddonInfo.xml"), m_item(CFileItemPtr(new CFileItem)) + : CGUIDialog(WINDOW_DIALOG_ADDON_INFO, "DialogAddonInfo.xml"), + m_item(std::make_shared()) { m_loadType = KEEP_IN_MEMORY; } diff --git a/xbmc/addons/settings/AddonSettings.cpp b/xbmc/addons/settings/AddonSettings.cpp index 1d4b808b316f7..fbf1dbe69faa2 100644 --- a/xbmc/addons/settings/AddonSettings.cpp +++ b/xbmc/addons/settings/AddonSettings.cpp @@ -39,6 +39,7 @@ #include #include +#include #include #include @@ -590,7 +591,7 @@ std::shared_ptr CAddonSettings::ParseOldSettingElement( category->AddGroup(group); // and create a new one - group.reset(new CSettingGroup(std::to_string(groupId), GetSettingsManager())); + group = std::make_shared(std::to_string(groupId), GetSettingsManager()); groupId += 1; } diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index c0408a965c098..8696ea9b06cc0 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -10,20 +10,32 @@ #include "Autorun.h" #include "CompileInfo.h" +#include "DatabaseManager.h" +#include "FileItem.h" #include "GUIInfoManager.h" +#include "GUILargeTextureManager.h" +#include "GUIPassword.h" +#include "GUIUserMessages.h" #include "HDRStatus.h" #include "LangInfo.h" +#include "PartyModeManager.h" #include "PlayListPlayer.h" +#include "SectionLoader.h" +#include "SeekHandler.h" +#include "ServiceBroker.h" #include "ServiceManager.h" +#include "TextureCache.h" #include "URL.h" #include "Util.h" #include "addons/AddonManager.h" +#include "addons/AddonSystemSettings.h" #include "addons/RepositoryUpdater.h" #include "addons/Service.h" #include "addons/Skin.h" #include "addons/VFSEntry.h" #include "addons/addoninfo/AddonInfo.h" #include "addons/addoninfo/AddonType.h" +#include "addons/gui/GUIDialogAddonSettings.h" #include "application/AppInboundProtocol.h" #include "application/AppParams.h" #include "application/ApplicationActionListeners.h" @@ -33,170 +45,141 @@ #include "application/ApplicationStackHelper.h" #include "application/ApplicationVolumeHandling.h" #include "cores/AudioEngine/Engines/ActiveAE/ActiveAE.h" +#include "cores/FFmpeg.h" #include "cores/IPlayer.h" #include "cores/playercorefactory/PlayerCoreFactory.h" #include "dialogs/GUIDialogBusy.h" #include "dialogs/GUIDialogCache.h" #include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogSimpleMenu.h" #include "events/EventLog.h" #include "events/NotificationEvent.h" +#include "filesystem/Directory.h" +#include "filesystem/DirectoryCache.h" #include "filesystem/DirectoryFactory.h" +#include "filesystem/DllLibCurl.h" #include "filesystem/File.h" +#ifdef HAS_FILESYSTEM_NFS +#include "filesystem/NFSFile.h" +#endif +#include "filesystem/PluginDirectory.h" +#include "filesystem/SpecialProtocol.h" +#ifdef HAS_UPNP +#include "filesystem/UPnPDirectory.h" +#endif +#include "guilib/GUIAudioManager.h" #include "guilib/GUIComponent.h" #include "guilib/GUIControlProfiler.h" #include "guilib/GUIFontManager.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" #include "guilib/StereoscopicsManager.h" #include "guilib/TextureManager.h" +#include "input/InertialScrollingHandler.h" +#include "input/InputManager.h" +#include "input/KeyboardLayoutManager.h" +#include "input/actions/ActionTranslator.h" +#include "interfaces/AnnouncementManager.h" #include "interfaces/builtins/Builtins.h" #include "interfaces/generic/ScriptInvocationManager.h" -#include "music/MusicLibraryQueue.h" -#include "music/tags/MusicInfoTag.h" -#include "network/EventServer.h" -#include "network/Network.h" -#include "platform/Environment.h" -#include "playlists/PlayListFactory.h" -#include "threads/SystemClock.h" -#include "utils/ContentUtils.h" -#include "utils/JobManager.h" -#include "utils/LangCodeExpander.h" -#include "utils/Screenshot.h" -#include "utils/Variant.h" -#include "video/Bookmark.h" -#include "video/VideoLibraryQueue.h" - +#include "interfaces/json-rpc/JSONRPC.h" #ifdef HAS_PYTHON #include "interfaces/python/XBPython.h" #endif -#include "GUILargeTextureManager.h" -#include "GUIPassword.h" -#include "GUIUserMessages.h" -#include "SectionLoader.h" -#include "SeekHandler.h" -#include "ServiceBroker.h" -#include "TextureCache.h" -#include "filesystem/Directory.h" -#include "filesystem/DirectoryCache.h" -#include "filesystem/DllLibCurl.h" -#include "filesystem/PluginDirectory.h" -#include "filesystem/SpecialProtocol.h" -#include "guilib/GUIAudioManager.h" -#include "guilib/LocalizeStrings.h" -#include "input/InertialScrollingHandler.h" -#include "input/KeyboardLayoutManager.h" -#include "input/actions/ActionTranslator.h" #include "messaging/ApplicationMessenger.h" #include "messaging/ThreadMessage.h" #include "messaging/helpers/DialogHelper.h" #include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicLibraryQueue.h" +#include "music/MusicThumbLoader.h" +#include "music/MusicUtils.h" +#include "music/infoscanner/MusicInfoScanner.h" +#include "music/tags/MusicInfoTag.h" +#include "network/EventServer.h" +#include "network/Network.h" +#include "network/ZeroconfBrowser.h" +#ifdef HAS_UPNP +#include "network/upnp/UPnP.h" +#endif +#include "peripherals/Peripherals.h" +#include "pictures/GUIWindowSlideShow.h" +#include "platform/Environment.h" #include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" #include "playlists/SmartPlayList.h" #include "powermanagement/PowerManager.h" #include "profiles/ProfileManager.h" +#include "pvr/PVRManager.h" +#include "pvr/guilib/PVRGUIActionsPlayback.h" +#include "pvr/guilib/PVRGUIActionsPowerManagement.h" #include "settings/AdvancedSettings.h" #include "settings/DisplaySettings.h" #include "settings/MediaSettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "speech/ISpeechRecognition.h" +#include "storage/MediaManager.h" #include "threads/SingleLock.h" +#include "threads/SystemClock.h" +#include "utils/AlarmClock.h" #include "utils/CPUInfo.h" +#include "utils/CharsetConverter.h" +#include "utils/ContentUtils.h" #include "utils/FileExtensionProvider.h" +#include "utils/JobManager.h" +#include "utils/LangCodeExpander.h" #include "utils/RegExp.h" +#include "utils/Screenshot.h" +#include "utils/StringUtils.h" #include "utils/SystemInfo.h" #include "utils/TimeUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" #include "utils/XTimeUtils.h" #include "utils/log.h" -#include "windowing/WinSystem.h" -#include "windowing/WindowSystemFactory.h" - -#include - -#ifdef HAS_UPNP -#include "network/upnp/UPnP.h" -#include "filesystem/UPnPDirectory.h" -#endif -#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB) -#include "platform/posix/filesystem/SMBFile.h" -#endif -#ifdef HAS_FILESYSTEM_NFS -#include "filesystem/NFSFile.h" -#endif -#include "PartyModeManager.h" -#include "network/ZeroconfBrowser.h" -#ifndef TARGET_POSIX -#include "platform/win32/threads/Win32Exception.h" -#endif -#include "interfaces/json-rpc/JSONRPC.h" -#include "interfaces/AnnouncementManager.h" -#include "peripherals/Peripherals.h" -#include "music/infoscanner/MusicInfoScanner.h" -#include "music/MusicUtils.h" -#include "music/MusicThumbLoader.h" - -// Windows includes -#include "guilib/GUIWindowManager.h" +#include "video/Bookmark.h" #include "video/PlayerController.h" - -// Dialog includes -#include "addons/gui/GUIDialogAddonSettings.h" -#include "dialogs/GUIDialogKaiToast.h" -#include "dialogs/GUIDialogSimpleMenu.h" +#include "video/VideoLibraryQueue.h" #include "video/dialogs/GUIDialogVideoBookmarks.h" - -// PVR related include Files -#include "pvr/PVRManager.h" -#include "pvr/guilib/PVRGUIActionsPlayback.h" -#include "pvr/guilib/PVRGUIActionsPowerManagement.h" - #ifdef TARGET_WINDOWS #include "win32util.h" #endif +#include "windowing/WinSystem.h" +#include "windowing/WindowSystemFactory.h" -#ifdef TARGET_DARWIN_OSX -#ifdef HAS_XBMCHELPER -#include "platform/darwin/osx/XBMCHelper.h" -#endif +#if defined(TARGET_ANDROID) +#include "platform/android/activity/XBMCApp.h" #endif #ifdef TARGET_DARWIN #include "platform/darwin/DarwinUtils.h" #endif - -#ifdef HAS_OPTICAL_DRIVE -#include +#ifdef TARGET_DARWIN_OSX +#ifdef HAS_XBMCHELPER +#include "platform/darwin/osx/XBMCHelper.h" +#endif #endif - -#include "DatabaseManager.h" -#include "input/InputManager.h" -#include "storage/MediaManager.h" -#include "utils/AlarmClock.h" -#include "utils/StringUtils.h" -#include "utils/URIUtils.h" - #ifdef TARGET_POSIX -#include "platform/posix/XHandle.h" #include "platform/posix/PlatformPosix.h" +#include "platform/posix/XHandle.h" #endif - -#if defined(TARGET_ANDROID) -#include "platform/android/activity/XBMCApp.h" +#if defined(TARGET_POSIX) && defined(HAS_FILESYSTEM_SMB) +#include "platform/posix/filesystem/SMBFile.h" #endif - -#ifdef TARGET_WINDOWS -#include "platform/Environment.h" +#ifndef TARGET_POSIX +#include "platform/win32/threads/Win32Exception.h" #endif +#include +#include +#include + //TODO: XInitThreads #ifdef HAVE_X11 #include #endif - -#include "FileItem.h" -#include "addons/AddonSystemSettings.h" -#include "cores/FFmpeg.h" -#include "pictures/GUIWindowSlideShow.h" -#include "utils/CharsetConverter.h" - -#include +#ifdef HAS_OPTICAL_DRIVE +#include +#endif using namespace ADDON; using namespace XFILE; @@ -2747,7 +2730,7 @@ bool CApplication::OnMessage(CGUIMessage& message) CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_CHANGED, 0, 0, CServiceBroker::GetPlaylistPlayer().GetCurrentPlaylist(), param, item); CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg); CServiceBroker::GetPlaylistPlayer().SetCurrentSong(m_nextPlaylistItem); - m_itemCurrentFile.reset(new CFileItem(*item)); + m_itemCurrentFile = std::make_shared(*item); } CServiceBroker::GetGUI()->GetInfoManager().SetCurrentItem(*m_itemCurrentFile); g_partyModeManager.OnSongChange(true); diff --git a/xbmc/application/ApplicationPlayerCallback.cpp b/xbmc/application/ApplicationPlayerCallback.cpp index 0dbb4b9fe578e..262a106e01629 100644 --- a/xbmc/application/ApplicationPlayerCallback.cpp +++ b/xbmc/application/ApplicationPlayerCallback.cpp @@ -35,6 +35,8 @@ #include "video/VideoDatabase.h" #include "video/VideoInfoTag.h" +#include + CApplicationPlayerCallback::CApplicationPlayerCallback() : m_itemCurrentFile(new CFileItem), m_playerEvent(true, true) { @@ -77,9 +79,9 @@ void CApplicationPlayerCallback::OnPlayBackStarted(const CFileItem& file) auto& components = CServiceBroker::GetAppComponents(); const auto stackHelper = components.GetComponent(); if (stackHelper->IsPlayingISOStack() || stackHelper->IsPlayingRegularStack()) - m_itemCurrentFile.reset(new CFileItem(*stackHelper->GetRegisteredStack(file))); + m_itemCurrentFile = std::make_shared(*stackHelper->GetRegisteredStack(file)); else - m_itemCurrentFile.reset(new CFileItem(file)); + m_itemCurrentFile = std::make_shared(file); /* When playing video pause any low priority jobs, they will be unpaused when playback stops. * This should speed up player startup for files on internet filesystems (eg. webdav) and diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp index 21ded53a1b9fd..0cdf8c3864ac3 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxClient.cpp @@ -432,7 +432,7 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map>(currentStream); if (forceInit || !streamAudio || streamAudio->codec != source->codec) { - streamAudio.reset(new CDemuxStreamClientInternalTpl()); + streamAudio = std::make_shared>(); streamAudio->m_parser = av_parser_init(source->codec); if (streamAudio->m_parser) streamAudio->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; @@ -469,7 +469,7 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map>(currentStream); if (forceInit || !streamVideo || streamVideo->codec != source->codec) { - streamVideo.reset(new CDemuxStreamClientInternalTpl()); + streamVideo = std::make_shared>(); streamVideo->m_parser = av_parser_init(source->codec); if (streamVideo->m_parser) streamVideo->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; @@ -513,7 +513,7 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map>(currentStream); if (!streamSubtitle || streamSubtitle->codec != source->codec) { - streamSubtitle.reset(new CDemuxStreamClientInternalTpl()); + streamSubtitle = std::make_shared>(); streamSubtitle->m_parser = av_parser_init(source->codec); if (streamSubtitle->m_parser) streamSubtitle->m_parser->flags |= PARSER_FLAG_COMPLETE_FRAMES; @@ -543,7 +543,7 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map>(currentStream); if (!streamTeletext || streamTeletext->codec != source->codec) { - streamTeletext.reset(new CDemuxStreamClientInternalTpl()); + streamTeletext = std::make_shared>(); } map[stream->uniqueId] = streamTeletext; @@ -566,7 +566,7 @@ void CDVDDemuxClient::SetStreamProps(CDemuxStream *stream, std::map>(currentStream); if (!streamRDS || streamRDS->codec != source->codec) { - streamRDS.reset(new CDemuxStreamClientInternalTpl()); + streamRDS = std::make_shared>(); } map[stream->uniqueId] = streamRDS; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp index caacf7f86af0e..e25e35dcaf8fa 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDFactoryInputStream.cpp @@ -33,6 +33,7 @@ #include "utils/FileUtils.h" #include "utils/URIUtils.h" +#include std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, bool scanforextaudio) { @@ -68,7 +69,7 @@ std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVide if (fileitem.GetProperty(STREAM_PROPERTY_INPUTSTREAM).asString() == STREAM_PROPERTY_VALUE_INPUTSTREAMFFMPEG) - return std::shared_ptr(new CDVDInputStreamFFmpeg(fileitem)); + return std::make_shared(fileitem); if (fileitem.IsDiscImage()) { @@ -77,14 +78,14 @@ std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVide url.SetHostName(file); url.SetFileName("BDMV/index.bdmv"); if (CFileUtils::Exists(url.Get())) - return std::shared_ptr(new CDVDInputStreamBluray(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); url.SetHostName(file); url.SetFileName("BDMV/INDEX.BDM"); if (CFileUtils::Exists(url.Get())) - return std::shared_ptr(new CDVDInputStreamBluray(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); #endif - return std::shared_ptr(new CDVDInputStreamNavigator(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); } #ifdef HAS_OPTICAL_DRIVE @@ -93,24 +94,24 @@ std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVide #ifdef HAVE_LIBBLURAY if (CFileUtils::Exists(URIUtils::AddFileToFolder(file, "BDMV", "index.bdmv")) || CFileUtils::Exists(URIUtils::AddFileToFolder(file, "BDMV", "INDEX.BDM"))) - return std::shared_ptr(new CDVDInputStreamBluray(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); #endif - return std::shared_ptr(new CDVDInputStreamNavigator(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); } #endif if (fileitem.IsDVDFile(false, true)) - return std::shared_ptr(new CDVDInputStreamNavigator(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); else if (URIUtils::IsPVRChannel(file)) - return std::shared_ptr(new CInputStreamPVRChannel(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); else if (URIUtils::IsPVRRecording(file)) - return std::shared_ptr(new CInputStreamPVRRecording(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); #ifdef HAVE_LIBBLURAY else if (fileitem.IsType(".bdmv") || fileitem.IsType(".mpls") || fileitem.IsType(".bdm") || fileitem.IsType(".mpl") || StringUtils::StartsWithNoCase(file, "bluray:")) - return std::shared_ptr(new CDVDInputStreamBluray(pPlayer, fileitem)); + return std::make_shared(pPlayer, fileitem); #endif else if (StringUtils::StartsWithNoCase(file, "rtp://") || StringUtils::StartsWithNoCase(file, "rtsp://") || @@ -128,10 +129,10 @@ std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVide StringUtils::StartsWithNoCase(file, "rtmpte://") || StringUtils::StartsWithNoCase(file, "rtmps://")) { - return std::shared_ptr(new CDVDInputStreamFFmpeg(fileitem)); + return std::make_shared(fileitem); } else if(StringUtils::StartsWithNoCase(file, "stack://")) - return std::shared_ptr(new CDVDInputStreamStack(fileitem)); + return std::make_shared(fileitem); CFileItem finalFileitem(fileitem); @@ -165,25 +166,23 @@ std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVide } if (finalFileitem.IsType(".m3u8")) - return std::shared_ptr(new CDVDInputStreamFFmpeg(finalFileitem)); + return std::make_shared(finalFileitem); // mime type for m3u8/hls streams if (finalFileitem.GetMimeType() == "application/vnd.apple.mpegurl" || finalFileitem.GetMimeType() == "application/x-mpegURL") - return std::shared_ptr(new CDVDInputStreamFFmpeg(finalFileitem)); + return std::make_shared(finalFileitem); if (URIUtils::IsProtocol(finalFileitem.GetPath(), "udp")) - return std::shared_ptr(new CDVDInputStreamFFmpeg(finalFileitem)); + return std::make_shared(finalFileitem); } // our file interface handles all these types of streams - return std::shared_ptr(new CDVDInputStreamFile(finalFileitem, - XFILE::READ_TRUNCATED | - XFILE::READ_BITRATE | - XFILE::READ_CHUNKED)); + return std::make_shared( + finalFileitem, XFILE::READ_TRUNCATED | XFILE::READ_BITRATE | XFILE::READ_CHUNKED); } std::shared_ptr CDVDFactoryInputStream::CreateInputStream(IVideoPlayer* pPlayer, const CFileItem &fileitem, const std::vector& filenames) { - return std::shared_ptr(new CInputStreamMultiSource(pPlayer, fileitem, filenames)); + return std::make_shared(pPlayer, fileitem, filenames); } diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp index be8f46d8ab81b..66173a9d366fd 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/InputStreamAddon.cpp @@ -24,6 +24,8 @@ #include "utils/log.h" #include "windowing/Resolution.h" +#include + CInputStreamProvider::CInputStreamProvider(const ADDON::AddonInfoPtr& addonInfo, KODI_HANDLE parentInstance) : m_addonInfo(addonInfo), m_parentInstance(parentInstance) @@ -191,8 +193,8 @@ bool CInputStreamAddon::Open() m_caps = {}; m_ifc.inputstream->toAddon->get_capabilities(m_ifc.inputstream, &m_caps); - m_subAddonProvider = std::shared_ptr( - new CInputStreamProvider(GetAddonInfo(), m_ifc.inputstream->toAddon->addonInstance)); + m_subAddonProvider = std::make_shared( + GetAddonInfo(), m_ifc.inputstream->toAddon->addonInstance); } return ret; } @@ -444,8 +446,7 @@ KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle, if (stream->m_masteringMetadata) { - videoStream->masteringMetaData = - std::shared_ptr(new AVMasteringDisplayMetadata); + videoStream->masteringMetaData = std::make_shared(); videoStream->masteringMetaData->display_primaries[0][0] = av_d2q(stream->m_masteringMetadata->primary_r_chromaticity_x, INT_MAX); videoStream->masteringMetaData->display_primaries[0][1] = @@ -472,8 +473,7 @@ KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle, if (stream->m_contentLightMetadata) { - videoStream->contentLightMetaData = - std::shared_ptr(new AVContentLightMetadata); + videoStream->contentLightMetaData = std::make_shared(); videoStream->contentLightMetaData->MaxCLL = static_cast(stream->m_contentLightMetadata->max_cll); videoStream->contentLightMetaData->MaxFALL = @@ -543,9 +543,9 @@ KODI_HANDLE CInputStreamAddon::cb_get_stream_transfer(KODI_HANDLE handle, CRYPTO_SESSION_SYSTEM_PLAYREADY, CRYPTO_SESSION_SYSTEM_WISEPLAY, CRYPTO_SESSION_SYSTEM_CLEARKEY, }; - demuxStream->cryptoSession = std::shared_ptr( - new DemuxCryptoSession(map[stream->m_cryptoSession.keySystem], - stream->m_cryptoSession.sessionId, stream->m_cryptoSession.flags)); + demuxStream->cryptoSession = std::make_shared( + map[stream->m_cryptoSession.keySystem], stream->m_cryptoSession.sessionId, + stream->m_cryptoSession.flags); if ((stream->m_features & INPUTSTREAM_FEATURE_DECODE) != 0) demuxStream->externalInterfaces = thisClass->m_subAddonProvider; diff --git a/xbmc/dialogs/GUIDialogColorPicker.cpp b/xbmc/dialogs/GUIDialogColorPicker.cpp index a73e47b1428b2..5b6842996bee8 100644 --- a/xbmc/dialogs/GUIDialogColorPicker.cpp +++ b/xbmc/dialogs/GUIDialogColorPicker.cpp @@ -19,6 +19,7 @@ #include "utils/log.h" #include +#include #include #include @@ -143,7 +144,7 @@ void CGUIDialogColorPicker::Reset() void CGUIDialogColorPicker::AddItem(const CFileItem& item) { - m_vecList->Add(CFileItemPtr(new CFileItem(item))); + m_vecList->Add(std::make_shared(item)); } void CGUIDialogColorPicker::SetItems(const CFileItemList& pList) diff --git a/xbmc/dialogs/GUIDialogSelect.cpp b/xbmc/dialogs/GUIDialogSelect.cpp index 0f125b288400b..daa11d551d31e 100644 --- a/xbmc/dialogs/GUIDialogSelect.cpp +++ b/xbmc/dialogs/GUIDialogSelect.cpp @@ -14,6 +14,8 @@ #include "input/Key.h" #include "utils/StringUtils.h" +#include + #define CONTROL_HEADING 1 #define CONTROL_NUMBER_OF_ITEMS 2 #define CONTROL_SIMPLE_LIST 3 @@ -195,7 +197,7 @@ int CGUIDialogSelect::Add(const std::string& strLabel) int CGUIDialogSelect::Add(const CFileItem& item) { - m_vecList->Add(CFileItemPtr(new CFileItem(item))); + m_vecList->Add(std::make_shared(item)); return m_vecList->Size() - 1; } @@ -217,7 +219,7 @@ const CFileItemPtr CGUIDialogSelect::GetSelectedFileItem() const { if (m_selectedItem) return m_selectedItem; - return CFileItemPtr(new CFileItem); + return std::make_shared(); } const std::vector& CGUIDialogSelect::GetSelectedItems() const diff --git a/xbmc/filesystem/BlurayDirectory.cpp b/xbmc/filesystem/BlurayDirectory.cpp index dbfb41bd74150..b6c339a1100f1 100644 --- a/xbmc/filesystem/BlurayDirectory.cpp +++ b/xbmc/filesystem/BlurayDirectory.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -187,7 +188,7 @@ void CBlurayDirectory::GetRoot(CFileItemList &items) CFileItemPtr item; path.SetFileName(URIUtils::AddFileToFolder(m_url.GetFileName(), "titles")); - item.reset(new CFileItem()); + item = std::make_shared(); item->SetPath(path.Get()); item->m_bIsFolder = true; item->SetLabel(g_localizeStrings.Get(25002) /* All titles */); @@ -202,7 +203,7 @@ void CBlurayDirectory::GetRoot(CFileItemList &items) } path.SetFileName("menu"); - item.reset(new CFileItem()); + item = std::make_shared(); item->SetPath(path.Get()); item->m_bIsFolder = false; item->SetLabel(g_localizeStrings.Get(25003) /* Menus */); diff --git a/xbmc/filesystem/ShoutcastFile.cpp b/xbmc/filesystem/ShoutcastFile.cpp index 7488ee7d3d024..e54f275b980f5 100644 --- a/xbmc/filesystem/ShoutcastFile.cpp +++ b/xbmc/filesystem/ShoutcastFile.cpp @@ -31,6 +31,7 @@ #include "utils/UrlOptions.h" #include +#include #include using namespace XFILE; @@ -120,7 +121,7 @@ bool CShoutcastFile::Open(const CURL& url) { std::unique_lock lock(m_tagSection); - m_masterTag.reset(new CMusicInfoTag()); + m_masterTag = std::make_shared(); m_masterTag->SetStationName(icyTitle); m_masterTag->SetGenre(icyGenre); m_masterTag->SetLoaded(true); diff --git a/xbmc/filesystem/SmartPlaylistDirectory.cpp b/xbmc/filesystem/SmartPlaylistDirectory.cpp index 9e05599a20d60..a45e796071a67 100644 --- a/xbmc/filesystem/SmartPlaylistDirectory.cpp +++ b/xbmc/filesystem/SmartPlaylistDirectory.cpp @@ -26,6 +26,7 @@ #include "video/VideoDbUrl.h" #include +#include #define PROPERTY_PATH_DB "path.db" #define PROPERTY_SORT_ORDER "sort.order" @@ -82,7 +83,7 @@ namespace XFILE playlist.GetVirtualFolders(virtualFolders); for (const std::string& virtualFolder : virtualFolders) { - CFileItemPtr pItem = CFileItemPtr(new CFileItem(virtualFolder, true)); + CFileItemPtr pItem = std::make_shared(virtualFolder, true); IFileDirectory *dir = CFileDirectoryFactory::Create(pItem->GetURL(), pItem.get()); if (dir != NULL) diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp index 273f376fe34e2..0ae30d26f8564 100644 --- a/xbmc/games/addons/input/GameClientInput.cpp +++ b/xbmc/games/addons/input/GameClientInput.cpp @@ -35,6 +35,7 @@ #include "utils/log.h" #include +#include #include using namespace KODI; @@ -626,7 +627,8 @@ bool CGameClientInput::OpenJoystick(const std::string& portAddress, const Contro return false; } - m_joysticks[portAddress].reset(new CGameClientJoystick(m_gameClient, portAddress, controller)); + m_joysticks[portAddress] = + std::make_shared(m_gameClient, portAddress, controller); return true; } diff --git a/xbmc/input/IRTranslator.cpp b/xbmc/input/IRTranslator.cpp index e115352b7e2d5..394eae62de37e 100644 --- a/xbmc/input/IRTranslator.cpp +++ b/xbmc/input/IRTranslator.cpp @@ -19,6 +19,7 @@ #include "utils/log.h" #include +#include #include #include @@ -106,7 +107,7 @@ void CIRTranslator::MapRemote(tinyxml2::XMLNode* pRemote, const std::string& szD auto it = m_irRemotesMap.find(szDevice); if (it == m_irRemotesMap.end()) - m_irRemotesMap[szDevice].reset(new IRButtonMap); + m_irRemotesMap[szDevice] = std::make_shared(); const std::shared_ptr& buttons = m_irRemotesMap[szDevice]; diff --git a/xbmc/input/JoystickMapper.cpp b/xbmc/input/JoystickMapper.cpp index 4e1337ab5f322..72145e2972ead 100644 --- a/xbmc/input/JoystickMapper.cpp +++ b/xbmc/input/JoystickMapper.cpp @@ -16,6 +16,7 @@ #include "utils/StringUtils.h" #include +#include #include #include @@ -43,7 +44,7 @@ void CJoystickMapper::MapActions(int windowID, const tinyxml2::XMLNode* pDevice) // Create/overwrite keymap auto& keymap = m_joystickKeymaps[controllerId]; if (!keymap) - keymap.reset(new CWindowKeymap(controllerId)); + keymap = std::make_shared(controllerId); const auto* pButton = pDevice->FirstChildElement(); while (pButton != nullptr) diff --git a/xbmc/interfaces/AnnouncementManager.cpp b/xbmc/interfaces/AnnouncementManager.cpp index 2f5101bf713b0..2718adf0d5a95 100644 --- a/xbmc/interfaces/AnnouncementManager.cpp +++ b/xbmc/interfaces/AnnouncementManager.cpp @@ -19,6 +19,7 @@ #include "utils/log.h" #include "video/VideoDatabase.h" +#include #include #include @@ -135,7 +136,7 @@ void CAnnouncementManager::Announce(AnnouncementFlag flag, announcement.data = data; if (item != nullptr) - announcement.item = CFileItemPtr(new CFileItem(*item)); + announcement.item = std::make_shared(*item); { std::unique_lock lock(m_queueCritSection); diff --git a/xbmc/interfaces/generic/ScriptInvocationManager.cpp b/xbmc/interfaces/generic/ScriptInvocationManager.cpp index 9daae0f69f8a0..a3ea29c3687f4 100644 --- a/xbmc/interfaces/generic/ScriptInvocationManager.cpp +++ b/xbmc/interfaces/generic/ScriptInvocationManager.cpp @@ -18,6 +18,7 @@ #include "utils/log.h" #include +#include #include #include #include @@ -268,8 +269,7 @@ int CScriptInvocationManager::ExecuteAsync( return invokerThread->GetId(); } - m_lastInvokerThread = - CLanguageInvokerThreadPtr(new CLanguageInvokerThread(languageInvoker, this, reuseable)); + m_lastInvokerThread = std::make_shared(languageInvoker, this, reuseable); if (m_lastInvokerThread == NULL) return -1; diff --git a/xbmc/interfaces/json-rpc/AudioLibrary.cpp b/xbmc/interfaces/json-rpc/AudioLibrary.cpp index 813c4c85c4659..3d6fa79d79f4a 100644 --- a/xbmc/interfaces/json-rpc/AudioLibrary.cpp +++ b/xbmc/interfaces/json-rpc/AudioLibrary.cpp @@ -29,6 +29,8 @@ #include "utils/URIUtils.h" #include "utils/Variant.h" +#include + using namespace MUSIC_INFO; using namespace JSONRPC; using namespace XFILE; @@ -495,7 +497,7 @@ JSONRPC_STATUS CAudioLibrary::GetSongDetails(const std::string &method, ITranspo return InvalidParams; CFileItemList items; - CFileItemPtr item = CFileItemPtr(new CFileItem(song)); + CFileItemPtr item = std::make_shared(song); FillItemArtistIDs(song.GetArtistIDArray(), item); items.Add(item); @@ -1151,7 +1153,7 @@ bool CAudioLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemL CSong song; if (musicdatabase.GetSong(songID, song)) { - list.Add(CFileItemPtr(new CFileItem(song))); + list.Add(std::make_shared(song)); success = true; } } @@ -1190,7 +1192,7 @@ void CAudioLibrary::FillAlbumItem(const CAlbum& album, const std::string& path, std::shared_ptr& item) { - item = CFileItemPtr(new CFileItem(path, album)); + item = std::make_shared(path, album); // Add album artistIds as separate property as not part of CMusicInfoTag std::vector artistids = album.GetArtistIDArray(); FillItemArtistIDs(artistids, item); diff --git a/xbmc/interfaces/json-rpc/FileItemHandler.cpp b/xbmc/interfaces/json-rpc/FileItemHandler.cpp index 9d649d346daff..07426447e169f 100644 --- a/xbmc/interfaces/json-rpc/FileItemHandler.cpp +++ b/xbmc/interfaces/json-rpc/FileItemHandler.cpp @@ -37,6 +37,7 @@ #include "video/VideoThumbLoader.h" #include +#include #include using namespace MUSIC_INFO; @@ -524,7 +525,7 @@ bool CFileItemHandler::FillFileItemList(const CVariant ¶meterObject, CFileIt if (!added) { - CFileItemPtr item = CFileItemPtr(new CFileItem(file, false)); + CFileItemPtr item = std::make_shared(file, false); if (item->IsPicture()) { CPictureInfoTag picture; diff --git a/xbmc/interfaces/json-rpc/FileOperations.cpp b/xbmc/interfaces/json-rpc/FileOperations.cpp index 6329a7867c8e8..4658e993537b1 100644 --- a/xbmc/interfaces/json-rpc/FileOperations.cpp +++ b/xbmc/interfaces/json-rpc/FileOperations.cpp @@ -26,6 +26,8 @@ #include "utils/Variant.h" #include "video/VideoDatabase.h" +#include + using namespace XFILE; using namespace JSONRPC; @@ -44,7 +46,7 @@ JSONRPC_STATUS CFileOperations::GetRootDirectory(const std::string &method, ITra if (sources->at(i).m_iHasLock == LOCK_STATE_LOCKED) continue; - items.Add(CFileItemPtr(new CFileItem(sources->at(i)))); + items.Add(std::make_shared(sources->at(i))); } for (unsigned int i = 0; i < (unsigned int)items.Size(); i++) @@ -184,7 +186,7 @@ JSONRPC_STATUS CFileOperations::GetFileDetails(const std::string &method, ITrans if (CDirectory::GetDirectory(path, items, "", DIR_FLAG_DEFAULTS) && items.Contains(file)) item = items.Get(file); else - item = CFileItemPtr(new CFileItem(file, false)); + item = std::make_shared(file, false); if (!URIUtils::IsUPnP(file)) FillFileItem(item, item, parameterObject["media"].asString(), parameterObject); diff --git a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp index da100b24cbc84..3a9cd037ad3fa 100644 --- a/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp +++ b/xbmc/interfaces/json-rpc/JSONServiceDescription.cpp @@ -30,6 +30,8 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include + using namespace JSONRPC; std::map CJSONServiceDescription::m_notifications = std::map(); @@ -422,7 +424,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* // 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()); + JSONSchemaTypeDefinitionPtr propertyType = std::make_shared(); propertyType->name = itr->first; if (!propertyType->Parse(itr->second)) { @@ -435,7 +437,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* } hasAdditionalProperties = true; - additionalProperties = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + additionalProperties = std::make_shared(); if (value.isMember("additionalProperties")) { if (value["additionalProperties"].isBoolean()) @@ -484,7 +486,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* // If it is an object, there is only one schema for it if (value["additionalItems"].isObject()) { - JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr additionalItem = std::make_shared(); if (additionalItem->Parse(value["additionalItems"])) additionalItems.push_back(additionalItem); else @@ -499,7 +501,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* { for (unsigned int itemIndex = 0; itemIndex < value["additionalItems"].size(); itemIndex++) { - JSONSchemaTypeDefinitionPtr additionalItem = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr additionalItem = std::make_shared(); if (additionalItem->Parse(value["additionalItems"][itemIndex])) additionalItems.push_back(additionalItem); @@ -527,7 +529,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* { if (value["items"].isObject()) { - JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr item = std::make_shared(); if (!item->Parse(value["items"])) { CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" for type {}", name); @@ -542,7 +544,7 @@ bool JSONSchemaTypeDefinition::Parse(const CVariant &value, bool isParameter /* { for (CVariant::const_iterator_array itemItr = value["items"].begin_array(); itemItr != value["items"].end_array(); ++itemItr) { - JSONSchemaTypeDefinitionPtr item = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr item = std::make_shared(); if (!item->Parse(*itemItr)) { CLog::Log(LOGDEBUG, "Invalid item definition in \"items\" array for type {}", name); @@ -1335,7 +1337,7 @@ bool JsonRpcMethod::Parse(const CVariant &value) // Parse the parameter and add it to the list // of defined parameters - JSONSchemaTypeDefinitionPtr param = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr param = std::make_shared(); if (!parseParameter(parameter, param)) { missingReference = param->missingReference; @@ -1615,7 +1617,7 @@ bool CJSONServiceDescription::AddType(const std::string &jsonType) // Make sure the "id" attribute is correctly populated descriptionObject[typeName]["id"] = typeName; - JSONSchemaTypeDefinitionPtr globalType = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr globalType = std::make_shared(); globalType->name = typeName; globalType->ID = typeName; CJSONServiceDescription::addReferenceTypeDefinition(globalType); @@ -1704,7 +1706,7 @@ bool CJSONServiceDescription::AddEnum(const std::string &name, const std::vector values.size() == 0) return false; - JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr definition = std::make_shared(); definition->ID = name; std::vector types; @@ -1994,7 +1996,7 @@ bool CJSONServiceDescription::parseJSONSchemaType(const CVariant &value, std::ve // to handle a union type for (unsigned int typeIndex = 0; typeIndex < value.size(); typeIndex++) { - JSONSchemaTypeDefinitionPtr definition = JSONSchemaTypeDefinitionPtr(new JSONSchemaTypeDefinition()); + JSONSchemaTypeDefinitionPtr definition = std::make_shared(); // If the type is a string try to parse it if (value[typeIndex].isString()) definition->type = StringToSchemaValueType(value[typeIndex].asString()); diff --git a/xbmc/interfaces/json-rpc/PVROperations.cpp b/xbmc/interfaces/json-rpc/PVROperations.cpp index 3611ad6d66619..50922320d01f6 100644 --- a/xbmc/interfaces/json-rpc/PVROperations.cpp +++ b/xbmc/interfaces/json-rpc/PVROperations.cpp @@ -27,6 +27,8 @@ #include "pvr/timers/PVRTimers.h" #include "utils/Variant.h" +#include + using namespace JSONRPC; using namespace PVR; using namespace KODI::MESSAGING; @@ -217,7 +219,8 @@ JSONRPC_STATUS CPVROperations::GetBroadcastDetails(const std::string &method, IT if (!epgTag) return InvalidParams; - HandleFileItem("broadcastid", false, "broadcastdetails", CFileItemPtr(new CFileItem(epgTag)), parameterObject, parameterObject["properties"], result, false); + HandleFileItem("broadcastid", false, "broadcastdetails", std::make_shared(epgTag), + parameterObject, parameterObject["properties"], result, false); return OK; } @@ -396,7 +399,8 @@ JSONRPC_STATUS CPVROperations::GetTimerDetails(const std::string &method, ITrans if (!timer) return InvalidParams; - HandleFileItem("timerid", false, "timerdetails", CFileItemPtr(new CFileItem(timer)), parameterObject, parameterObject["properties"], result, false); + HandleFileItem("timerid", false, "timerdetails", std::make_shared(timer), + parameterObject, parameterObject["properties"], result, false); return OK; } diff --git a/xbmc/interfaces/json-rpc/VideoLibrary.cpp b/xbmc/interfaces/json-rpc/VideoLibrary.cpp index db152cd634a6e..f98c84552e67c 100644 --- a/xbmc/interfaces/json-rpc/VideoLibrary.cpp +++ b/xbmc/interfaces/json-rpc/VideoLibrary.cpp @@ -22,6 +22,8 @@ #include "video/VideoDbUrl.h" #include "video/VideoLibraryQueue.h" +#include + using namespace JSONRPC; JSONRPC_STATUS CVideoLibrary::GetMovies(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) @@ -93,7 +95,8 @@ JSONRPC_STATUS CVideoLibrary::GetMovieDetails(const std::string &method, ITransp 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); + HandleFileItem("movieid", true, "moviedetails", std::make_shared(infos), + parameterObject, parameterObject["properties"], result, false); return OK; } @@ -124,7 +127,8 @@ JSONRPC_STATUS CVideoLibrary::GetMovieSetDetails(const std::string &method, ITra if (!videodatabase.GetSetInfo(id, infos) || infos.m_iDbId <= 0) return InvalidParams; - HandleFileItem("setid", false, "setdetails", CFileItemPtr(new CFileItem(infos)), parameterObject, parameterObject["properties"], result, false); + HandleFileItem("setid", false, "setdetails", std::make_shared(infos), parameterObject, + parameterObject["properties"], result, false); // Get movies from the set CFileItemList items; @@ -227,7 +231,7 @@ JSONRPC_STATUS CVideoLibrary::GetSeasonDetails(const std::string &method, ITrans infos.m_iDbId <= 0 || infos.m_iIdShow <= 0) return InvalidParams; - CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + CFileItemPtr pItem = std::make_shared(infos); HandleFileItem("seasonid", false, "seasondetails", pItem, parameterObject, parameterObject["properties"], result, false); return OK; } @@ -301,7 +305,7 @@ JSONRPC_STATUS CVideoLibrary::GetEpisodeDetails(const std::string &method, ITran if (!videodatabase.GetEpisodeInfo("", infos, id, RequiresAdditionalDetails(MediaTypeEpisode, parameterObject)) || infos.m_iDbId <= 0) return InvalidParams; - CFileItemPtr pItem = CFileItemPtr(new CFileItem(infos)); + CFileItemPtr pItem = std::make_shared(infos); // We need to set the correct base path to get the valid fanart int tvshowid = infos.m_iIdShow; if (tvshowid <= 0) @@ -374,7 +378,8 @@ JSONRPC_STATUS CVideoLibrary::GetMusicVideoDetails(const std::string &method, IT 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); + HandleFileItem("musicvideoid", true, "musicvideodetails", std::make_shared(infos), + parameterObject, parameterObject["properties"], result, false); return OK; } @@ -861,7 +866,8 @@ JSONRPC_STATUS CVideoLibrary::RefreshMovie(const std::string &method, ITransport bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); std::string searchTitle = parameterObject["title"].asString(); - CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + CVideoLibraryQueue::GetInstance().RefreshItem(std::make_shared(infos), ignoreNfo, true, + false, searchTitle); return ACK; } @@ -901,7 +907,7 @@ JSONRPC_STATUS CVideoLibrary::RefreshEpisode(const std::string &method, ITranspo if (!videodatabase.GetEpisodeInfo("", infos, id) || infos.m_iDbId <= 0) return InvalidParams; - CFileItemPtr item = CFileItemPtr(new CFileItem(infos)); + CFileItemPtr item = std::make_shared(infos); // We need to set the correct base path to get the valid fanart int tvshowid = infos.m_iIdShow; if (tvshowid <= 0) @@ -928,7 +934,8 @@ JSONRPC_STATUS CVideoLibrary::RefreshMusicVideo(const std::string &method, ITran bool ignoreNfo = parameterObject["ignorenfo"].asBoolean(); std::string searchTitle = parameterObject["title"].asString(); - CVideoLibraryQueue::GetInstance().RefreshItem(CFileItemPtr(new CFileItem(infos)), ignoreNfo, true, false, searchTitle); + CVideoLibraryQueue::GetInstance().RefreshItem(std::make_shared(infos), ignoreNfo, true, + false, searchTitle); return ACK; } @@ -1063,7 +1070,7 @@ bool CVideoLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemL videodatabase.GetMovieInfo("", details, movieID); if (!details.IsEmpty()) { - list.Add(CFileItemPtr(new CFileItem(details))); + list.Add(std::make_shared(details)); success = true; } } @@ -1072,7 +1079,7 @@ bool CVideoLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemL CVideoInfoTag details; if (videodatabase.GetEpisodeInfo("", details, episodeID) && !details.IsEmpty()) { - list.Add(CFileItemPtr(new CFileItem(details))); + list.Add(std::make_shared(details)); success = true; } } @@ -1082,7 +1089,7 @@ bool CVideoLibrary::FillFileItemList(const CVariant ¶meterObject, CFileItemL videodatabase.GetMusicVideoInfo("", details, musicVideoID); if (!details.IsEmpty()) { - list.Add(CFileItemPtr(new CFileItem(details))); + list.Add(std::make_shared(details)); success = true; } } diff --git a/xbmc/interfaces/legacy/ListItem.cpp b/xbmc/interfaces/legacy/ListItem.cpp index 64410ad22a67a..397a38e1360ee 100644 --- a/xbmc/interfaces/legacy/ListItem.cpp +++ b/xbmc/interfaces/legacy/ListItem.cpp @@ -23,6 +23,7 @@ #include "video/VideoInfoTag.h" #include +#include #include #include @@ -39,7 +40,7 @@ namespace XBMCAddon item.reset(); // create CFileItem - item.reset(new CFileItem()); + item = std::make_shared(); if (!item) // not sure if this is really possible return; diff --git a/xbmc/interfaces/legacy/ListItem.h b/xbmc/interfaces/legacy/ListItem.h index 82513e0f4538f..85662588e96e8 100644 --- a/xbmc/interfaces/legacy/ListItem.h +++ b/xbmc/interfaces/legacy/ListItem.h @@ -22,6 +22,7 @@ #include "commons/Exception.h" #include +#include #include #include @@ -110,7 +111,7 @@ namespace XBMCAddon static inline AddonClass::Ref fromString(const String& str) { AddonClass::Ref ret = AddonClass::Ref(new ListItem()); - ret->item.reset(new CFileItem(str)); + ret->item = std::make_shared(str); return ret; } #endif diff --git a/xbmc/interfaces/python/ContextItemAddonInvoker.cpp b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp index 734193b15cefa..be529ffbeae8e 100644 --- a/xbmc/interfaces/python/ContextItemAddonInvoker.cpp +++ b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp @@ -12,14 +12,14 @@ #include "interfaces/python/swig.h" #include "utils/log.h" +#include + #include #include - -CContextItemAddonInvoker::CContextItemAddonInvoker( - ILanguageInvocationHandler *invocationHandler, - const CFileItemPtr& item) - : CAddonPythonInvoker(invocationHandler), m_item(CFileItemPtr(new CFileItem(*item.get()))) +CContextItemAddonInvoker::CContextItemAddonInvoker(ILanguageInvocationHandler* invocationHandler, + const CFileItemPtr& item) + : CAddonPythonInvoker(invocationHandler), m_item(std::make_shared(*item.get())) { } diff --git a/xbmc/messaging/ApplicationMessenger.cpp b/xbmc/messaging/ApplicationMessenger.cpp index dfacac9e1eeaa..d9eab6147a14c 100644 --- a/xbmc/messaging/ApplicationMessenger.cpp +++ b/xbmc/messaging/ApplicationMessenger.cpp @@ -96,7 +96,7 @@ int CApplicationMessenger::SendMsg(ThreadMessage&& message, bool wait) // forever! if (m_guiThreadId != CThread::GetCurrentThreadId()) { - message.waitEvent.reset(new CEvent(true)); + message.waitEvent = std::make_shared(true); waitEvent = message.waitEvent; result = message.result; } diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index 0ebee30c01c65..15697919c121d 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -42,6 +42,8 @@ #include "utils/log.h" #include "view/GUIViewState.h" +#include + using namespace MUSIC_INFO; using namespace XFILE; using namespace std::chrono_literals; @@ -155,7 +157,7 @@ class CSetArtJob : public CJob const auto appPlayer = components.GetComponent(); if (appPlayer->IsPlayingAudio() && g_application.CurrentFileItem().HasMusicInfoTag()) { - CFileItemPtr songitem = CFileItemPtr(new CFileItem(g_application.CurrentFileItem())); + CFileItemPtr songitem = std::make_shared(g_application.CurrentFileItem()); if (HasSongExtraArtChanged(songitem, type, itemID, db)) g_application.UpdateCurrentPlayArt(); } diff --git a/xbmc/music/windows/GUIWindowMusicBase.cpp b/xbmc/music/windows/GUIWindowMusicBase.cpp index b3cff68c93002..9dc4e2dddd850 100644 --- a/xbmc/music/windows/GUIWindowMusicBase.cpp +++ b/xbmc/music/windows/GUIWindowMusicBase.cpp @@ -8,33 +8,24 @@ #include "GUIWindowMusicBase.h" +#include "Autorun.h" +#include "FileItem.h" +#include "GUIInfoManager.h" +#include "GUIPassword.h" #include "GUIUserMessages.h" +#include "PartyModeManager.h" #include "PlayListPlayer.h" #include "ServiceBroker.h" +#include "URL.h" #include "Util.h" +#include "addons/gui/GUIDialogAddonInfo.h" #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" -#include "dialogs/GUIDialogMediaSource.h" -#include "input/actions/Action.h" -#include "input/actions/ActionIDs.h" -#include "music/MusicDbUrl.h" -#include "music/MusicLibraryQueue.h" -#include "music/MusicUtils.h" -#include "music/dialogs/GUIDialogInfoProviderSettings.h" -#include "music/dialogs/GUIDialogMusicInfo.h" -#include "playlists/PlayList.h" -#include "playlists/PlayListFactory.h" #ifdef HAS_CDDA_RIPPER #include "cdrip/CDDARipper.h" #endif -#include "Autorun.h" -#include "FileItem.h" -#include "GUIInfoManager.h" -#include "GUIPassword.h" -#include "PartyModeManager.h" -#include "URL.h" -#include "addons/gui/GUIDialogAddonInfo.h" +#include "dialogs/GUIDialogMediaSource.h" #include "dialogs/GUIDialogProgress.h" #include "dialogs/GUIDialogSmartPlaylistEditor.h" #include "dialogs/GUIDialogYesNo.h" @@ -44,10 +35,19 @@ #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" #include "guilib/guiinfo/GUIInfoLabels.h" +#include "input/actions/Action.h" +#include "input/actions/ActionIDs.h" #include "messaging/helpers/DialogHelper.h" #include "messaging/helpers/DialogOKHelper.h" +#include "music/MusicDbUrl.h" +#include "music/MusicLibraryQueue.h" +#include "music/MusicUtils.h" +#include "music/dialogs/GUIDialogInfoProviderSettings.h" +#include "music/dialogs/GUIDialogMusicInfo.h" #include "music/infoscanner/MusicInfoScanner.h" #include "music/tags/MusicInfoTag.h" +#include "playlists/PlayList.h" +#include "playlists/PlayListFactory.h" #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" #include "settings/MediaSourceSettings.h" @@ -65,6 +65,7 @@ #include "view/GUIViewState.h" #include +#include using namespace XFILE; using namespace MUSICDATABASEDIRECTORY; @@ -842,7 +843,7 @@ bool CGUIWindowMusicBase::GetDirectory(const std::string &strDirectory, CFileIte newPlaylist->m_bIsFolder = true; items.Add(newPlaylist); - newPlaylist.reset(new CFileItem("newplaylist://", false)); + newPlaylist = std::make_shared("newplaylist://", false); newPlaylist->SetLabel(g_localizeStrings.Get(525)); newPlaylist->SetArt("icon", "DefaultAddSource.png"); newPlaylist->SetLabelPreformatted(true); @@ -850,7 +851,7 @@ bool CGUIWindowMusicBase::GetDirectory(const std::string &strDirectory, CFileIte newPlaylist->SetCanQueue(false); items.Add(newPlaylist); - newPlaylist.reset(new CFileItem("newsmartplaylist://music", false)); + newPlaylist = std::make_shared("newsmartplaylist://music", false); newPlaylist->SetLabel(g_localizeStrings.Get(21437)); newPlaylist->SetArt("icon", "DefaultAddSource.png"); newPlaylist->SetLabelPreformatted(true); diff --git a/xbmc/music/windows/MusicFileItemListModifier.cpp b/xbmc/music/windows/MusicFileItemListModifier.cpp index c78de793fd87a..fe04aed423f7b 100644 --- a/xbmc/music/windows/MusicFileItemListModifier.cpp +++ b/xbmc/music/windows/MusicFileItemListModifier.cpp @@ -17,6 +17,8 @@ #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include + using namespace XFILE::MUSICDATABASEDIRECTORY; bool CMusicFileItemListModifier::CanModify(const CFileItemList &items) const @@ -74,7 +76,7 @@ void CMusicFileItemListModifier::AddQueuingFolder(CFileItemList& items) switch (nodeChildType) { case NODE_TYPE_ARTIST: - pItem.reset(new CFileItem(g_localizeStrings.Get(15103))); // "All Artists" + pItem = std::make_shared(g_localizeStrings.Get(15103)); // "All Artists" musicUrl.AppendPath("-1/"); pItem->SetPath(musicUrl.ToString()); break; @@ -84,14 +86,14 @@ void CMusicFileItemListModifier::AddQueuingFolder(CFileItemList& items) case NODE_TYPE_ALBUM_RECENTLY_PLAYED: case NODE_TYPE_ALBUM_RECENTLY_ADDED: case NODE_TYPE_ALBUM_TOP100: - pItem.reset(new CFileItem(g_localizeStrings.Get(15102))); // "All Albums" + pItem = std::make_shared(g_localizeStrings.Get(15102)); // "All Albums" musicUrl.AppendPath("-1/"); pItem->SetPath(musicUrl.ToString()); break; // Disc node case NODE_TYPE_DISC: - pItem.reset(new CFileItem(g_localizeStrings.Get(38075))); // "All Discs" + pItem = std::make_shared(g_localizeStrings.Get(38075)); // "All Discs" musicUrl.AppendPath("-1/"); pItem->SetPath(musicUrl.ToString()); break; diff --git a/xbmc/network/upnp/UPnPInternal.cpp b/xbmc/network/upnp/UPnPInternal.cpp index 19f1cfbe03ad1..c4ec1d946c7f1 100644 --- a/xbmc/network/upnp/UPnPInternal.cpp +++ b/xbmc/network/upnp/UPnPInternal.cpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -1411,7 +1412,7 @@ std::shared_ptr GetFileItem(const NPT_String& uri, const NPT_String& } else { - item.reset(new CFileItem((const char*)uri, false)); + item = std::make_shared((const char*)uri, false); } return item; } diff --git a/xbmc/network/upnp/UPnPServer.cpp b/xbmc/network/upnp/UPnPServer.cpp index 7085c065992e8..ebd11ddb99a90 100644 --- a/xbmc/network/upnp/UPnPServer.cpp +++ b/xbmc/network/upnp/UPnPServer.cpp @@ -43,6 +43,8 @@ #include "view/GUIViewState.h" #include "xbmc/interfaces/AnnouncementManager.h" +#include + #include NPT_SET_LOCAL_LOGGER("xbmc.upnp.server") @@ -601,7 +603,7 @@ CUPnPServer::OnBrowseMetadata(PLT_ActionReference& action, id.TrimRight("/"); if (id == "virtualpath://upnproot") { id += "/"; - item.reset(new CFileItem((const char*)id, true)); + item = std::make_shared((const char*)id, true); item->SetLabel("Root"); item->SetLabelPreformatted(true); object = Build(item, true, context, thumb_loader); @@ -610,7 +612,7 @@ CUPnPServer::OnBrowseMetadata(PLT_ActionReference& action, return NPT_FAILURE; } } else { - item.reset(new CFileItem((const char*)id, false)); + item = std::make_shared((const char*)id, false); // attempt to determine the parent of this item std::string parent; @@ -713,13 +715,13 @@ CUPnPServer::OnBrowseDirectChildren(PLT_ActionReference& action, CFileItemPtr item; // music library - item.reset(new CFileItem("musicdb://", true)); + item = std::make_shared("musicdb://", true); item->SetLabel("Music Library"); item->SetLabelPreformatted(true); items.Add(item); // video library - item.reset(new CFileItem("library://video/", true)); + item = std::make_shared("library://video/", true); item->SetLabel("Video Library"); item->SetLabelPreformatted(true); items.Add(item); diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp index e2f9db7bba584..ba56dad85afb8 100644 --- a/xbmc/pictures/GUIWindowSlideShow.cpp +++ b/xbmc/pictures/GUIWindowSlideShow.cpp @@ -40,6 +40,7 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#include #include using namespace XFILE; @@ -345,7 +346,7 @@ void CGUIWindowSlideShow::Select(const std::string& strPicture) void CGUIWindowSlideShow::GetSlideShowContents(CFileItemList &list) { for (size_t index = 0; index < m_slides.size(); index++) - list.Add(CFileItemPtr(new CFileItem(*m_slides.at(index)))); + list.Add(std::make_shared(*m_slides.at(index))); } std::shared_ptr CGUIWindowSlideShow::GetCurrentSlide() diff --git a/xbmc/profiles/ProfileManager.cpp b/xbmc/profiles/ProfileManager.cpp index 600c812cf1090..827bfb112f8b9 100644 --- a/xbmc/profiles/ProfileManager.cpp +++ b/xbmc/profiles/ProfileManager.cpp @@ -8,13 +8,17 @@ #include "ProfileManager.h" +#include "ContextMenuManager.h" //! @todo Remove me #include "DatabaseManager.h" #include "FileItem.h" #include "GUIInfoManager.h" #include "GUIPassword.h" #include "PasswordManager.h" +#include "PlayListPlayer.h" //! @todo Remove me #include "ServiceBroker.h" #include "Util.h" +#include "addons/AddonManager.h" //! @todo Remove me +#include "addons/Service.h" //! @todo Remove me #include "addons/Skin.h" #include "application/Application.h" //! @todo Remove me #include "application/ApplicationComponents.h" @@ -23,6 +27,7 @@ #include "dialogs/GUIDialogYesNo.h" #include "events/EventLog.h" #include "events/EventLogManager.h" +#include "favourites/FavouritesService.h" //! @todo Remove me #include "filesystem/Directory.h" #include "filesystem/DirectoryCache.h" #include "filesystem/File.h" @@ -30,31 +35,20 @@ #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" #include "guilib/LocalizeStrings.h" +#include "guilib/StereoscopicsManager.h" //! @todo Remove me #include "input/InputManager.h" +#include "interfaces/json-rpc/JSONRPC.h" //! @todo Remove me #include "music/MusicLibraryQueue.h" +#include "network/Network.h" //! @todo Remove me +#include "network/NetworkServices.h" //! @todo Remove me +#include "pvr/PVRManager.h" //! @todo Remove me #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "settings/lib/SettingsManager.h" -#include "threads/SingleLock.h" - -#include -#include -#include -#include #if !defined(TARGET_WINDOWS) && defined(HAS_OPTICAL_DRIVE) #include "storage/DetectDVDType.h" #endif -#include "ContextMenuManager.h" //! @todo Remove me -#include "PlayListPlayer.h" //! @todo Remove me -#include "addons/AddonManager.h" //! @todo Remove me -#include "addons/Service.h" //! @todo Remove me -#include "application/Application.h" //! @todo Remove me -#include "favourites/FavouritesService.h" //! @todo Remove me -#include "guilib/StereoscopicsManager.h" //! @todo Remove me -#include "interfaces/json-rpc/JSONRPC.h" //! @todo Remove me -#include "network/Network.h" //! @todo Remove me -#include "network/NetworkServices.h" //! @todo Remove me -#include "pvr/PVRManager.h" //! @todo Remove me +#include "threads/SingleLock.h" #include "utils/FileUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" @@ -64,6 +58,12 @@ #include "video/VideoLibraryQueue.h" //! @todo Remove me #include "weather/WeatherManager.h" //! @todo Remove me +#include +#include +#include +#include +#include + //! @todo //! eventually the profile should dictate where special://masterprofile/ is //! but for now it makes sense to leave all the profile settings in a user @@ -506,7 +506,8 @@ bool CProfileManager::DeleteProfile(unsigned int index) m_settings->Save(); } - CFileItemPtr item = CFileItemPtr(new CFileItem(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory))); + CFileItemPtr item = + std::make_shared(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory)); item->SetPath(URIUtils::AddFileToFolder(GetUserDataFolder(), strDirectory + "/")); item->m_bIsFolder = true; item->Select(true); diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index dc950a71484d3..9dc0d7c120f4f 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -349,11 +349,11 @@ void CPVRManager::ResetProperties() std::unique_lock lock(m_critSection); Clear(); - m_database.reset(new CPVRDatabase); - m_providers.reset(new CPVRProviders); - m_channelGroups.reset(new CPVRChannelGroupsContainer); - m_recordings.reset(new CPVRRecordings); - m_timers.reset(new CPVRTimers); + m_database = std::make_shared(); + m_providers = std::make_shared(); + m_channelGroups = std::make_shared(); + m_recordings = std::make_shared(); + m_timers = std::make_shared(); m_guiInfo.reset(new CPVRGUIInfo); m_parentalTimer.reset(new CStopWatch); m_knownClients.clear(); diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp index f149301cce68b..7e19c5650e313 100644 --- a/xbmc/pvr/addons/PVRClient.cpp +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -1532,7 +1532,7 @@ PVR_ERROR CPVRClient::OnPowerSavingDeactivated() std::shared_ptr CPVRClient::GetMenuHooks() { if (!m_menuhooks) - m_menuhooks.reset(new CPVRClientMenuHooks(ID())); + m_menuhooks = std::make_shared(ID()); return m_menuhooks; } diff --git a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp index f7866278e7048..a1098ea377b4b 100644 --- a/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp +++ b/xbmc/pvr/dialogs/GUIDialogPVRTimerSettings.cpp @@ -778,8 +778,8 @@ void CGUIDialogPVRTimerSettings::AddCondition(const std::shared_ptr& s { GetSettingsManager()->AddDynamicCondition(identifier, condition, this); CSettingDependency dep(depType, GetSettingsManager()); - dep.And()->Add(CSettingDependencyConditionPtr( - new CSettingDependencyCondition(identifier, "true", settingId, false, GetSettingsManager()))); + dep.And()->Add(std::make_shared(identifier, "true", settingId, false, + GetSettingsManager())); SettingDependencies deps(setting->GetDependencies()); deps.push_back(dep); setting->SetDependencies(deps); diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp index 027cea91edf0e..fa8865078d1bd 100644 --- a/xbmc/pvr/epg/EpgContainer.cpp +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -572,8 +572,8 @@ std::shared_ptr CPVREpgContainer::CreateChannelEpg(int iEpgId, const st if (iEpgId <= 0) iEpgId = NextEpgId(); - epg.reset(new CPVREpg(iEpgId, channelData->ChannelName(), strScraperName, channelData, - GetEpgDatabase())); + epg = std::make_shared(iEpgId, channelData->ChannelName(), strScraperName, channelData, + GetEpgDatabase()); std::unique_lock lock(m_critSection); m_epgIdToEpgMap.insert({iEpgId, epg}); diff --git a/xbmc/pvr/epg/EpgInfoTag.cpp b/xbmc/pvr/epg/EpgInfoTag.cpp index 8b0930e5d2e0e..f7843dfce060f 100644 --- a/xbmc/pvr/epg/EpgInfoTag.cpp +++ b/xbmc/pvr/epg/EpgInfoTag.cpp @@ -138,7 +138,7 @@ void CPVREpgInfoTag::SetChannelData(const std::shared_ptr& d if (data) m_channelData = data; else - m_channelData.reset(new CPVREpgChannelData); + m_channelData = std::make_shared(); } bool CPVREpgInfoTag::operator==(const CPVREpgInfoTag& right) const diff --git a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp index cf7baf0576634..8928cc053943b 100644 --- a/xbmc/pvr/filesystem/PVRGUIDirectory.cpp +++ b/xbmc/pvr/filesystem/PVRGUIDirectory.cpp @@ -72,8 +72,8 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) const bool bAnyClientSupportingEPG = clients->AnyClientSupportingEPG(); if (bAnyClientSupportingEPG) { - item.reset( - new CFileItem(StringUtils::Format("pvr://guide/{}/", bRadio ? "radio" : "tv"), true)); + item = std::make_shared( + StringUtils::Format("pvr://guide/{}/", bRadio ? "radio" : "tv"), true); item->SetLabel(g_localizeStrings.Get(19069)); // Guide item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_GUIDE : WINDOW_TV_GUIDE)); @@ -82,8 +82,8 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) } // Channels - item.reset(new CFileItem( - bRadio ? CPVRChannelsPath::PATH_RADIO_CHANNELS : CPVRChannelsPath::PATH_TV_CHANNELS, true)); + item = std::make_shared( + bRadio ? CPVRChannelsPath::PATH_RADIO_CHANNELS : CPVRChannelsPath::PATH_TV_CHANNELS, true); item->SetLabel(g_localizeStrings.Get(19019)); // Channels item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_CHANNELS : WINDOW_TV_CHANNELS)); @@ -93,9 +93,9 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) // Recordings if (clients->AnyClientSupportingRecordings()) { - item.reset(new CFileItem(bRadio ? CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS - : CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS, - true)); + item = std::make_shared(bRadio ? CPVRRecordingsPath::PATH_ACTIVE_RADIO_RECORDINGS + : CPVRRecordingsPath::PATH_ACTIVE_TV_RECORDINGS, + true); item->SetLabel(g_localizeStrings.Get(19017)); // Recordings item->SetProperty("node.target", CWindowTranslator::TranslateWindow( bRadio ? WINDOW_RADIO_RECORDINGS : WINDOW_TV_RECORDINGS)); @@ -105,16 +105,16 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) // Timers/Timer rules // - always present, because Reminders are always available, no client support needed for this - item.reset(new CFileItem( - bRadio ? CPVRTimersPath::PATH_RADIO_TIMERS : CPVRTimersPath::PATH_TV_TIMERS, true)); + item = std::make_shared( + bRadio ? CPVRTimersPath::PATH_RADIO_TIMERS : CPVRTimersPath::PATH_TV_TIMERS, true); item->SetLabel(g_localizeStrings.Get(19040)); // Timers item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_TIMERS : WINDOW_TV_TIMERS)); item->SetArt("icon", "DefaultPVRTimers.png"); results.Add(item); - item.reset(new CFileItem( - bRadio ? CPVRTimersPath::PATH_RADIO_TIMER_RULES : CPVRTimersPath::PATH_TV_TIMER_RULES, true)); + item = std::make_shared( + bRadio ? CPVRTimersPath::PATH_RADIO_TIMER_RULES : CPVRTimersPath::PATH_TV_TIMER_RULES, true); item->SetLabel(g_localizeStrings.Get(19138)); // Timer rules item->SetProperty("node.target", CWindowTranslator::TranslateWindow( bRadio ? WINDOW_RADIO_TIMER_RULES : WINDOW_TV_TIMER_RULES)); @@ -124,8 +124,8 @@ bool GetRootDirectory(bool bRadio, CFileItemList& results) // Search if (bAnyClientSupportingEPG) { - item.reset(new CFileItem( - bRadio ? CPVREpgSearchPath::PATH_RADIO_SEARCH : CPVREpgSearchPath::PATH_TV_SEARCH, true)); + item = std::make_shared( + bRadio ? CPVREpgSearchPath::PATH_RADIO_SEARCH : CPVREpgSearchPath::PATH_TV_SEARCH, true); item->SetLabel(g_localizeStrings.Get(137)); // Search item->SetProperty("node.target", CWindowTranslator::TranslateWindow(bRadio ? WINDOW_RADIO_SEARCH : WINDOW_TV_SEARCH)); @@ -154,17 +154,17 @@ bool CPVRGUIDirectory::GetDirectory(CFileItemList& results) const { std::shared_ptr item; - item.reset(new CFileItem(base + "channels/", true)); + item = std::make_shared(base + "channels/", true); item->SetLabel(g_localizeStrings.Get(19019)); // Channels item->SetLabelPreformatted(true); results.Add(item); - item.reset(new CFileItem(base + "recordings/active/", true)); + item = std::make_shared(base + "recordings/active/", true); item->SetLabel(g_localizeStrings.Get(19017)); // Recordings item->SetLabelPreformatted(true); results.Add(item); - item.reset(new CFileItem(base + "recordings/deleted/", true)); + item = std::make_shared(base + "recordings/deleted/", true); item->SetLabel(g_localizeStrings.Get(19184)); // Deleted recordings item->SetLabelPreformatted(true); results.Add(item); @@ -309,7 +309,7 @@ void GetSubDirectories(const CPVRRecordingsPath& recParentPath, std::shared_ptr item; if (!results.Contains(strFilePath)) { - item.reset(new CFileItem(strCurrent, true)); + item = std::make_shared(strCurrent, true); item->SetPath(strFilePath); item->SetLabel(strCurrent); item->SetLabelPreformatted(true); @@ -468,13 +468,13 @@ bool CPVRGUIDirectory::GetChannelsDirectory(CFileItemList& results) const std::shared_ptr item; // all tv channels - item.reset(new CFileItem(CPVRChannelsPath::PATH_TV_CHANNELS, true)); + item = std::make_shared(CPVRChannelsPath::PATH_TV_CHANNELS, true); item->SetLabel(g_localizeStrings.Get(19020)); // TV item->SetLabelPreformatted(true); results.Add(item); // all radio channels - item.reset(new CFileItem(CPVRChannelsPath::PATH_RADIO_CHANNELS, true)); + item = std::make_shared(CPVRChannelsPath::PATH_RADIO_CHANNELS, true); item->SetLabel(g_localizeStrings.Get(19021)); // Radio item->SetLabelPreformatted(true); results.Add(item); @@ -575,7 +575,7 @@ bool GetTimersSubDirectory(const CPVRTimersPath& path, if ((timer->IsRadio() == bRadio) && timer->HasParent() && (timer->ClientID() == iClientId) && (timer->ParentClientIndex() == iParentId) && (!bHideDisabled || !timer->IsDisabled())) { - item.reset(new CFileItem(timer)); + item = std::make_shared(timer); const CPVRTimersPath timersPath(path.GetPath(), timer->ClientID(), timer->ClientIndex()); item->SetPath(timersPath.GetPath()); results.Add(item); diff --git a/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp b/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp index f4b4fee56fe7d..38e6e0645dc29 100644 --- a/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp +++ b/xbmc/pvr/guilib/GUIEPGGridContainerModel.cpp @@ -132,7 +132,7 @@ void CGUIEPGGridContainerModel::Initialize(const std::unique_ptr& const CDateTimeSpan unit(0, 0, iRulerUnit * MINSPERBLOCK, 0); for (; ruler < rulerEnd; ruler += unit) { - rulerItem.reset(new CFileItem(ruler.GetAsLocalizedTime("", false))); + rulerItem = std::make_shared(ruler.GetAsLocalizedTime("", false)); rulerItem->SetLabel2(ruler.GetAsLocalizedDate(true)); m_rulerItems.emplace_back(rulerItem); } diff --git a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp index 0ccfdf8822207..538217c9ae235 100644 --- a/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp +++ b/xbmc/pvr/guilib/PVRGUIChannelNavigator.cpp @@ -23,6 +23,7 @@ #include "utils/JobManager.h" #include "utils/XTimeUtils.h" +#include #include using namespace KODI::GUILIB::GUIINFO; @@ -316,7 +317,7 @@ void CPVRGUIChannelNavigator::HideInfo() { m_currentChannel = m_playingChannel; if (m_playingChannel) - item.reset(new CFileItem(m_playingChannel)); + item = std::make_shared(m_playingChannel); } CheckAndPublishPreviewAndPlayerShowInfoChangedEvent(); @@ -350,7 +351,7 @@ void CPVRGUIChannelNavigator::SetPlayingChannel( { m_currentChannel = m_playingChannel; if (m_playingChannel) - item.reset(new CFileItem(m_playingChannel)); + item = std::make_shared(m_playingChannel); } CheckAndPublishPreviewAndPlayerShowInfoChangedEvent(); diff --git a/xbmc/pvr/timers/PVRTimerInfoTag.cpp b/xbmc/pvr/timers/PVRTimerInfoTag.cpp index 0a11cf86e0c20..ba153d0033a3a 100644 --- a/xbmc/pvr/timers/PVRTimerInfoTag.cpp +++ b/xbmc/pvr/timers/PVRTimerInfoTag.cpp @@ -829,7 +829,7 @@ std::shared_ptr CPVRTimerInfoTag::CreateFromDate( if (!newTimer) { - newTimer.reset(new CPVRTimerInfoTag); + newTimer = std::make_shared(); newTimer->m_iClientIndex = PVR_TIMER_NO_CLIENT_INDEX; newTimer->m_iParentClientIndex = PVR_TIMER_NO_PARENT; diff --git a/xbmc/pvr/timers/PVRTimers.cpp b/xbmc/pvr/timers/PVRTimers.cpp index 9537a52016ea6..0d37840eed7b1 100644 --- a/xbmc/pvr/timers/PVRTimers.cpp +++ b/xbmc/pvr/timers/PVRTimers.cpp @@ -264,8 +264,7 @@ bool CPVRTimers::UpdateEntries(const CPVRTimersContainer& timers, else { /* new timer */ - std::shared_ptr newTimer = - std::shared_ptr(new CPVRTimerInfoTag); + std::shared_ptr newTimer = std::make_shared(); newTimer->UpdateEntry(timersEntry); newTimer->SetTimerID(++m_iLastId); InsertEntry(newTimer); @@ -928,7 +927,7 @@ std::shared_ptr CPVRTimers::UpdateEntry( } else { - tag.reset(new CPVRTimerInfoTag()); + tag = std::make_shared(); if (tag->UpdateEntry(timer)) { tag->SetTimerID(++m_iLastId); diff --git a/xbmc/settings/dialogs/GUIDialogContentSettings.cpp b/xbmc/settings/dialogs/GUIDialogContentSettings.cpp index da4caf1b6cce8..61a5edc454daf 100644 --- a/xbmc/settings/dialogs/GUIDialogContentSettings.cpp +++ b/xbmc/settings/dialogs/GUIDialogContentSettings.cpp @@ -367,16 +367,29 @@ void CGUIDialogContentSettings::InitializeSettings() // define an enable dependency with (m_useDirectoryNames && !m_containsSingleItem) || !m_useDirectoryNames CSettingDependency dependencyScanRecursive(SettingDependencyType::Enable, GetSettingsManager()); dependencyScanRecursive.Or() - ->Add(CSettingDependencyConditionCombinationPtr((new CSettingDependencyConditionCombination(BooleanLogicOperationAnd, GetSettingsManager())) // m_useDirectoryNames && !m_containsSingleItem - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))) // m_useDirectoryNames - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_CONTAINS_SINGLE_ITEM, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))))) // !m_containsSingleItem - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))); // !m_useDirectoryNames + ->Add(CSettingDependencyConditionCombinationPtr( + (new CSettingDependencyConditionCombination( + BooleanLogicOperationAnd, + GetSettingsManager())) // m_useDirectoryNames && !m_containsSingleItem + ->Add(std::make_shared( + SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, + GetSettingsManager())) // m_useDirectoryNames + ->Add(std::make_shared( + SETTING_CONTAINS_SINGLE_ITEM, "false", SettingDependencyOperator::Equals, + false, GetSettingsManager())))) // !m_containsSingleItem + ->Add(std::make_shared( + SETTING_USE_DIRECTORY_NAMES, "false", SettingDependencyOperator::Equals, false, + GetSettingsManager())); // !m_useDirectoryNames // define an enable dependency with m_useDirectoryNames && !m_scanRecursive CSettingDependency dependencyContainsSingleItem(SettingDependencyType::Enable, GetSettingsManager()); dependencyContainsSingleItem.And() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))) // m_useDirectoryNames - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_SCAN_RECURSIVE, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))); // !m_scanRecursive + ->Add(std::make_shared( + SETTING_USE_DIRECTORY_NAMES, "true", SettingDependencyOperator::Equals, false, + GetSettingsManager())) // m_useDirectoryNames + ->Add(std::make_shared( + SETTING_SCAN_RECURSIVE, "false", SettingDependencyOperator::Equals, false, + GetSettingsManager())); // !m_scanRecursive SettingDependencies deps; deps.push_back(dependencyScanRecursive); diff --git a/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp b/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp index d466e5da1f6ef..77086caf2a615 100644 --- a/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp +++ b/xbmc/settings/dialogs/GUIDialogSettingsBase.cpp @@ -30,6 +30,7 @@ #include "utils/StringUtils.h" #include "utils/Variant.h" +#include #include #include #include @@ -675,8 +676,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlRadioButtonSetting( - static_cast(pControl), iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "spinner") { @@ -686,8 +687,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetText(label); - pSettingControl.reset(new CGUIControlSpinExSetting(static_cast(pControl), - iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "edit") { @@ -697,8 +698,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlEditSetting(static_cast(pControl), - iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "list") { @@ -708,8 +709,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlListSetting(static_cast(pControl), - iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "button" || controlType == "slider") { @@ -722,8 +723,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlButtonSetting(static_cast(pControl), - iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else { @@ -733,8 +734,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetText(label); - pSettingControl.reset(new CGUIControlSliderSetting( - static_cast(pControl), iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } } else if (controlType == "range") @@ -745,8 +746,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetText(label); - pSettingControl.reset(new CGUIControlRangeSetting( - static_cast(pControl), iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "label") { @@ -756,8 +757,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return NULL; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlLabelSetting(static_cast(pControl), - iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else if (controlType == "colorbutton") { @@ -767,8 +768,8 @@ CGUIControl* CGUIDialogSettingsBase::AddSetting(const std::shared_ptr& return nullptr; static_cast(pControl)->SetLabel(label); - pSettingControl.reset(new CGUIControlColorButtonSetting( - static_cast(pControl), iControlID, pSetting, this)); + pSettingControl = std::make_shared( + static_cast(pControl), iControlID, pSetting, this); } else return nullptr; diff --git a/xbmc/threads/test/TestEvent.cpp b/xbmc/threads/test/TestEvent.cpp index 5f9fd10fb1d91..0109a9c5131ba 100644 --- a/xbmc/threads/test/TestEvent.cpp +++ b/xbmc/threads/test/TestEvent.cpp @@ -572,7 +572,7 @@ template void RunMassEventTest(std::vector>& m, boo { std::vector> t(NUMTHREADS); for(size_t i=0; i(*m[i]); EXPECT_TRUE(waitForThread(g_mutex, NUMTHREADS, 10000ms)); if (canWaitOnEvent) @@ -608,7 +608,7 @@ TEST(TestMassEvent, General) std::vector> m(NUMTHREADS); for(size_t i=0; i(); RunMassEventTest(m,true); delete g_event; @@ -620,7 +620,7 @@ TEST(TestMassEvent, Polling) std::vector> m(NUMTHREADS); for(size_t i=0; i(); RunMassEventTest(m,false); delete g_event; diff --git a/xbmc/utils/BooleanLogic.cpp b/xbmc/utils/BooleanLogic.cpp index 7ccc54750cadb..f58a05b98719d 100644 --- a/xbmc/utils/BooleanLogic.cpp +++ b/xbmc/utils/BooleanLogic.cpp @@ -12,6 +12,8 @@ #include "utils/XBMCTinyXML.h" #include "utils/log.h" +#include + bool CBooleanLogicValue::Deserialize(const TiXmlNode *node) { if (node == NULL) @@ -112,7 +114,7 @@ bool CBooleanLogic::Deserialize(const TiXmlNode *node) if (m_operation == NULL) { - m_operation = CBooleanLogicOperationPtr(new CBooleanLogicOperation()); + m_operation = std::make_shared(); if (m_operation == NULL) return false; diff --git a/xbmc/utils/FileOperationJob.cpp b/xbmc/utils/FileOperationJob.cpp index 8b5066a7e0428..7950c792ba94f 100644 --- a/xbmc/utils/FileOperationJob.cpp +++ b/xbmc/utils/FileOperationJob.cpp @@ -22,6 +22,8 @@ #include "utils/URIUtils.h" #include "utils/log.h" +#include + using namespace XFILE; CFileOperationJob::CFileOperationJob() @@ -58,7 +60,7 @@ void CFileOperationJob::SetFileOperation(FileAction action, m_items.Clear(); for (int i = 0; i < items.Size(); i++) - m_items.Add(CFileItemPtr(new CFileItem(*items[i]))); + m_items.Add(std::make_shared(*items[i])); } bool CFileOperationJob::DoWork() diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 087232e4f2cdd..f5ea42ca25c19 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -6225,7 +6225,7 @@ CDateTime CVideoDatabase::SetPlayCount(const CFileItem& item, int count, const C if (item.GetVideoInfoTag()->GetPlayCount() != count) data["playcount"] = count; CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::VideoLibrary, "OnUpdate", - CFileItemPtr(new CFileItem(item)), data); + std::make_shared(item), data); } return lastPlayed; diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 6b7ab281e5220..835d9e86180f5 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -47,6 +47,7 @@ #include "video/VideoThumbLoader.h" #include +#include #include using namespace XFILE; @@ -1478,7 +1479,7 @@ namespace VIDEO m_database.Close(); - CFileItemPtr itemCopy = CFileItemPtr(new CFileItem(*pItem)); + CFileItemPtr itemCopy = std::make_shared(*pItem); CVariant data; data["added"] = true; if (m_bRunning) @@ -2079,7 +2080,7 @@ namespace VIDEO const std::vector &excludes) const { CFileItemList items; - items.Add(CFileItemPtr(new CFileItem(directory, true))); + items.Add(std::make_shared(directory, true)); CUtil::GetRecursiveDirsListing(directory, items, DIR_FLAG_NO_FILE_DIRS | DIR_FLAG_NO_FILE_INFO); CDigest digest{CDigest::Type::MD5}; diff --git a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp index 83b888b90d6ec..9c8b1b10ffb0e 100644 --- a/xbmc/video/dialogs/GUIDialogAudioSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogAudioSettings.cpp @@ -33,6 +33,7 @@ #include "utils/log.h" #include "video/VideoDatabase.h" +#include #include #include @@ -241,8 +242,11 @@ void CGUIDialogAudioSettings::InitializeSettings() CSettingDependency dependencyAudioOutputPassthroughDisabled(SettingDependencyType::Enable, GetSettingsManager()); dependencyAudioOutputPassthroughDisabled.Or() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_AUDIO_PASSTHROUGH, "false", SettingDependencyOperator::Equals, false, GetSettingsManager()))) - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition("IsPlayingPassthrough", "", "", true, GetSettingsManager()))); + ->Add(std::make_shared(SETTING_AUDIO_PASSTHROUGH, "false", + SettingDependencyOperator::Equals, false, + GetSettingsManager())) + ->Add(std::make_shared("IsPlayingPassthrough", "", "", true, + GetSettingsManager())); SettingDependencies depsAudioOutputPassthroughDisabled; depsAudioOutputPassthroughDisabled.push_back(dependencyAudioOutputPassthroughDisabled); diff --git a/xbmc/video/dialogs/GUIDialogCMSSettings.cpp b/xbmc/video/dialogs/GUIDialogCMSSettings.cpp index ac1a7addfa0a3..eae6ee6354733 100644 --- a/xbmc/video/dialogs/GUIDialogCMSSettings.cpp +++ b/xbmc/video/dialogs/GUIDialogCMSSettings.cpp @@ -27,6 +27,7 @@ #include "utils/log.h" #include "video/VideoDatabase.h" +#include #include #define SETTING_VIDEO_CMSENABLE "videoscreen.cmsenabled" @@ -79,31 +80,35 @@ void CGUIDialogCMSSettings::InitializeSettings() // create "depsCmsEnabled" for settings depending on CMS being enabled CSettingDependency dependencyCmsEnabled(SettingDependencyType::Enable, GetSettingsManager()); - dependencyCmsEnabled.Or() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSENABLE, "true", SettingDependencyOperator::Equals, false, GetSettingsManager()))); + dependencyCmsEnabled.Or()->Add(std::make_shared( + SETTING_VIDEO_CMSENABLE, "true", SettingDependencyOperator::Equals, false, + GetSettingsManager())); SettingDependencies depsCmsEnabled; depsCmsEnabled.push_back(dependencyCmsEnabled); // create "depsCms3dlut" for 3dlut settings CSettingDependency dependencyCms3dlut(SettingDependencyType::Visible, GetSettingsManager()); - dependencyCms3dlut.And() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_3DLUT), SettingDependencyOperator::Equals, false, GetSettingsManager()))); + dependencyCms3dlut.And()->Add(std::make_shared( + SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_3DLUT), SettingDependencyOperator::Equals, + false, GetSettingsManager())); SettingDependencies depsCms3dlut; depsCms3dlut.push_back(dependencyCmsEnabled); depsCms3dlut.push_back(dependencyCms3dlut); // create "depsCmsIcc" for display settings with icc profile CSettingDependency dependencyCmsIcc(SettingDependencyType::Visible, GetSettingsManager()); - dependencyCmsIcc.And() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_PROFILE), SettingDependencyOperator::Equals, false, GetSettingsManager()))); + dependencyCmsIcc.And()->Add(std::make_shared( + SETTING_VIDEO_CMSMODE, std::to_string(CMS_MODE_PROFILE), SettingDependencyOperator::Equals, + false, GetSettingsManager())); SettingDependencies depsCmsIcc; depsCmsIcc.push_back(dependencyCmsEnabled); depsCmsIcc.push_back(dependencyCmsIcc); // create "depsCmsGamma" for effective gamma adjustment (not available with bt.1886) CSettingDependency dependencyCmsGamma(SettingDependencyType::Visible, GetSettingsManager()); - dependencyCmsGamma.And() - ->Add(CSettingDependencyConditionPtr(new CSettingDependencyCondition(SETTING_VIDEO_CMSGAMMAMODE, std::to_string(CMS_TRC_BT1886), SettingDependencyOperator::Equals, true, GetSettingsManager()))); + dependencyCmsGamma.And()->Add(std::make_shared( + SETTING_VIDEO_CMSGAMMAMODE, std::to_string(CMS_TRC_BT1886), SettingDependencyOperator::Equals, + true, GetSettingsManager())); SettingDependencies depsCmsGamma; depsCmsGamma.push_back(dependencyCmsEnabled); depsCmsGamma.push_back(dependencyCmsIcc); diff --git a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp index 25381bbe8230f..486bd7a7277c8 100644 --- a/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp +++ b/xbmc/video/jobs/VideoLibraryMarkWatchedJob.cpp @@ -6,23 +6,25 @@ * See LICENSES/README.md for more information. */ -#include - #include "VideoLibraryMarkWatchedJob.h" + #include "FileItem.h" +#include "ServiceBroker.h" #include "Util.h" #include "filesystem/Directory.h" #ifdef HAS_UPNP #include "network/upnp/UPnP.h" #endif +#include "profiles/ProfileManager.h" #include "pvr/PVRManager.h" #include "pvr/recordings/PVRRecordings.h" -#include "profiles/ProfileManager.h" #include "settings/SettingsComponent.h" -#include "ServiceBroker.h" #include "utils/URIUtils.h" #include "video/VideoDatabase.h" +#include +#include + CVideoLibraryMarkWatchedJob::CVideoLibraryMarkWatchedJob(const std::shared_ptr& item, bool mark) : m_item(item), m_mark(mark) @@ -50,7 +52,7 @@ bool CVideoLibraryMarkWatchedJob::Work(CVideoDatabase &db) return false; CFileItemList items; - items.Add(CFileItemPtr(new CFileItem(*m_item))); + items.Add(std::make_shared(*m_item)); if (m_item->m_bIsFolder) CUtil::GetRecursiveListing(m_item->GetPath(), items, "", XFILE::DIR_FLAG_NO_FILE_INFO); diff --git a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp index 9f300720b2507..4e10720a3c9f3 100644 --- a/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp +++ b/xbmc/video/jobs/VideoLibraryRefreshingJob.cpp @@ -32,6 +32,7 @@ #include "video/tags/VideoInfoTagLoaderFactory.h" #include "video/tags/VideoTagLoaderPlugin.h" +#include #include using namespace KODI::MESSAGING; @@ -281,13 +282,13 @@ bool CVideoLibraryRefreshingJob::Work(CVideoDatabase &db) } // otherwise just add a copy of the item else - items.Add(CFileItemPtr(new CFileItem(*m_item->GetVideoInfoTag()))); + items.Add(std::make_shared(*m_item->GetVideoInfoTag())); // update the path to the real path (instead of a videodb:// one) path = m_item->GetVideoInfoTag()->m_strPath; } else - items.Add(CFileItemPtr(new CFileItem(*m_item))); + items.Add(std::make_shared(*m_item)); // set the proper path of the list of items to lookup items.SetPath(m_item->m_bIsFolder ? URIUtils::GetParentPath(path) : URIUtils::GetDirectory(path)); diff --git a/xbmc/video/windows/GUIWindowVideoBase.cpp b/xbmc/video/windows/GUIWindowVideoBase.cpp index 53b9dcccd87cc..37665111aad19 100644 --- a/xbmc/video/windows/GUIWindowVideoBase.cpp +++ b/xbmc/video/windows/GUIWindowVideoBase.cpp @@ -59,6 +59,8 @@ #include "video/guilib/VideoSelectActionProcessor.h" #include "view/GUIViewState.h" +#include + using namespace XFILE; using namespace VIDEODATABASEDIRECTORY; using namespace VIDEO; @@ -1137,7 +1139,7 @@ bool CGUIWindowVideoBase::GetDirectory(const std::string &strDirectory, CFileIte newPlaylist->SetLabelPreformatted(true); items.Add(newPlaylist); */ - newPlaylist.reset(new CFileItem("newsmartplaylist://video", false)); + newPlaylist = std::make_shared("newsmartplaylist://video", false); newPlaylist->SetLabel(g_localizeStrings.Get(21437)); // "new smart playlist..." newPlaylist->SetArt("icon", "DefaultAddSource.png"); newPlaylist->SetLabelPreformatted(true); diff --git a/xbmc/video/windows/VideoFileItemListModifier.cpp b/xbmc/video/windows/VideoFileItemListModifier.cpp index 4205d68af13b4..b17a71db873bc 100644 --- a/xbmc/video/windows/VideoFileItemListModifier.cpp +++ b/xbmc/video/windows/VideoFileItemListModifier.cpp @@ -18,6 +18,8 @@ #include "video/VideoDatabase.h" #include "video/VideoDbUrl.h" +#include + using namespace XFILE::VIDEODATABASEDIRECTORY; bool CVideoFileItemListModifier::CanModify(const CFileItemList &items) const @@ -65,7 +67,7 @@ void CVideoFileItemListModifier::AddQueuingFolder(CFileItemList& items) case NODE_TYPE_SEASONS: { const std::string& strLabel = g_localizeStrings.Get(20366); - pItem.reset(new CFileItem(strLabel)); // "All Seasons" + pItem = std::make_shared(strLabel); // "All Seasons" videoUrl.AppendPath("-1/"); pItem->SetPath(videoUrl.ToString()); // set the number of watched and unwatched items accordingly @@ -120,7 +122,7 @@ void CVideoFileItemListModifier::AddQueuingFolder(CFileItemList& items) } break; case NODE_TYPE_MUSICVIDEOS_ALBUM: - pItem.reset(new CFileItem("* " + g_localizeStrings.Get(16100))); // "* All Videos" + pItem = std::make_shared("* " + g_localizeStrings.Get(16100)); // "* All Videos" videoUrl.AppendPath("-1/"); pItem->SetPath(videoUrl.ToString()); break; From f0786bd95fb1268fe1dfe0a94a598b20918d0d52 Mon Sep 17 00:00:00 2001 From: Rechi Date: Fri, 13 Oct 2023 14:52:11 +1000 Subject: [PATCH 395/811] [clang-tidy] modernize-make-unique --- .clang-tidy | 1 + xbmc/ServiceManager.cpp | 55 ++++++++------- xbmc/addons/Skin.cpp | 5 +- xbmc/application/Application.cpp | 6 +- .../AudioEngine/Engines/ActiveAE/ActiveAE.cpp | 12 ++-- .../Engines/ActiveAE/ActiveAEBuffer.cpp | 4 +- xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp | 3 +- xbmc/cores/RetroPlayer/RetroPlayer.cpp | 11 +-- .../playback/ReversiblePlayback.cpp | 3 +- .../savestates/SavestateDatabase.cpp | 4 +- .../savestates/SavestateFlatBuffer.cpp | 17 +++-- xbmc/cores/VideoPlayer/DVDClock.cpp | 3 +- .../DVDDemuxers/DVDDemuxFFmpeg.cpp | 20 +++--- .../DVDDemuxers/DVDDemuxVobsub.cpp | 4 +- .../DVDInputStreams/DVDInputStreamBluray.cpp | 3 +- .../DVDInputStreamNavigator.cpp | 31 +++++---- .../DVDSubtitles/DVDSubtitleParser.h | 2 +- .../cores/VideoPlayer/Process/ProcessInfo.cpp | 4 +- xbmc/cores/VideoPlayer/VideoPlayer.cpp | 12 ++-- .../HwDecRender/DRMPRIMEEGL.cpp | 4 +- .../HwDecRender/RendererDRMPRIMEGLES.cpp | 4 +- .../HwDecRender/RendererVAAPIGL.cpp | 6 +- .../HwDecRender/RendererVAAPIGLES.cpp | 8 ++- .../VideoRenderers/LinuxRendererGL.cpp | 10 ++- .../VideoRenderers/RenderManager.cpp | 10 +-- xbmc/cores/paplayer/PAPlayer.cpp | 3 +- xbmc/dbwrappers/Database.cpp | 13 ++-- xbmc/events/EventLogManager.cpp | 3 +- xbmc/games/addons/GameClient.cpp | 3 +- xbmc/games/addons/GameClientSubsystem.cpp | 9 ++- xbmc/games/addons/input/GameClientInput.cpp | 7 +- .../addons/streams/GameClientStreams.cpp | 6 +- .../controllers/types/ControllerNode.cpp | 7 +- xbmc/games/ports/input/PortInput.cpp | 4 +- xbmc/guilib/GUIBaseContainer.cpp | 2 + xbmc/guilib/GUIComponent.cpp | 16 +++-- xbmc/guilib/GUIVisualisationControl.cpp | 4 +- xbmc/guilib/guiinfo/PicturesGUIInfo.cpp | 3 +- xbmc/guilib/guiinfo/PlayerGUIInfo.cpp | 3 +- .../input/joysticks/generic/ButtonMapping.cpp | 3 +- .../input/joysticks/keymaps/KeymapHandler.cpp | 3 +- xbmc/interfaces/legacy/Dialog.cpp | 4 +- .../peripherals/addons/AddonButtonMapping.cpp | 6 +- .../peripherals/addons/AddonInputHandling.cpp | 17 +++-- .../devices/PeripheralJoystick.cpp | 9 +-- xbmc/pictures/GUIWindowSlideShow.cpp | 2 +- .../platform/android/activity/JNIXBMCFile.cpp | 4 +- xbmc/platform/android/activity/XBMCApp.cpp | 69 +++++++++---------- xbmc/platform/linux/input/LibInputHandler.cpp | 9 +-- xbmc/powermanagement/PowerManager.cpp | 2 +- xbmc/pvr/PVRManager.cpp | 4 +- xbmc/pvr/PVRPlaybackState.cpp | 5 +- xbmc/pvr/addons/PVRClientCapabilities.cpp | 7 +- xbmc/pvr/addons/PVRClientMenuHooks.cpp | 4 +- xbmc/pvr/epg/EpgContainer.cpp | 4 +- xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp | 5 +- xbmc/pvr/recordings/PVRRecordings.cpp | 2 +- xbmc/pvr/timers/PVRTimerRuleMatcher.cpp | 6 +- xbmc/pvr/windows/GUIWindowPVRBase.cpp | 2 +- xbmc/pvr/windows/GUIWindowPVRGuide.cpp | 2 +- xbmc/rendering/RenderSystem.cpp | 10 ++- xbmc/rendering/gl/ScreenshotSurfaceGL.cpp | 3 +- xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp | 3 +- xbmc/threads/Event.cpp | 3 +- xbmc/video/tags/VideoTagLoaderPlugin.cpp | 8 ++- xbmc/windowing/WinSystem.cpp | 9 ++- xbmc/windowing/X11/WinSystemX11.cpp | 3 +- xbmc/windowing/X11/WinSystemX11GLContext.cpp | 3 +- xbmc/windowing/android/WinSystemAndroid.cpp | 6 +- .../android/WinSystemAndroidGLESContext.cpp | 10 ++- xbmc/windowing/ios/WinSystemIOS.mm | 3 +- xbmc/windowing/osx/WinSystemOSX.mm | 3 +- xbmc/windowing/tvos/WinSystemTVOS.mm | 2 +- .../wayland/InputProcessorKeyboard.cpp | 3 +- .../windowing/wayland/SeatInputProcessing.cpp | 7 +- xbmc/windowing/wayland/SeatInputProcessing.h | 2 +- xbmc/windowing/wayland/WinEventsWayland.cpp | 2 +- xbmc/windowing/wayland/WinSystemWayland.cpp | 15 ++-- xbmc/windowing/wayland/WinSystemWayland.h | 4 +- xbmc/windowing/wayland/WindowDecorator.cpp | 3 +- xbmc/windowing/wayland/XkbcommonKeymap.cpp | 5 +- xbmc/windows/GUIWindowSplash.cpp | 7 +- 82 files changed, 358 insertions(+), 250 deletions(-) diff --git a/.clang-tidy b/.clang-tidy index 2a0e381fa28f2..e298de45658e9 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ Checks: "\ modernize-make-shared,\ + modernize-make-unique,\ modernize-use-default-member-init,\ performance-faster-string-find,\ performance-for-range-copy,\ diff --git a/xbmc/ServiceManager.cpp b/xbmc/ServiceManager.cpp index ba94b8eec8b42..e6901ac7a873d 100644 --- a/xbmc/ServiceManager.cpp +++ b/xbmc/ServiceManager.cpp @@ -48,6 +48,8 @@ #include "utils/log.h" #include "weather/WeatherManager.h" +#include + using namespace KODI; CServiceManager::CServiceManager() = default; @@ -66,18 +68,18 @@ bool CServiceManager::InitForTesting() { m_network = CNetworkBase::GetNetwork(); - m_databaseManager.reset(new CDatabaseManager); + m_databaseManager = std::make_unique(); - m_binaryAddonManager.reset(new ADDON::CBinaryAddonManager()); - m_addonMgr.reset(new ADDON::CAddonMgr()); + m_binaryAddonManager = std::make_unique(); + m_addonMgr = std::make_unique(); if (!m_addonMgr->Init()) { CLog::Log(LOGFATAL, "CServiceManager::{}: Unable to start CAddonMgr", __FUNCTION__); return false; } - m_extsMimeSupportList.reset(new ADDONS::CExtsMimeSupportList(*m_addonMgr)); - m_fileExtensionProvider.reset(new CFileExtensionProvider(*m_addonMgr)); + m_extsMimeSupportList = std::make_unique(*m_addonMgr); + m_fileExtensionProvider = std::make_unique(*m_addonMgr); init_level = 1; return true; @@ -101,12 +103,12 @@ bool CServiceManager::InitStageOne() return false; #ifdef HAS_PYTHON - m_XBPython.reset(new XBPython()); + m_XBPython = std::make_unique(); CScriptInvocationManager::GetInstance().RegisterLanguageInvocationHandler(m_XBPython.get(), ".py"); #endif - m_playlistPlayer.reset(new PLAYLIST::CPlayListPlayer()); + m_playlistPlayer = std::make_unique(); m_network = CNetworkBase::GetNetwork(); @@ -117,55 +119,56 @@ bool CServiceManager::InitStageOne() bool CServiceManager::InitStageTwo(const std::string& profilesUserDataFolder) { // Initialize the addon database (must be before the addon manager is init'd) - m_databaseManager.reset(new CDatabaseManager); + m_databaseManager = std::make_unique(); - m_binaryAddonManager.reset( - new ADDON:: - CBinaryAddonManager()); /* Need to constructed before, GetRunningInstance() of binary CAddonDll need to call them */ - m_addonMgr.reset(new ADDON::CAddonMgr()); + m_binaryAddonManager = std::make_unique< + ADDON:: + CBinaryAddonManager>(); /* Need to constructed before, GetRunningInstance() of binary CAddonDll need to call them */ + m_addonMgr = std::make_unique(); if (!m_addonMgr->Init()) { CLog::Log(LOGFATAL, "CServiceManager::{}: Unable to start CAddonMgr", __FUNCTION__); return false; } - m_repositoryUpdater.reset(new ADDON::CRepositoryUpdater(*m_addonMgr)); + m_repositoryUpdater = std::make_unique(*m_addonMgr); - m_extsMimeSupportList.reset(new ADDONS::CExtsMimeSupportList(*m_addonMgr)); + m_extsMimeSupportList = std::make_unique(*m_addonMgr); - m_vfsAddonCache.reset(new ADDON::CVFSAddonCache()); + m_vfsAddonCache = std::make_unique(); m_vfsAddonCache->Init(); - m_PVRManager.reset(new PVR::CPVRManager()); + m_PVRManager = std::make_unique(); m_dataCacheCore.reset(new CDataCacheCore()); - m_binaryAddonCache.reset(new ADDON::CBinaryAddonCache()); + m_binaryAddonCache = std::make_unique(); m_binaryAddonCache->Init(); m_favouritesService.reset(new CFavouritesService(profilesUserDataFolder)); - m_serviceAddons.reset(new ADDON::CServiceAddonManager(*m_addonMgr)); + m_serviceAddons = std::make_unique(*m_addonMgr); m_contextMenuManager.reset(new CContextMenuManager(*m_addonMgr)); m_gameControllerManager = std::make_unique(*m_addonMgr); - m_inputManager.reset(new CInputManager()); + m_inputManager = std::make_unique(); m_inputManager->InitializeInputs(); - m_peripherals.reset(new PERIPHERALS::CPeripherals(*m_inputManager, *m_gameControllerManager)); + m_peripherals = + std::make_unique(*m_inputManager, *m_gameControllerManager); - m_gameRenderManager.reset(new RETRO::CGUIGameRenderManager); + m_gameRenderManager = std::make_unique(); - m_fileExtensionProvider.reset(new CFileExtensionProvider(*m_addonMgr)); + m_fileExtensionProvider = std::make_unique(*m_addonMgr); - m_powerManager.reset(new CPowerManager()); + m_powerManager = std::make_unique(); m_powerManager->Initialize(); m_powerManager->SetDefaults(); - m_weatherManager.reset(new CWeatherManager()); + m_weatherManager = std::make_unique(); - m_mediaManager.reset(new CMediaManager()); + m_mediaManager = std::make_unique(); m_mediaManager->Initialize(); #if !defined(TARGET_WINDOWS) && defined(HAS_OPTICAL_DRIVE) @@ -205,7 +208,7 @@ bool CServiceManager::InitStageThree(const std::shared_ptr& pro if (!profileManager->UsingLoginScreen()) m_PVRManager->Init(); - m_playerCoreFactory.reset(new CPlayerCoreFactory(*profileManager)); + m_playerCoreFactory = std::make_unique(*profileManager); if (!m_Platform->InitStageThree()) return false; diff --git a/xbmc/addons/Skin.cpp b/xbmc/addons/Skin.cpp index 9e63a3f04783a..a6192d902ba5a 100644 --- a/xbmc/addons/Skin.cpp +++ b/xbmc/addons/Skin.cpp @@ -34,6 +34,7 @@ #include "utils/log.h" #include +#include #define XML_SETTINGS "settings" #define XML_SETTING "setting" @@ -157,7 +158,7 @@ CSkinInfo::CSkinInfo(const AddonInfoPtr& addonInfo, m_effectsSlowDown(1.f), m_debugging(false) { - m_settingsUpdateHandler.reset(new CSkinSettingUpdateHandler(*this)); + m_settingsUpdateHandler = std::make_unique(*this); } CSkinInfo::CSkinInfo(const AddonInfoPtr& addonInfo) : CAddon(addonInfo, AddonType::SKIN) @@ -193,7 +194,7 @@ CSkinInfo::CSkinInfo(const AddonInfoPtr& addonInfo) : CAddon(addonInfo, AddonTyp m_debugging = Type(AddonType::SKIN)->GetValue("@debugging").asBoolean(); - m_settingsUpdateHandler.reset(new CSkinSettingUpdateHandler(*this)); + m_settingsUpdateHandler = std::make_unique(*this); LoadStartupWindows(addonInfo); } diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index 8696ea9b06cc0..ce5b77c8e88e1 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -334,7 +334,7 @@ bool CApplication::Create() const auto keyboardLayoutManager = std::make_shared(); CServiceBroker::RegisterKeyboardLayoutManager(keyboardLayoutManager); - m_ServiceManager.reset(new CServiceManager()); + m_ServiceManager = std::make_unique(); if (!m_ServiceManager->InitStageOne()) { @@ -404,7 +404,7 @@ bool CApplication::Create() return false; } - m_pActiveAE.reset(new ActiveAE::CActiveAE()); + m_pActiveAE = std::make_unique(); CServiceBroker::RegisterAE(m_pActiveAE.get()); // initialize m_replayGainSettings @@ -545,7 +545,7 @@ bool CApplication::CreateGUI() if (sav_res) CDisplaySettings::GetInstance().SetCurrentResolution(RES_DESKTOP, true); - m_pGUI.reset(new CGUIComponent()); + m_pGUI = std::make_unique(); m_pGUI->Init(); // Splash requires gui component!! diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp index 8bd9e45a7644a..908b087b9809c 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAE.cpp @@ -8,10 +8,6 @@ #include "ActiveAE.h" -#include - -using namespace AE; -using namespace ActiveAE; #include "ActiveAESettings.h" #include "ActiveAESound.h" #include "ActiveAEStream.h" @@ -27,6 +23,12 @@ using namespace ActiveAE; #include "utils/log.h" #include "windowing/WinSystem.h" +#include +#include + +using namespace AE; +using namespace ActiveAE; + using namespace std::chrono_literals; namespace @@ -288,7 +290,7 @@ CActiveAE::CActiveAE() : m_stats.Reset(44100, true); m_streamIdGen = 0; - m_settingsHandler.reset(new CActiveAESettings(*this)); + m_settingsHandler = std::make_unique(*this); } CActiveAE::~CActiveAE() diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp index 3ef7c5d52d450..8e4d957cea6cd 100644 --- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp +++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAEBuffer.cpp @@ -13,6 +13,8 @@ #include "cores/AudioEngine/AEResampleFactory.h" #include "cores/AudioEngine/Utils/AEUtil.h" +#include + using namespace ActiveAE; CSoundPacket::CSoundPacket(const SampleConfig& conf, int samples) : config(conf) @@ -467,7 +469,7 @@ bool CActiveAEBufferPoolAtempo::Create(unsigned int totaltime) { CActiveAEBufferPool::Create(totaltime); - m_pTempoFilter.reset(new CActiveAEFilter()); + m_pTempoFilter = std::make_unique(); m_pTempoFilter->Init(CAEUtil::GetAVSampleFormat(m_format.m_dataFormat), m_format.m_sampleRate, CAEUtil::GetAVChannelLayout(m_format.m_channelLayout)); return true; diff --git a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp index 587406f79fe96..c6763bae13877 100644 --- a/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp +++ b/xbmc/cores/AudioEngine/Sinks/AESinkPULSE.cpp @@ -16,6 +16,7 @@ #include "utils/log.h" #include +#include #include //----------------------------------------------------------------------------- @@ -706,7 +707,7 @@ bool CAESinkPULSE::Register() pa_simple_free(s); } - m_pMonitor.reset(new CDriverMonitor()); + m_pMonitor = std::make_unique(); m_pMonitor->Start(); AE::AESinkRegEntry entry; diff --git a/xbmc/cores/RetroPlayer/RetroPlayer.cpp b/xbmc/cores/RetroPlayer/RetroPlayer.cpp index def0e7d945e7c..e80987a701178 100644 --- a/xbmc/cores/RetroPlayer/RetroPlayer.cpp +++ b/xbmc/cores/RetroPlayer/RetroPlayer.cpp @@ -50,6 +50,7 @@ #include "utils/log.h" #include "windowing/WinSystem.h" +#include #include using namespace KODI; @@ -100,7 +101,7 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options m_processInfo->ResetInfo(); m_guiMessenger = std::make_unique(*m_processInfo); - m_renderManager.reset(new CRPRenderManager(*m_processInfo)); + m_renderManager = std::make_unique(*m_processInfo); std::unique_lock lock(m_mutex); @@ -128,7 +129,7 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options m_gameClient = std::static_pointer_cast(addon); if (m_gameClient->Initialize()) { - m_streamManager.reset(new CRPStreamManager(*m_renderManager, *m_processInfo)); + m_streamManager = std::make_unique(*m_renderManager, *m_processInfo); // Initialize input m_input = std::make_unique(CServiceBroker::GetPeripherals(), @@ -196,11 +197,11 @@ bool CRetroPlayer::OpenFile(const CFileItem& file, const CPlayerOptions& options // Initialize gameplay CreatePlayback(savestatePath); RegisterWindowCallbacks(); - m_playbackControl.reset(new CGUIPlaybackControl(*this)); + m_playbackControl = std::make_unique(*this); m_callback.OnPlayBackStarted(fileCopy); m_callback.OnAVStarted(fileCopy); if (!bStandalone) - m_autoSave.reset(new CRetroPlayerAutoSave(*this, m_gameServices.GameSettings())); + m_autoSave = std::make_unique(*this, m_gameServices.GameSettings()); // Set video framerate m_processInfo->SetVideoFps(static_cast(m_gameClient->GetFrameRate())); @@ -648,7 +649,7 @@ void CRetroPlayer::ResetPlayback() if (m_playback) m_playback->Deinitialize(); - m_playback.reset(new CRealtimePlayback); + m_playback = std::make_unique(); } void CRetroPlayer::OpenOSD() diff --git a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp index 8148dfd58a6b4..16a0a9369b101 100644 --- a/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp +++ b/xbmc/cores/RetroPlayer/playback/ReversiblePlayback.cpp @@ -26,6 +26,7 @@ #include "utils/log.h" #include +#include #include using namespace KODI; @@ -414,7 +415,7 @@ void CReversiblePlayback::UpdateMemoryStream() if (!m_memoryStream) { - m_memoryStream.reset(new CDeltaPairMemoryStream); + m_memoryStream = std::make_unique(); m_memoryStream->Init(m_gameClient->SerializeSize(), frameCount); } diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp index c1fee544d758a..7864a15261f06 100644 --- a/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp +++ b/xbmc/cores/RetroPlayer/savestates/SavestateDatabase.cpp @@ -20,6 +20,8 @@ #include "utils/URIUtils.h" #include "utils/log.h" +#include + namespace { constexpr auto SAVESTATE_EXTENSION = ".sav"; @@ -35,7 +37,7 @@ std::unique_ptr CSavestateDatabase::AllocateSavestate() { std::unique_ptr savestate; - savestate.reset(new CSavestateFlatBuffer); + savestate = std::make_unique(); return savestate; } diff --git a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp index 9b98bde9ce29c..e9f19ca30a9c2 100644 --- a/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp +++ b/xbmc/cores/RetroPlayer/savestates/SavestateFlatBuffer.cpp @@ -13,6 +13,8 @@ #include "utils/log.h" #include "video_generated.h" +#include + using namespace KODI; using namespace RETRO; @@ -192,7 +194,7 @@ CSavestateFlatBuffer::~CSavestateFlatBuffer() = default; void CSavestateFlatBuffer::Reset() { - m_builder.reset(new flatbuffers::FlatBufferBuilder(INITIAL_FLATBUFFER_SIZE)); + m_builder = std::make_unique(INITIAL_FLATBUFFER_SIZE); m_data.clear(); m_savestate = nullptr; } @@ -252,7 +254,7 @@ std::string CSavestateFlatBuffer::Label() const void CSavestateFlatBuffer::SetLabel(const std::string& label) { - m_labelOffset.reset(new StringOffset{m_builder->CreateString(label)}); + m_labelOffset = std::make_unique(m_builder->CreateString(label)); } std::string CSavestateFlatBuffer::Caption() const @@ -298,7 +300,7 @@ std::string CSavestateFlatBuffer::GameFileName() const void CSavestateFlatBuffer::SetGameFileName(const std::string& gameFileName) { - m_gameFileNameOffset.reset(new StringOffset{m_builder->CreateString(gameFileName)}); + m_gameFileNameOffset = std::make_unique(m_builder->CreateString(gameFileName)); } uint64_t CSavestateFlatBuffer::TimestampFrames() const @@ -336,7 +338,7 @@ std::string CSavestateFlatBuffer::GameClientID() const void CSavestateFlatBuffer::SetGameClientID(const std::string& gameClientId) { - m_emulatorAddonIdOffset.reset(new StringOffset{m_builder->CreateString(gameClientId)}); + m_emulatorAddonIdOffset = std::make_unique(m_builder->CreateString(gameClientId)); } std::string CSavestateFlatBuffer::GameClientVersion() const @@ -351,7 +353,8 @@ std::string CSavestateFlatBuffer::GameClientVersion() const void CSavestateFlatBuffer::SetGameClientVersion(const std::string& gameClientVersion) { - m_emulatorVersionOffset.reset(new StringOffset{m_builder->CreateString(gameClientVersion)}); + m_emulatorVersionOffset = + std::make_unique(m_builder->CreateString(gameClientVersion)); } AVPixelFormat CSavestateFlatBuffer::GetPixelFormat() const @@ -517,8 +520,8 @@ uint8_t* CSavestateFlatBuffer::GetMemoryBuffer(size_t size) { uint8_t* memoryBuffer = nullptr; - m_memoryDataOffset.reset( - new VectorOffset{m_builder->CreateUninitializedVector(size, &memoryBuffer)}); + m_memoryDataOffset = + std::make_unique(m_builder->CreateUninitializedVector(size, &memoryBuffer)); return memoryBuffer; } diff --git a/xbmc/cores/VideoPlayer/DVDClock.cpp b/xbmc/cores/VideoPlayer/DVDClock.cpp index cd2d024dd7bf2..d27c956ef30dd 100644 --- a/xbmc/cores/VideoPlayer/DVDClock.cpp +++ b/xbmc/cores/VideoPlayer/DVDClock.cpp @@ -16,6 +16,7 @@ #include #include +#include #include CDVDClock::CDVDClock() @@ -34,7 +35,7 @@ CDVDClock::CDVDClock() m_vSyncAdjust = 0; m_frameTime = DVD_TIME_BASE / 60.0; - m_videoRefClock.reset(new CVideoReferenceClock()); + m_videoRefClock = std::make_unique(); m_lastSystemTime = m_videoRefClock->GetTime(); m_systemOffset = m_videoRefClock->GetTime(); m_systemFrequency = CurrentHostFrequency(); diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp index 24fbc18518156..9499a383fb567 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxFFmpeg.cpp @@ -10,6 +10,9 @@ #include "DVDDemuxUtils.h" #include "DVDInputStreams/DVDInputStream.h" +#ifdef HAVE_LIBBLURAY +#include "DVDInputStreams/DVDInputStreamBluray.h" +#endif #include "DVDInputStreams/DVDInputStreamFFmpeg.h" #include "ServiceBroker.h" #include "URL.h" @@ -32,21 +35,12 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#include #include #include #include #include -extern "C" -{ -#include "libavutil/channel_layout.h" -#include "libavutil/display.h" -#include "libavutil/pixdesc.h" -} - -#ifdef HAVE_LIBBLURAY -#include "DVDInputStreams/DVDInputStreamBluray.h" -#endif #ifndef __STDC_CONSTANT_MACROS #define __STDC_CONSTANT_MACROS #endif @@ -59,9 +53,12 @@ extern "C" extern "C" { +#include #include +#include #include #include +#include } using namespace std::chrono_literals; @@ -2292,8 +2289,7 @@ void CDVDDemuxFFmpeg::ParsePacket(AVPacket* pkt) auto parser = m_parsers.find(st->index); if (parser == m_parsers.end()) { - m_parsers.insert(std::make_pair(st->index, - std::unique_ptr(new CDemuxParserFFmpeg()))); + m_parsers.insert(std::make_pair(st->index, std::make_unique())); parser = m_parsers.find(st->index); parser->second->m_parserCtx = av_parser_init(st->codecpar->codec_id); diff --git a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp index ba02a306e5489..0e8bb11dbc75d 100644 --- a/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp +++ b/xbmc/cores/VideoPlayer/DVDDemuxers/DVDDemuxVobsub.cpp @@ -18,6 +18,8 @@ #include "cores/VideoPlayer/Interface/TimingConstants.h" #include "utils/StringUtils.h" +#include + CDVDDemuxVobsub::CDVDDemuxVobsub() = default; CDVDDemuxVobsub::~CDVDDemuxVobsub() @@ -64,7 +66,7 @@ bool CDVDDemuxVobsub::Open(const std::string& filename, int source, const std::s if (!m_Input || !m_Input->Open()) return false; - m_Demuxer.reset(new CDVDDemuxFFmpeg()); + m_Demuxer = std::make_unique(); if (!m_Demuxer->Open(m_Input, false)) return false; diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp index 35b030ee228af..c7ae0433c6745 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamBluray.cpp @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -1241,7 +1242,7 @@ void CDVDInputStreamBluray::SetupPlayerSettings() bool CDVDInputStreamBluray::OpenStream(CFileItem &item) { - m_pstream.reset(new CDVDInputStreamFile(item, 0)); + m_pstream = std::make_unique(item, 0); if (!m_pstream->Open()) { diff --git a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp index 0fa5121dc01d6..178ed401dc39d 100644 --- a/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp +++ b/xbmc/cores/VideoPlayer/DVDInputStreams/DVDInputStreamNavigator.cpp @@ -7,25 +7,31 @@ */ #include "DVDInputStreamNavigator.h" -#include "filesystem/IFileTypes.h" -#include "utils/LangCodeExpander.h" + #include "../DVDDemuxSPU.h" -#include "settings/Settings.h" -#include "settings/SettingsComponent.h" #include "LangInfo.h" #include "ServiceBroker.h" +#include "filesystem/IFileTypes.h" +#if defined(TARGET_WINDOWS_STORE) +#include "filesystem/SpecialProtocol.h" +#endif +#include "guilib/LocalizeStrings.h" +#if defined(TARGET_WINDOWS_STORE) +#include "platform/Environment.h" +#endif +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" #include "utils/Geometry.h" -#include "utils/log.h" -#include "utils/URIUtils.h" +#include "utils/LangCodeExpander.h" #include "utils/StringUtils.h" -#include "guilib/LocalizeStrings.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + #if defined(TARGET_DARWIN_OSX) #include "platform/darwin/osx/CocoaInterface.h" #endif -#if defined(TARGET_WINDOWS_STORE) -#include "filesystem/SpecialProtocol.h" -#include "platform/Environment.h" -#endif + +#include namespace { @@ -144,7 +150,8 @@ bool CDVDInputStreamNavigator::Open() if (m_item.IsDiscImage()) { // if dvd image file (ISO or alike) open using libdvdnav stream callback functions - m_pstream.reset(new CDVDInputStreamFile(m_item, XFILE::READ_TRUNCATED | XFILE::READ_BITRATE | XFILE::READ_CHUNKED)); + m_pstream = std::make_unique( + m_item, XFILE::READ_TRUNCATED | XFILE::READ_BITRATE | XFILE::READ_CHUNKED); #if DVDNAV_VERSION >= 60100 if (!m_pstream->Open() || m_dll.dvdnav_open_stream2(&m_dvdnav, m_pstream.get(), &loggerCallback, &m_dvdnav_stream_cb) != DVDNAV_STATUS_OK) diff --git a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h index 4a822ad856535..1e7164944b6ba 100644 --- a/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h +++ b/xbmc/cores/VideoPlayer/DVDSubtitles/DVDSubtitleParser.h @@ -76,7 +76,7 @@ class CDVDSubtitleParserText return true; } else - m_pStream.reset(new CDVDSubtitleStream()); + m_pStream = std::make_unique(); return m_pStream->Open(m_filename); } diff --git a/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp b/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp index 00c58cb10c6af..ce244f96f9a7c 100644 --- a/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp +++ b/xbmc/cores/VideoPlayer/Process/ProcessInfo.cpp @@ -13,6 +13,7 @@ #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" +#include #include CCriticalSection createSection; @@ -42,7 +43,8 @@ CProcessInfo* CProcessInfo::CreateInstance() CProcessInfo::CProcessInfo() { - m_videoSettingsLocked.reset(new CVideoSettingsLocked(m_videoSettings, m_settingsSection)); + m_videoSettingsLocked = + std::make_unique(m_videoSettings, m_settingsSection); } void CProcessInfo::SetDataCache(CDataCacheCore *cache) diff --git a/xbmc/cores/VideoPlayer/VideoPlayer.cpp b/xbmc/cores/VideoPlayer/VideoPlayer.cpp index c4590feca78a1..238d3b383d2e4 100644 --- a/xbmc/cores/VideoPlayer/VideoPlayer.cpp +++ b/xbmc/cores/VideoPlayer/VideoPlayer.cpp @@ -17,16 +17,12 @@ #include "DVDDemuxers/DVDFactoryDemuxer.h" #include "DVDInputStreams/DVDFactoryInputStream.h" #include "DVDInputStreams/DVDInputStream.h" -#include "DVDMessage.h" -#include "VideoPlayerVideo.h" -#include "application/Application.h" - -#include #if defined(HAVE_LIBBLURAY) #include "DVDInputStreams/DVDInputStreamBluray.h" #endif #include "DVDInputStreams/DVDInputStreamNavigator.h" #include "DVDInputStreams/InputStreamPVRBase.h" +#include "DVDMessage.h" #include "FileItem.h" #include "GUIUserMessages.h" #include "LangInfo.h" @@ -35,6 +31,8 @@ #include "Util.h" #include "VideoPlayerAudio.h" #include "VideoPlayerRadioRDS.h" +#include "VideoPlayerVideo.h" +#include "application/Application.h" #include "cores/DataCacheCore.h" #include "cores/EdlEdit.h" #include "cores/FFmpeg.h" @@ -67,6 +65,8 @@ #include "windowing/WinSystem.h" #include +#include +#include #include using namespace std::chrono_literals; @@ -616,7 +616,7 @@ CVideoPlayer::CVideoPlayer(IPlayerCallback& callback) m_messenger("player"), m_renderManager(m_clock, this) { - m_outboundEvents.reset(new CJobQueue(false, 1, CJob::PRIORITY_NORMAL)); + m_outboundEvents = std::make_unique(false, 1, CJob::PRIORITY_NORMAL); m_players_created = false; m_pDemuxer = nullptr; m_pSubtitleDemuxer = nullptr; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp index ee540c9d2c697..3bf8f05f9f8be 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/DRMPRIMEEGL.cpp @@ -10,6 +10,8 @@ #include "utils/log.h" +#include + using namespace DRMPRIME; namespace @@ -55,7 +57,7 @@ CDRMPRIMETexture::~CDRMPRIMETexture() void CDRMPRIMETexture::Init(EGLDisplay eglDisplay) { - m_eglImage.reset(new CEGLImage(eglDisplay)); + m_eglImage = std::make_unique(eglDisplay); } bool CDRMPRIMETexture::Map(CVideoBufferDRMPRIME* buffer) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp index 2fcf54f3f57bb..42b82873151f8 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererDRMPRIMEGLES.cpp @@ -22,6 +22,8 @@ #include "windowing/WinSystem.h" #include "windowing/linux/WinSystemEGL.h" +#include + using namespace KODI::UTILS::EGL; CRendererDRMPRIMEGLES::~CRendererDRMPRIMEGLES() @@ -110,7 +112,7 @@ bool CRendererDRMPRIMEGLES::Configure(const VideoPicture& picture, if (!buf.fence) { buf.texture.Init(eglDisplay); - buf.fence.reset(new CEGLFence(eglDisplay)); + buf.fence = std::make_unique(eglDisplay); } } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp index 4b2a19b1b5b1d..d3d35ec20aa34 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGL.cpp @@ -13,6 +13,8 @@ #include "utils/GLUtils.h" #include "utils/log.h" +#include + using namespace VAAPI; IVaapiWinSystem* CRendererVAAPIGL::m_pWinSystem = nullptr; @@ -95,11 +97,11 @@ bool CRendererVAAPIGL::Configure(const VideoPicture& picture, float fps, unsigne { if (useVaapi2) { - tex.reset(new VAAPI::CVaapi2Texture); + tex = std::make_unique(); } else { - tex.reset(new VAAPI::CVaapi1Texture); + tex = std::make_unique(); } tex->Init(interop); } diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp index 65e1e84d02ebc..b4ce2c18b140a 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/HwDecRender/RendererVAAPIGLES.cpp @@ -17,6 +17,8 @@ #include "utils/GLUtils.h" #include "utils/log.h" +#include + using namespace VAAPI; using namespace KODI::UTILS::EGL; @@ -97,18 +99,18 @@ bool CRendererVAAPIGLES::Configure(const VideoPicture& picture, float fps, unsig { if (useVaapi2) { - tex.reset(new VAAPI::CVaapi2Texture); + tex = std::make_unique(); } else { - tex.reset(new VAAPI::CVaapi1Texture); + tex = std::make_unique(); } tex->Init(interop); } for (auto& fence : m_fences) { - fence.reset(new CEGLFence(CRendererVAAPIGLES::m_pWinSystem->GetEGLDisplay())); + fence = std::make_unique(CRendererVAAPIGLES::m_pWinSystem->GetEGLDisplay()); } return CLinuxRendererGLES::Configure(picture, fps, orientation); diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp index b4a028877b06f..f5740038620c8 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/LinuxRendererGL.cpp @@ -33,11 +33,15 @@ #include "windowing/GraphicContext.h" #include "windowing/WinSystem.h" +#ifdef TARGET_DARWIN_OSX +#include "platform/darwin/osx/CocoaInterface.h" +#endif + #include +#include #include -#ifdef TARGET_DARWIN_OSX -#include "platform/darwin/osx/CocoaInterface.h" +#if defined(TARGET_DARWIN_OSX) #include #include #endif @@ -119,7 +123,7 @@ CLinuxRendererGL::CLinuxRendererGL() m_fbo.width = 0.0; m_fbo.height = 0.0; - m_ColorManager.reset(new CColorManager()); + m_ColorManager = std::make_unique(); m_tCLUTTex = 0; m_CLUT = NULL; m_CLUTsize = 0; diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp index 432934e62d558..4d363848cbba8 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/RenderManager.cpp @@ -8,6 +8,9 @@ #include "RenderManager.h" +/* to use the same as player */ +#include "../VideoPlayer/DVDClock.h" +#include "../VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h" #include "RenderCapture.h" #include "RenderFactory.h" #include "RenderFlags.h" @@ -25,12 +28,9 @@ #include "windowing/GraphicContext.h" #include "windowing/WinSystem.h" +#include #include -/* to use the same as player */ -#include "../VideoPlayer/DVDClock.h" -#include "../VideoPlayer/DVDCodecs/Video/DVDVideoCodec.h" - using namespace std::chrono_literals; void CRenderManager::CClockSync::Reset() @@ -141,7 +141,7 @@ bool CRenderManager::Configure(const VideoPicture& picture, float fps, unsigned m_stateEvent.Reset(); m_clockSync.Reset(); m_dvdClock.SetVsyncAdjust(0); - m_pConfigPicture.reset(new VideoPicture()); + m_pConfigPicture = std::make_unique(); m_pConfigPicture->CopyRef(picture); std::unique_lock lock2(m_presentlock); diff --git a/xbmc/cores/paplayer/PAPlayer.cpp b/xbmc/cores/paplayer/PAPlayer.cpp index 496596efce4b2..1165b1c48090a 100644 --- a/xbmc/cores/paplayer/PAPlayer.cpp +++ b/xbmc/cores/paplayer/PAPlayer.cpp @@ -29,6 +29,7 @@ #include "utils/log.h" #include "video/Bookmark.h" +#include #include using namespace std::chrono_literals; @@ -296,7 +297,7 @@ bool PAPlayer::QueueNextFileEx(const CFileItem &file, bool fadeIn) file.GetStartOffset() == m_currentStream->m_fileItem->GetEndOffset() && m_currentStream && m_currentStream->m_prepareTriggered) { - m_currentStream->m_nextFileItem.reset(new CFileItem(file)); + m_currentStream->m_nextFileItem = std::make_unique(file); m_upcomingCrossfadeMS = 0; return true; } diff --git a/xbmc/dbwrappers/Database.cpp b/xbmc/dbwrappers/Database.cpp index 1c2c48c18b983..27b56d0dd2de2 100644 --- a/xbmc/dbwrappers/Database.cpp +++ b/xbmc/dbwrappers/Database.cpp @@ -12,6 +12,9 @@ #include "DbUrl.h" #include "ServiceBroker.h" #include "filesystem/SpecialProtocol.h" +#if defined(HAS_MYSQL) || defined(HAS_MARIADB) +#include "mysqldataset.h" +#endif #include "profiles/ProfileManager.h" #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" @@ -20,14 +23,12 @@ #include "utils/StringUtils.h" #include "utils/log.h" -#if defined(HAS_MYSQL) || defined(HAS_MARIADB) -#include "mysqldataset.h" -#endif - #ifdef TARGET_POSIX #include "platform/posix/ConvUtils.h" #endif +#include + using namespace dbiplus; #define MAX_COMPRESS_COUNT 20 @@ -583,12 +584,12 @@ bool CDatabase::Connect(const std::string& dbName, const DatabaseSettings& dbSet // create the appropriate database structure if (dbSettings.type == "sqlite3") { - m_pDB.reset(new SqliteDatabase()); + m_pDB = std::make_unique(); } #if defined(HAS_MYSQL) || defined(HAS_MARIADB) else if (dbSettings.type == "mysql") { - m_pDB.reset(new MysqlDatabase()); + m_pDB = std::make_unique(); } #endif else diff --git a/xbmc/events/EventLogManager.cpp b/xbmc/events/EventLogManager.cpp index d56b88acb028c..8fc306c25730c 100644 --- a/xbmc/events/EventLogManager.cpp +++ b/xbmc/events/EventLogManager.cpp @@ -10,6 +10,7 @@ #include "EventLog.h" +#include #include #include @@ -20,7 +21,7 @@ CEventLog& CEventLogManager::GetEventLog(unsigned int profileId) auto eventLog = m_eventLogs.find(profileId); if (eventLog == m_eventLogs.end()) { - m_eventLogs.insert(std::make_pair(profileId, std::unique_ptr(new CEventLog))); + m_eventLogs.insert(std::make_pair(profileId, std::make_unique())); eventLog = m_eventLogs.find(profileId); } diff --git a/xbmc/games/addons/GameClient.cpp b/xbmc/games/addons/GameClient.cpp index 4d60eb101cddb..45594fb1b44a1 100644 --- a/xbmc/games/addons/GameClient.cpp +++ b/xbmc/games/addons/GameClient.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include @@ -312,7 +313,7 @@ bool CGameClient::InitializeGameplay(const std::string& gamePath, m_gamePath = gamePath; m_input = input; - m_inGameSaves.reset(new CGameClientInGameSaves(this, m_ifc.game)); + m_inGameSaves = std::make_unique(this, m_ifc.game); m_inGameSaves->Load(); return true; diff --git a/xbmc/games/addons/GameClientSubsystem.cpp b/xbmc/games/addons/GameClientSubsystem.cpp index 599125fef6e5d..f350cf568f3ae 100644 --- a/xbmc/games/addons/GameClientSubsystem.cpp +++ b/xbmc/games/addons/GameClientSubsystem.cpp @@ -15,6 +15,8 @@ #include "games/addons/input/GameClientInput.h" #include "games/addons/streams/GameClientStreams.h" +#include + using namespace KODI; using namespace GAME; @@ -34,9 +36,10 @@ GameClientSubsystems CGameClientSubsystem::CreateSubsystems(CGameClient& gameCli GameClientSubsystems subsystems = {}; subsystems.Cheevos = std::make_unique(gameClient, gameStruct); - subsystems.Input.reset(new CGameClientInput(gameClient, gameStruct, clientAccess)); - subsystems.AddonProperties.reset(new CGameClientProperties(gameClient, *gameStruct.props)); - subsystems.Streams.reset(new CGameClientStreams(gameClient)); + subsystems.Input = std::make_unique(gameClient, gameStruct, clientAccess); + subsystems.AddonProperties = + std::make_unique(gameClient, *gameStruct.props); + subsystems.Streams = std::make_unique(gameClient); return subsystems; } diff --git a/xbmc/games/addons/input/GameClientInput.cpp b/xbmc/games/addons/input/GameClientInput.cpp index 0ae30d26f8564..95fcfef91d013 100644 --- a/xbmc/games/addons/input/GameClientInput.cpp +++ b/xbmc/games/addons/input/GameClientInput.cpp @@ -94,7 +94,7 @@ void CGameClientInput::Start(IGameInputCallback* input) } // Ensure hardware is open to receive events - m_hardware.reset(new CGameClientHardware(m_gameClient)); + m_hardware = std::make_unique(m_gameClient); } // Notify observers of the initial port configuration @@ -238,7 +238,7 @@ void CGameClientInput::LoadTopology() if (hardwarePorts.empty()) hardwarePorts.emplace_back(new CGameClientPort(GetControllers(m_gameClient))); - m_topology.reset(new CGameClientTopology(std::move(hardwarePorts), playerLimit)); + m_topology = std::make_unique(std::move(hardwarePorts), playerLimit); } void CGameClientInput::ActivateControllers(CControllerHub& hub) @@ -264,7 +264,8 @@ void CGameClientInput::SetControllerLayouts(const ControllerVector& controllers) { const std::string controllerId = controller->ID(); if (m_controllerLayouts.find(controllerId) == m_controllerLayouts.end()) - m_controllerLayouts[controllerId].reset(new CGameClientController(*this, controller)); + m_controllerLayouts[controllerId] = + std::make_unique(*this, controller); } std::vector controllerStructs; diff --git a/xbmc/games/addons/streams/GameClientStreams.cpp b/xbmc/games/addons/streams/GameClientStreams.cpp index 7d005ea62f43b..b7936d46dd001 100644 --- a/xbmc/games/addons/streams/GameClientStreams.cpp +++ b/xbmc/games/addons/streams/GameClientStreams.cpp @@ -97,17 +97,17 @@ std::unique_ptr CGameClientStreams::CreateStream( { case GAME_STREAM_AUDIO: { - gameStream.reset(new CGameClientStreamAudio(m_gameClient.GetSampleRate())); + gameStream = std::make_unique(m_gameClient.GetSampleRate()); break; } case GAME_STREAM_VIDEO: { - gameStream.reset(new CGameClientStreamVideo); + gameStream = std::make_unique(); break; } case GAME_STREAM_SW_FRAMEBUFFER: { - gameStream.reset(new CGameClientStreamSwFramebuffer); + gameStream = std::make_unique(); break; } default: diff --git a/xbmc/games/controllers/types/ControllerNode.cpp b/xbmc/games/controllers/types/ControllerNode.cpp index 701b2d1ba6b6d..c3714744659d2 100644 --- a/xbmc/games/controllers/types/ControllerNode.cpp +++ b/xbmc/games/controllers/types/ControllerNode.cpp @@ -14,6 +14,7 @@ #include "games/ports/types/PortNode.h" #include +#include #include using namespace KODI; @@ -32,7 +33,7 @@ CControllerNode& CControllerNode::operator=(const CControllerNode& rhs) m_controller = rhs.m_controller; m_portAddress = rhs.m_portAddress; m_controllerAddress = rhs.m_controllerAddress; - m_hub.reset(new CControllerHub(*rhs.m_hub)); + m_hub = std::make_unique(*rhs.m_hub); } return *this; @@ -56,7 +57,7 @@ void CControllerNode::Clear() m_controller.reset(); m_portAddress.clear(); m_controllerAddress.clear(); - m_hub.reset(new CControllerHub); + m_hub = std::make_unique(); } void CControllerNode::SetController(ControllerPtr controller) @@ -94,7 +95,7 @@ void CControllerNode::SetControllerAddress(std::string controllerAddress) void CControllerNode::SetHub(CControllerHub hub) { - m_hub.reset(new CControllerHub(std::move(hub))); + m_hub = std::make_unique(std::move(hub)); } bool CControllerNode::IsControllerAccepted(const std::string& controllerId) const diff --git a/xbmc/games/ports/input/PortInput.cpp b/xbmc/games/ports/input/PortInput.cpp index ccfe3093ee86a..253567981c3fd 100644 --- a/xbmc/games/ports/input/PortInput.cpp +++ b/xbmc/games/ports/input/PortInput.cpp @@ -15,6 +15,8 @@ #include "input/joysticks/keymaps/KeymapHandling.h" #include "peripherals/devices/Peripheral.h" +#include + using namespace KODI; using namespace GAME; @@ -37,7 +39,7 @@ void CPortInput::RegisterInput(JOYSTICK::IInputProvider* provider) provider->RegisterInputHandler(this, false); // Register GUI input - m_appInput.reset(new JOYSTICK::CKeymapHandling(provider, false, this)); + m_appInput = std::make_unique(provider, false, this); } void CPortInput::UnregisterInput(JOYSTICK::IInputProvider* provider) diff --git a/xbmc/guilib/GUIBaseContainer.cpp b/xbmc/guilib/GUIBaseContainer.cpp index 237ced88a8f5c..3db7db9364ed0 100644 --- a/xbmc/guilib/GUIBaseContainer.cpp +++ b/xbmc/guilib/GUIBaseContainer.cpp @@ -26,6 +26,8 @@ #include "utils/XBMCTinyXML.h" #include "utils/log.h" +#include + #define HOLD_TIME_START 100 #define HOLD_TIME_END 3000 #define SCROLLING_GAP 200U diff --git a/xbmc/guilib/GUIComponent.cpp b/xbmc/guilib/GUIComponent.cpp index e143e7557ef55..8e908856e3b36 100644 --- a/xbmc/guilib/GUIComponent.cpp +++ b/xbmc/guilib/GUIComponent.cpp @@ -19,15 +19,17 @@ #include "URL.h" #include "dialogs/GUIDialogYesNo.h" +#include + CGUIComponent::CGUIComponent() { - m_pWindowManager.reset(new CGUIWindowManager()); - m_pTextureManager.reset(new CGUITextureManager()); - m_pLargeTextureManager.reset(new CGUILargeTextureManager()); - m_stereoscopicsManager.reset(new CStereoscopicsManager()); - m_guiInfoManager.reset(new CGUIInfoManager()); - m_guiColorManager.reset(new CGUIColorManager()); - m_guiAudioManager.reset(new CGUIAudioManager()); + m_pWindowManager = std::make_unique(); + m_pTextureManager = std::make_unique(); + m_pLargeTextureManager = std::make_unique(); + m_stereoscopicsManager = std::make_unique(); + m_guiInfoManager = std::make_unique(); + m_guiColorManager = std::make_unique(); + m_guiAudioManager = std::make_unique(); } CGUIComponent::~CGUIComponent() diff --git a/xbmc/guilib/GUIVisualisationControl.cpp b/xbmc/guilib/GUIVisualisationControl.cpp index 0399edde0c825..fb02bc9f18a37 100644 --- a/xbmc/guilib/GUIVisualisationControl.cpp +++ b/xbmc/guilib/GUIVisualisationControl.cpp @@ -31,6 +31,8 @@ #include "utils/URIUtils.h" #include "utils/log.h" +#include + namespace { constexpr unsigned int MAX_AUDIO_BUFFERS = 16; @@ -386,7 +388,7 @@ bool CGUIVisualisationControl::InitVisualization() if (y + h > context.GetHeight()) h = context.GetHeight() - y; - m_instance.reset(new KODI::ADDONS::CVisualization(addonBase, x, y, w, h)); + m_instance = std::make_unique(addonBase, x, y, w, h); CreateBuffers(); m_alreadyStarted = false; diff --git a/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp b/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp index 6115517fdb43e..ff6b9e28772cf 100644 --- a/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/PicturesGUIInfo.cpp @@ -22,6 +22,7 @@ #include "utils/log.h" #include +#include using namespace KODI::GUILIB::GUIINFO; @@ -99,7 +100,7 @@ void CPicturesGUIInfo::SetCurrentSlide(CFileItem *item) if (!tag->Loaded()) // If picture metadata has not been loaded yet, load it now tag->Load(item->GetPath()); } - m_currentSlide.reset(new CFileItem(*item)); + m_currentSlide = std::make_unique(*item); } else if (m_currentSlide) { diff --git a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp index dfb9455aa22e1..730159f809899 100644 --- a/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp +++ b/xbmc/guilib/guiinfo/PlayerGUIInfo.cpp @@ -33,6 +33,7 @@ #include #include +#include using namespace KODI::GUILIB::GUIINFO; @@ -147,7 +148,7 @@ bool CPlayerGUIInfo::InitCurrentItem(CFileItem *item) if (item && m_appPlayer->IsPlaying()) { CLog::Log(LOGDEBUG, "CPlayerGUIInfo::InitCurrentItem({})", CURL::GetRedacted(item->GetPath())); - m_currentItem.reset(new CFileItem(*item)); + m_currentItem = std::make_unique(*item); } else { diff --git a/xbmc/input/joysticks/generic/ButtonMapping.cpp b/xbmc/input/joysticks/generic/ButtonMapping.cpp index 8ecfb83c23663..6b4cd5cfa940f 100644 --- a/xbmc/input/joysticks/generic/ButtonMapping.cpp +++ b/xbmc/input/joysticks/generic/ButtonMapping.cpp @@ -25,6 +25,7 @@ #include #include #include +#include using namespace KODI; using namespace JOYSTICK; @@ -571,7 +572,7 @@ CMouseButtonDetector& CButtonMapping::GetMouseButton(MOUSE::BUTTON_ID buttonInde CPointerDetector& CButtonMapping::GetPointer() { if (!m_pointer) - m_pointer.reset(new CPointerDetector(this)); + m_pointer = std::make_unique(this); return *m_pointer; } diff --git a/xbmc/input/joysticks/keymaps/KeymapHandler.cpp b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp index 24e2a0b300c6b..51f5e5f076b8a 100644 --- a/xbmc/input/joysticks/keymaps/KeymapHandler.cpp +++ b/xbmc/input/joysticks/keymaps/KeymapHandler.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include using namespace KODI; @@ -33,7 +34,7 @@ CKeymapHandler::CKeymapHandler(IActionListener* actionHandler, const IKeymap* ke assert(m_keymap != nullptr); if (m_keymap->Environment()->UseEasterEgg()) - m_easterEgg.reset(new CJoystickEasterEgg(ControllerID())); + m_easterEgg = std::make_unique(ControllerID()); } bool CKeymapHandler::HotkeysPressed(const std::set& keyNames) const diff --git a/xbmc/interfaces/legacy/Dialog.cpp b/xbmc/interfaces/legacy/Dialog.cpp index 6dd00f179bf7c..55812f9099695 100644 --- a/xbmc/interfaces/legacy/Dialog.cpp +++ b/xbmc/interfaces/legacy/Dialog.cpp @@ -33,6 +33,8 @@ #include "utils/log.h" #include "video/dialogs/GUIDialogVideoInfo.h" +#include + using namespace KODI::MESSAGING; #define ACTIVE_WINDOW CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() @@ -171,7 +173,7 @@ namespace XBMCAddon pDialog->Open(); if (pDialog->IsConfirmed()) - return std::unique_ptr>(new std::vector(pDialog->GetSelectedItems())); + return std::make_unique>(pDialog->GetSelectedItems()); else return std::unique_ptr>(); } diff --git a/xbmc/peripherals/addons/AddonButtonMapping.cpp b/xbmc/peripherals/addons/AddonButtonMapping.cpp index af838aaf7f0b6..68e324065bd0e 100644 --- a/xbmc/peripherals/addons/AddonButtonMapping.cpp +++ b/xbmc/peripherals/addons/AddonButtonMapping.cpp @@ -14,6 +14,8 @@ #include "peripherals/addons/AddonButtonMap.h" #include "utils/log.h" +#include + using namespace KODI; using namespace JOYSTICK; using namespace PERIPHERALS; @@ -31,11 +33,11 @@ CAddonButtonMapping::CAddonButtonMapping(CPeripherals& manager, else { const std::string controllerId = mapper->ControllerID(); - m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, controllerId)); + m_buttonMap = std::make_unique(peripheral, addon, controllerId); if (m_buttonMap->Load()) { IKeymap* keymap = peripheral->GetKeymap(controllerId); - m_buttonMapping.reset(new CButtonMapping(mapper, m_buttonMap.get(), keymap)); + m_buttonMapping = std::make_unique(mapper, m_buttonMap.get(), keymap); // Allow the mapper to save our button map mapper->SetButtonMapCallback(peripheral->Location(), this); diff --git a/xbmc/peripherals/addons/AddonInputHandling.cpp b/xbmc/peripherals/addons/AddonInputHandling.cpp index 7ae7208a646bc..270c85de1c620 100644 --- a/xbmc/peripherals/addons/AddonInputHandling.cpp +++ b/xbmc/peripherals/addons/AddonInputHandling.cpp @@ -20,6 +20,8 @@ #include "peripherals/addons/AddonButtonMap.h" #include "utils/log.h" +#include + using namespace KODI; using namespace JOYSTICK; using namespace PERIPHERALS; @@ -37,14 +39,14 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, } else if (!handler->ControllerID().empty()) { - m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); + m_buttonMap = std::make_unique(peripheral, addon, handler->ControllerID()); if (m_buttonMap->Load()) { - m_driverHandler.reset(new CInputHandling(handler, m_buttonMap.get())); + m_driverHandler = std::make_unique(handler, m_buttonMap.get()); if (receiver) { - m_inputReceiver.reset(new CDriverReceiving(receiver, m_buttonMap.get())); + m_inputReceiver = std::make_unique(receiver, m_buttonMap.get()); // Interfaces are connected here because they share button map as a common resource handler->SetInputReceiver(m_inputReceiver.get()); @@ -69,10 +71,11 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, } else if (!handler->ControllerID().empty()) { - m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); + m_buttonMap = std::make_unique(peripheral, addon, handler->ControllerID()); if (m_buttonMap->Load()) { - m_keyboardHandler.reset(new KEYBOARD::CKeyboardInputHandling(handler, m_buttonMap.get())); + m_keyboardHandler = + std::make_unique(handler, m_buttonMap.get()); } else { @@ -93,10 +96,10 @@ CAddonInputHandling::CAddonInputHandling(CPeripherals& manager, } else if (!handler->ControllerID().empty()) { - m_buttonMap.reset(new CAddonButtonMap(peripheral, addon, handler->ControllerID())); + m_buttonMap = std::make_unique(peripheral, addon, handler->ControllerID()); if (m_buttonMap->Load()) { - m_mouseHandler.reset(new MOUSE::CMouseInputHandling(handler, m_buttonMap.get())); + m_mouseHandler = std::make_unique(handler, m_buttonMap.get()); } else { diff --git a/xbmc/peripherals/devices/PeripheralJoystick.cpp b/xbmc/peripherals/devices/PeripheralJoystick.cpp index 4033e94f5fc21..0d1e1eaedab00 100644 --- a/xbmc/peripherals/devices/PeripheralJoystick.cpp +++ b/xbmc/peripherals/devices/PeripheralJoystick.cpp @@ -29,6 +29,7 @@ #include "utils/log.h" #include +#include #include using namespace KODI; @@ -105,9 +106,9 @@ bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature) } // Give joystick monitor priority over default controller - m_appInput.reset( - new CKeymapHandling(this, false, m_manager.GetInputManager().KeymapEnvironment())); - m_joystickMonitor.reset(new CJoystickMonitor); + m_appInput = std::make_unique( + this, false, m_manager.GetInputManager().KeymapEnvironment()); + m_joystickMonitor = std::make_unique(); RegisterInputHandler(m_joystickMonitor.get(), false); } } @@ -126,7 +127,7 @@ bool CPeripheralJoystick::InitialiseFeature(const PeripheralFeature feature) void CPeripheralJoystick::InitializeDeadzoneFiltering(IButtonMap& buttonMap) { - m_deadzoneFilter.reset(new CDeadzoneFilter(&buttonMap, this)); + m_deadzoneFilter = std::make_unique(&buttonMap, this); } void CPeripheralJoystick::InitializeControllerProfile(IButtonMap& buttonMap) diff --git a/xbmc/pictures/GUIWindowSlideShow.cpp b/xbmc/pictures/GUIWindowSlideShow.cpp index ba56dad85afb8..29ae02f20fedf 100644 --- a/xbmc/pictures/GUIWindowSlideShow.cpp +++ b/xbmc/pictures/GUIWindowSlideShow.cpp @@ -405,7 +405,7 @@ void CGUIWindowSlideShow::Process(unsigned int currentTime, CDirtyRegionList &re // Create our background loader if necessary if (!m_pBackgroundLoader) { - m_pBackgroundLoader.reset(new CBackgroundPicLoader()); + m_pBackgroundLoader = std::make_unique(); m_pBackgroundLoader->Create(this); } diff --git a/xbmc/platform/android/activity/JNIXBMCFile.cpp b/xbmc/platform/android/activity/JNIXBMCFile.cpp index 0619490932669..efff89a64e517 100644 --- a/xbmc/platform/android/activity/JNIXBMCFile.cpp +++ b/xbmc/platform/android/activity/JNIXBMCFile.cpp @@ -12,6 +12,8 @@ #include "utils/FileUtils.h" #include "utils/log.h" +#include + #include #define BUFFSIZE 8192 @@ -53,7 +55,7 @@ jboolean CJNIXBMCFile::_open(JNIEnv *env, jobject thiz, jstring path) return false; CJNIXBMCFile* file = new CJNIXBMCFile(); - file->m_file.reset(new XFILE::CFile()); + file->m_file = std::make_unique(); bool ret = file->m_file->Open(strPath); if (!ret) { diff --git a/xbmc/platform/android/activity/XBMCApp.cpp b/xbmc/platform/android/activity/XBMCApp.cpp index e4fa080a9ef99..b04d63fb29c69 100644 --- a/xbmc/platform/android/activity/XBMCApp.cpp +++ b/xbmc/platform/android/activity/XBMCApp.cpp @@ -11,25 +11,58 @@ #include "AndroidKey.h" #include "CompileInfo.h" #include "FileItem.h" +// Audio Engine includes for Factory and interfaces +#include "GUIInfoManager.h" +#include "ServiceBroker.h" +#include "TextureCache.h" #include "application/AppEnvironment.h" #include "application/AppParams.h" #include "application/Application.h" #include "application/ApplicationComponents.h" #include "application/ApplicationPlayer.h" #include "application/ApplicationPowerHandling.h" +#include "cores/AudioEngine/AESinkFactory.h" +#include "cores/AudioEngine/Interfaces/AE.h" +#include "cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h" +#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/VideoDatabaseFile.h" +#include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" +#include "guilib/guiinfo/GUIInfoLabels.h" +#include "input/Key.h" +#include "input/mouse/MouseStat.h" #include "interfaces/AnnouncementManager.h" #include "messaging/ApplicationMessenger.h" +#include "platform/xbmc.h" +#include "powermanagement/PowerManager.h" #include "settings/AdvancedSettings.h" #include "settings/DisplaySettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" +#include "threads/Event.h" +#include "utils/StringUtils.h" +#include "utils/TimeUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" +#include "video/VideoInfoTag.h" #include "windowing/GraphicContext.h" +#include "windowing/WinEvents.h" +#include "windowing/android/VideoSyncAndroid.h" +#include "windowing/android/WinSystemAndroid.h" + +#include "platform/android/activity/IInputDeviceCallbacks.h" +#include "platform/android/activity/IInputDeviceEventHandler.h" +#include "platform/android/network/NetworkAndroid.h" +#include "platform/android/powermanagement/AndroidPowerSyscall.h" +#include #include #include #include #include +#include #include #include @@ -70,37 +103,6 @@ #include #include #include -// Audio Engine includes for Factory and interfaces -#include "GUIInfoManager.h" -#include "ServiceBroker.h" -#include "TextureCache.h" -#include "cores/AudioEngine/AESinkFactory.h" -#include "cores/AudioEngine/Interfaces/AE.h" -#include "cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h" -#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" -#include "filesystem/SpecialProtocol.h" -#include "filesystem/VideoDatabaseFile.h" -#include "guilib/GUIComponent.h" -#include "guilib/GUIWindowManager.h" -#include "guilib/guiinfo/GUIInfoLabels.h" -#include "input/Key.h" -#include "input/mouse/MouseStat.h" -#include "platform/xbmc.h" -#include "powermanagement/PowerManager.h" -#include "utils/StringUtils.h" -#include "utils/TimeUtils.h" -#include "utils/URIUtils.h" -#include "utils/Variant.h" -#include "utils/log.h" -#include "video/VideoInfoTag.h" -#include "windowing/WinEvents.h" -#include "windowing/android/VideoSyncAndroid.h" -#include "windowing/android/WinSystemAndroid.h" - -#include "platform/android/activity/IInputDeviceCallbacks.h" -#include "platform/android/activity/IInputDeviceEventHandler.h" -#include "platform/android/network/NetworkAndroid.h" -#include "platform/android/powermanagement/AndroidPowerSyscall.h" #define GIGABYTES 1073741824 @@ -174,7 +176,7 @@ CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity, IInputHandler& inputHandler) exit(1); return; } - m_mainView.reset(new CJNIXBMCMainView(this)); + m_mainView = std::make_unique(this); m_hdmiSource = CJNISystemProperties::get("ro.hdmi.device_type", "") == "4"; android_printf("CXBMCApp: Created"); @@ -674,9 +676,6 @@ bool CXBMCApp::SetBuffersGeometry(int width, int height, int format) return false; } -#include "threads/Event.h" -#include - void CXBMCApp::SetRefreshRateCallback(void* rateVariant) { CVariant* rateV = static_cast(rateVariant); diff --git a/xbmc/platform/linux/input/LibInputHandler.cpp b/xbmc/platform/linux/input/LibInputHandler.cpp index a8c39f5b914d1..bce9f78dc1bec 100644 --- a/xbmc/platform/linux/input/LibInputHandler.cpp +++ b/xbmc/platform/linux/input/LibInputHandler.cpp @@ -17,6 +17,7 @@ #include "utils/log.h" #include +#include #include #include @@ -93,10 +94,10 @@ CLibInputHandler::CLibInputHandler() : CThread("libinput") m_liFd = libinput_get_fd(m_li); - m_keyboard.reset(new CLibInputKeyboard()); - m_pointer.reset(new CLibInputPointer()); - m_touch.reset(new CLibInputTouch()); - m_settings.reset(new CLibInputSettings(this)); + m_keyboard = std::make_unique(); + m_pointer = std::make_unique(); + m_touch = std::make_unique(); + m_settings = std::make_unique(this); CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this); } diff --git a/xbmc/powermanagement/PowerManager.cpp b/xbmc/powermanagement/PowerManager.cpp index 3c30817331431..fb04187763628 100644 --- a/xbmc/powermanagement/PowerManager.cpp +++ b/xbmc/powermanagement/PowerManager.cpp @@ -259,7 +259,7 @@ void CPowerManager::StorePlayerState() if (appPlayer->IsPlaying()) { m_lastUsedPlayer = appPlayer->GetCurrentPlayer(); - m_lastPlayedFileItem.reset(new CFileItem(g_application.CurrentFileItem())); + m_lastPlayedFileItem = std::make_unique(g_application.CurrentFileItem()); // set the actual offset instead of store and load it from database m_lastPlayedFileItem->SetStartOffset(appPlayer->GetTime()); // in case of regular stack, correct the start offset by adding current part start time diff --git a/xbmc/pvr/PVRManager.cpp b/xbmc/pvr/PVRManager.cpp index 9dc0d7c120f4f..c8bc52c502642 100644 --- a/xbmc/pvr/PVRManager.cpp +++ b/xbmc/pvr/PVRManager.cpp @@ -354,8 +354,8 @@ void CPVRManager::ResetProperties() m_channelGroups = std::make_shared(); m_recordings = std::make_shared(); m_timers = std::make_shared(); - m_guiInfo.reset(new CPVRGUIInfo); - m_parentalTimer.reset(new CStopWatch); + m_guiInfo = std::make_unique(); + m_parentalTimer = std::make_unique(); m_knownClients.clear(); } diff --git a/xbmc/pvr/PVRPlaybackState.cpp b/xbmc/pvr/PVRPlaybackState.cpp index df63e1130eb92..aa3d0aefa276e 100644 --- a/xbmc/pvr/PVRPlaybackState.cpp +++ b/xbmc/pvr/PVRPlaybackState.cpp @@ -31,6 +31,7 @@ #include "utils/log.h" #include +#include #include using namespace PVR; @@ -186,8 +187,8 @@ void CPVRPlaybackState::OnPlaybackStarted(const CFileItem& item) if (m_lastWatchedUpdateTimer) m_lastWatchedUpdateTimer->Stop(true); - m_lastWatchedUpdateTimer.reset( - new CLastWatchedUpdateTimer(*this, channel, CDateTime::GetUTCDateTime())); + m_lastWatchedUpdateTimer = + std::make_unique(*this, channel, CDateTime::GetUTCDateTime()); m_lastWatchedUpdateTimer->Start(std::chrono::milliseconds(iLastWatchedDelay)); } else diff --git a/xbmc/pvr/addons/PVRClientCapabilities.cpp b/xbmc/pvr/addons/PVRClientCapabilities.cpp index 8650465ce8adc..7c09fd55ff792 100644 --- a/xbmc/pvr/addons/PVRClientCapabilities.cpp +++ b/xbmc/pvr/addons/PVRClientCapabilities.cpp @@ -13,20 +13,21 @@ #include #include +#include using namespace PVR; CPVRClientCapabilities::CPVRClientCapabilities(const CPVRClientCapabilities& other) { if (other.m_addonCapabilities) - m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(*other.m_addonCapabilities)); + m_addonCapabilities = std::make_unique(*other.m_addonCapabilities); InitRecordingsLifetimeValues(); } const CPVRClientCapabilities& CPVRClientCapabilities::operator=(const CPVRClientCapabilities& other) { if (other.m_addonCapabilities) - m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(*other.m_addonCapabilities)); + m_addonCapabilities = std::make_unique(*other.m_addonCapabilities); InitRecordingsLifetimeValues(); return *this; } @@ -34,7 +35,7 @@ const CPVRClientCapabilities& CPVRClientCapabilities::operator=(const CPVRClient const CPVRClientCapabilities& CPVRClientCapabilities::operator=( const PVR_ADDON_CAPABILITIES& addonCapabilities) { - m_addonCapabilities.reset(new PVR_ADDON_CAPABILITIES(addonCapabilities)); + m_addonCapabilities = std::make_unique(addonCapabilities); InitRecordingsLifetimeValues(); return *this; } diff --git a/xbmc/pvr/addons/PVRClientMenuHooks.cpp b/xbmc/pvr/addons/PVRClientMenuHooks.cpp index 12f150a1c558e..9d58f1a915dba 100644 --- a/xbmc/pvr/addons/PVRClientMenuHooks.cpp +++ b/xbmc/pvr/addons/PVRClientMenuHooks.cpp @@ -13,6 +13,8 @@ #include "pvr/PVRContextMenus.h" #include "utils/log.h" +#include + namespace PVR { @@ -100,7 +102,7 @@ std::string CPVRClientMenuHook::GetLabel() const void CPVRClientMenuHooks::AddHook(const PVR_MENUHOOK& addonHook) { if (!m_hooks) - m_hooks.reset(new std::vector()); + m_hooks = std::make_unique>(); const CPVRClientMenuHook hook(m_addonId, addonHook); m_hooks->emplace_back(hook); diff --git a/xbmc/pvr/epg/EpgContainer.cpp b/xbmc/pvr/epg/EpgContainer.cpp index fa8865078d1bd..b7f12c40012c2 100644 --- a/xbmc/pvr/epg/EpgContainer.cpp +++ b/xbmc/pvr/epg/EpgContainer.cpp @@ -741,8 +741,8 @@ bool CPVREpgContainer::UpdateEPG(bool bOnlyPending /* = false */) std::unique_ptr progressHandler; if (bShowProgress && !bOnlyPending && !epgsToUpdate.empty()) - progressHandler.reset( - new CPVRGUIProgressHandler(g_localizeStrings.Get(19004))); // Loading programme guide + progressHandler = std::make_unique( + g_localizeStrings.Get(19004)); // Loading programme guide for (const auto& epgEntry : epgsToUpdate) { diff --git a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp index a239fe2a92582..26859dd63aa21 100644 --- a/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp +++ b/xbmc/pvr/guilib/PVRGUIChannelIconUpdater.cpp @@ -25,6 +25,7 @@ #include "utils/log.h" #include +#include #include #include @@ -57,8 +58,8 @@ void CPVRGUIChannelIconUpdater::SearchAndUpdateMissingChannelIcons() const std::unique_ptr progressHandler; if (!m_groups.empty()) - progressHandler.reset( - new CPVRGUIProgressHandler(g_localizeStrings.Get(19286))); // Searching for channel icons + progressHandler = std::make_unique( + g_localizeStrings.Get(19286)); // Searching for channel icons for (const auto& group : m_groups) { diff --git a/xbmc/pvr/recordings/PVRRecordings.cpp b/xbmc/pvr/recordings/PVRRecordings.cpp index 2e6edede6383e..9f11b36b47c18 100644 --- a/xbmc/pvr/recordings/PVRRecordings.cpp +++ b/xbmc/pvr/recordings/PVRRecordings.cpp @@ -329,7 +329,7 @@ CVideoDatabase& CPVRRecordings::GetVideoDatabase() { if (!m_database) { - m_database.reset(new CVideoDatabase()); + m_database = std::make_unique(); m_database->Open(); if (!m_database->IsOpen()) diff --git a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp index 9178e85ec206d..631ff91f8bebe 100644 --- a/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp +++ b/xbmc/pvr/timers/PVRTimerRuleMatcher.cpp @@ -14,6 +14,8 @@ #include "pvr/timers/PVRTimerInfoTag.h" #include "utils/RegExp.h" +#include + using namespace PVR; CPVRTimerRuleMatcher::CPVRTimerRuleMatcher(const std::shared_ptr& timerRule, @@ -171,7 +173,7 @@ bool CPVRTimerRuleMatcher::MatchSearchText(const std::shared_ptr { if (!m_textSearch) { - m_textSearch.reset(new CRegExp(true /* case insensitive */)); + m_textSearch = std::make_unique(true /* case insensitive */); m_textSearch->RegComp(m_timerRule->EpgSearchString()); } return m_textSearch->RegFind(epgTag->Title()) >= 0 || @@ -183,7 +185,7 @@ bool CPVRTimerRuleMatcher::MatchSearchText(const std::shared_ptr { if (!m_textSearch) { - m_textSearch.reset(new CRegExp(true /* case insensitive */)); + m_textSearch = std::make_unique(true /* case insensitive */); m_textSearch->RegComp(m_timerRule->EpgSearchString()); } return m_textSearch->RegFind(epgTag->Title()) >= 0; diff --git a/xbmc/pvr/windows/GUIWindowPVRBase.cpp b/xbmc/pvr/windows/GUIWindowPVRBase.cpp index aa11d866f6b9c..df65926ac5059 100644 --- a/xbmc/pvr/windows/GUIWindowPVRBase.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRBase.cpp @@ -246,7 +246,7 @@ void CGUIWindowPVRBase::ClearData() { std::unique_lock lock(m_critSection); m_channelGroup.reset(); - m_channelGroupsSelector.reset(new CGUIPVRChannelGroupsSelector); + m_channelGroupsSelector = std::make_unique(); } void CGUIWindowPVRBase::OnInitWindow() diff --git a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp index f4c247f9106bd..feb9e013dbd67 100644 --- a/xbmc/pvr/windows/GUIWindowPVRGuide.cpp +++ b/xbmc/pvr/windows/GUIWindowPVRGuide.cpp @@ -144,7 +144,7 @@ void CGUIWindowPVRGuideBase::OnDeinitWindow(int nextWindowID) void CGUIWindowPVRGuideBase::StartRefreshTimelineItemsThread() { StopRefreshTimelineItemsThread(); - m_refreshTimelineItemsThread.reset(new CPVRRefreshTimelineItemsThread(this)); + m_refreshTimelineItemsThread = std::make_unique(this); m_refreshTimelineItemsThread->Create(); } diff --git a/xbmc/rendering/RenderSystem.cpp b/xbmc/rendering/RenderSystem.cpp index 3f10562e7b57a..718f06132db5f 100644 --- a/xbmc/rendering/RenderSystem.cpp +++ b/xbmc/rendering/RenderSystem.cpp @@ -15,6 +15,8 @@ #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" +#include + CRenderSystemBase::CRenderSystemBase() { m_bRenderCreated = false; @@ -62,8 +64,10 @@ void CRenderSystemBase::ShowSplash(const std::string& message) if (!m_splashImage) { - m_splashImage = std::unique_ptr(new CGUIImage(0, 0, 0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), - CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), CTextureInfo(CUtil::GetSplashPath()))); + m_splashImage = std::make_unique( + 0, 0, 0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), + CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), + CTextureInfo(CUtil::GetSplashPath())); m_splashImage->SetAspectRatio(CAspectRatio::AR_SCALE); } @@ -86,7 +90,7 @@ void CRenderSystemBase::ShowSplash(const std::string& message) { auto messageFont = g_fontManager.LoadTTF("__splash__", "arial.ttf", 0xFFFFFFFF, 0, 20, FONT_STYLE_NORMAL, false, 1.0f, 1.0f, &res); if (messageFont) - m_splashMessageLayout = std::unique_ptr(new CGUITextLayout(messageFont, true, 0)); + m_splashMessageLayout = std::make_unique(messageFont, true, 0); } if (m_splashMessageLayout) diff --git a/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp b/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp index 022611bece3d6..257bad601b84a 100644 --- a/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp +++ b/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp @@ -14,6 +14,7 @@ #include "utils/Screenshot.h" #include "windowing/GraphicContext.h" +#include #include #include @@ -26,7 +27,7 @@ void CScreenshotSurfaceGL::Register() std::unique_ptr CScreenshotSurfaceGL::CreateSurface() { - return std::unique_ptr(new CScreenshotSurfaceGL()); + return std::make_unique(); } bool CScreenshotSurfaceGL::Capture() diff --git a/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp b/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp index 3c290ec25c661..d25da6cd65e17 100644 --- a/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp +++ b/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp @@ -14,6 +14,7 @@ #include "utils/Screenshot.h" #include "windowing/GraphicContext.h" +#include #include #include @@ -26,7 +27,7 @@ void CScreenshotSurfaceGLES::Register() std::unique_ptr CScreenshotSurfaceGLES::CreateSurface() { - return std::unique_ptr(new CScreenshotSurfaceGLES()); + return std::make_unique(); } bool CScreenshotSurfaceGLES::Capture() diff --git a/xbmc/threads/Event.cpp b/xbmc/threads/Event.cpp index 1c67f4eac479c..9ec7f3f1c0ae2 100644 --- a/xbmc/threads/Event.cpp +++ b/xbmc/threads/Event.cpp @@ -12,6 +12,7 @@ #include #include +#include #include using namespace std::chrono_literals; @@ -20,7 +21,7 @@ void CEvent::addGroup(XbmcThreads::CEventGroup* group) { std::unique_lock lock(groupListMutex); if (!groups) - groups.reset(new std::vector); + groups = std::make_unique>(); groups->push_back(group); } diff --git a/xbmc/video/tags/VideoTagLoaderPlugin.cpp b/xbmc/video/tags/VideoTagLoaderPlugin.cpp index 407e0ee4f0e6d..f3b97ee608711 100644 --- a/xbmc/video/tags/VideoTagLoaderPlugin.cpp +++ b/xbmc/video/tags/VideoTagLoaderPlugin.cpp @@ -12,6 +12,8 @@ #include "URL.h" #include "filesystem/PluginDirectory.h" +#include + using namespace XFILE; CVideoTagLoaderPlugin::CVideoTagLoaderPlugin(const CFileItem& item, bool forceRefresh) @@ -21,10 +23,10 @@ CVideoTagLoaderPlugin::CVideoTagLoaderPlugin(const CFileItem& item, bool forceRe return; // Preserve CFileItem video info and art to avoid info loss between creating VideoInfoTagLoaderFactory and calling Load() if (m_item.HasVideoInfoTag()) - m_tag.reset(new CVideoInfoTag(*m_item.GetVideoInfoTag())); + m_tag = std::make_unique(*m_item.GetVideoInfoTag()); auto& art = item.GetArt(); if (!art.empty()) - m_art.reset(new CGUIListItem::ArtMap(art)); + m_art = std::make_unique(art); } bool CVideoTagLoaderPlugin::HasInfo() const @@ -48,7 +50,7 @@ CInfoScanner::INFO_TYPE CVideoTagLoaderPlugin::Load(CVideoInfoTag& tag, bool, st if (!items.IsEmpty()) { const CFileItemPtr &item = items[0]; - m_art.reset(new CGUIListItem::ArtMap(item->GetArt())); + m_art = std::make_unique(item->GetArt()); if (item->HasVideoInfoTag()) { tag = *item->GetVideoInfoTag(); diff --git a/xbmc/windowing/WinSystem.cpp b/xbmc/windowing/WinSystem.cpp index 33492876dca6f..aa8cbde021bec 100644 --- a/xbmc/windowing/WinSystem.cpp +++ b/xbmc/windowing/WinSystem.cpp @@ -10,6 +10,9 @@ #include "ServiceBroker.h" #include "guilib/DispResource.h" +#if HAS_GLES +#include "guilib/GUIFontTTFGL.h" +#endif #include "powermanagement/DPMSSupport.h" #include "settings/DisplaySettings.h" #include "settings/Settings.h" @@ -18,13 +21,14 @@ #include "utils/StringUtils.h" #include "windowing/GraphicContext.h" +#include #include const char* CWinSystemBase::SETTING_WINSYSTEM_IS_HDR_DISPLAY = "winsystem.ishdrdisplay"; CWinSystemBase::CWinSystemBase() { - m_gfxContext.reset(new CGraphicContext()); + m_gfxContext = std::make_unique(); } CWinSystemBase::~CWinSystemBase() = default; @@ -255,7 +259,8 @@ KODI::WINDOWING::COSScreenSaverManager* CWinSystemBase::GetOSScreenSaver() auto impl = GetOSScreenSaverImpl(); if (impl) { - m_screenSaverManager.reset(new KODI::WINDOWING::COSScreenSaverManager(std::move(impl))); + m_screenSaverManager = + std::make_unique(std::move(impl)); } } diff --git a/xbmc/windowing/X11/WinSystemX11.cpp b/xbmc/windowing/X11/WinSystemX11.cpp index 3cf1020c57fa1..562df661feebb 100644 --- a/xbmc/windowing/X11/WinSystemX11.cpp +++ b/xbmc/windowing/X11/WinSystemX11.cpp @@ -26,6 +26,7 @@ #include "utils/log.h" #include "windowing/GraphicContext.h" +#include #include #include #include @@ -496,7 +497,7 @@ std::unique_ptr CWinSystemX11::GetOSScreenSaver std::unique_ptr ret; if (m_dpy) { - ret.reset(new COSScreenSaverX11(m_dpy)); + ret = std::make_unique(m_dpy); } return ret; } diff --git a/xbmc/windowing/X11/WinSystemX11GLContext.cpp b/xbmc/windowing/X11/WinSystemX11GLContext.cpp index 7b3e9e40fec43..43637af2876b1 100644 --- a/xbmc/windowing/X11/WinSystemX11GLContext.cpp +++ b/xbmc/windowing/X11/WinSystemX11GLContext.cpp @@ -26,6 +26,7 @@ #include "windowing/GraphicContext.h" #include "windowing/WindowSystemFactory.h" +#include #include #include @@ -320,7 +321,7 @@ std::unique_ptr CWinSystemX11GLContext::GetVideoSync(CVideoReference if (dynamic_cast(m_pGLContext)) { - pVSync.reset(new CVideoSyncOML(clock, *this)); + pVSync = std::make_unique(clock, *this); } else { diff --git a/xbmc/windowing/android/WinSystemAndroid.cpp b/xbmc/windowing/android/WinSystemAndroid.cpp index 378968d886559..bd4bae289200c 100644 --- a/xbmc/windowing/android/WinSystemAndroid.cpp +++ b/xbmc/windowing/android/WinSystemAndroid.cpp @@ -24,7 +24,6 @@ #include "settings/DisplaySettings.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" -#include "system_egl.h" #include "utils/HDRCapabilities.h" #include "utils/log.h" #include "windowing/GraphicContext.h" @@ -35,9 +34,12 @@ #include "platform/android/media/drm/MediaDrmCryptoSession.h" #include +#include #include #include +#include "system_egl.h" + #include using namespace KODI; @@ -54,7 +56,7 @@ CWinSystemAndroid::CWinSystemAndroid() m_android = nullptr; - m_winEvents.reset(new CWinEventsAndroid()); + m_winEvents = std::make_unique(); } CWinSystemAndroid::~CWinSystemAndroid() diff --git a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp index 3c08226677872..a4cb02d71043c 100644 --- a/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp +++ b/xbmc/windowing/android/WinSystemAndroidGLESContext.cpp @@ -17,11 +17,14 @@ #include "platform/android/activity/XBMCApp.h" -#include +#include + #include #include "PlatformDefs.h" +#include + void CWinSystemAndroidGLESContext::Register() { KODI::WINDOWING::CWindowSystemFactory::RegisterWindowSystem(CreateWinSystem); @@ -254,7 +257,10 @@ bool CWinSystemAndroidGLESContext::SetHDR(const VideoPicture* videoPicture) CLog::Log(LOGDEBUG, "CWinSystemAndroidGLESContext::SetHDR: ColorSpace: {}", HDRColorSpace); m_HDRColorSpace = HDRColorSpace; - m_displayMetadata = m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr(new AVMasteringDisplayMetadata(videoPicture->displayMetadata)); + m_displayMetadata = + m_HDRColorSpace == EGL_NONE + ? nullptr + : std::make_unique(videoPicture->displayMetadata); // TODO: discuss with NVIDIA why this prevent turning HDR display off //m_lightMetadata = !videoPicture || m_HDRColorSpace == EGL_NONE ? nullptr : std::unique_ptr(new AVContentLightMetadata(videoPicture->lightMetadata)); m_pGLContext.DestroySurface(); diff --git a/xbmc/windowing/ios/WinSystemIOS.mm b/xbmc/windowing/ios/WinSystemIOS.mm index 651d654b75a3d..8935a342a671f 100644 --- a/xbmc/windowing/ios/WinSystemIOS.mm +++ b/xbmc/windowing/ios/WinSystemIOS.mm @@ -36,6 +36,7 @@ #import "platform/darwin/ios/IOSScreenManager.h" #import "platform/darwin/ios/XBMCController.h" +#include #include #include @@ -102,7 +103,7 @@ - (void) runDisplayLink; m_bIsBackgrounded = false; m_pDisplayLink = new CADisplayLinkWrapper; m_pDisplayLink->callbackClass = [[IOSDisplayLinkCallback alloc] init]; - m_winEvents.reset(new CWinEventsIOS()); + m_winEvents = std::make_unique(); CAESinkDARWINIOS::Register(); } diff --git a/xbmc/windowing/osx/WinSystemOSX.mm b/xbmc/windowing/osx/WinSystemOSX.mm index a5b4897b8ea63..2e9f77c2bdac8 100644 --- a/xbmc/windowing/osx/WinSystemOSX.mm +++ b/xbmc/windowing/osx/WinSystemOSX.mm @@ -40,6 +40,7 @@ #include #include +#include #include #import @@ -583,7 +584,7 @@ static void DisplayReconfigured(CGDirectDisplayID display, m_refreshRate = 0.0; m_delayDispReset = false; - m_winEvents.reset(new CWinEventsOSX()); + m_winEvents = std::make_unique(); AE::CAESinkFactory::ClearSinks(); CAESinkDARWINOSX::Register(); diff --git a/xbmc/windowing/tvos/WinSystemTVOS.mm b/xbmc/windowing/tvos/WinSystemTVOS.mm index bd47c6facb199..18546f3cd66e7 100644 --- a/xbmc/windowing/tvos/WinSystemTVOS.mm +++ b/xbmc/windowing/tvos/WinSystemTVOS.mm @@ -140,7 +140,7 @@ - (void)runDisplayLink; m_pDisplayLink = new CADisplayLinkWrapper; m_pDisplayLink->callbackClass = [[TVOSDisplayLinkCallback alloc] init]; - m_winEvents.reset(new CWinEventsTVOS()); + m_winEvents = std::make_unique(); CAESinkDARWINTVOS::Register(); } diff --git a/xbmc/windowing/wayland/InputProcessorKeyboard.cpp b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp index 6ec9ccb9f395c..73c7ba514fe29 100644 --- a/xbmc/windowing/wayland/InputProcessorKeyboard.cpp +++ b/xbmc/windowing/wayland/InputProcessorKeyboard.cpp @@ -12,6 +12,7 @@ #include #include +#include using namespace KODI::WINDOWING::WAYLAND; @@ -44,7 +45,7 @@ void CInputProcessorKeyboard::OnKeyboardKeymap(CSeat* seat, wayland::keyboard_ke if (!m_xkbContext) { // Lazily initialize XkbcommonContext - m_xkbContext.reset(new CXkbcommonContext); + m_xkbContext = std::make_unique(); } m_keymap = m_xkbContext->KeymapFromString(keymap); diff --git a/xbmc/windowing/wayland/SeatInputProcessing.cpp b/xbmc/windowing/wayland/SeatInputProcessing.cpp index 6430aad20c997..66ddc632cc1e2 100644 --- a/xbmc/windowing/wayland/SeatInputProcessing.cpp +++ b/xbmc/windowing/wayland/SeatInputProcessing.cpp @@ -9,6 +9,7 @@ #include "SeatInputProcessing.h" #include +#include using namespace KODI::WINDOWING::WAYLAND; @@ -22,11 +23,11 @@ void CSeatInputProcessing::AddSeat(CSeat* seat) assert(m_seats.find(seat->GetGlobalName()) == m_seats.end()); auto& seatState = m_seats.emplace(seat->GetGlobalName(), seat).first->second; - seatState.keyboardProcessor.reset(new CInputProcessorKeyboard(*this)); + seatState.keyboardProcessor = std::make_unique(*this); seat->AddRawInputHandlerKeyboard(seatState.keyboardProcessor.get()); - seatState.pointerProcessor.reset(new CInputProcessorPointer(m_inputSurface, *this)); + seatState.pointerProcessor = std::make_unique(m_inputSurface, *this); seat->AddRawInputHandlerPointer(seatState.pointerProcessor.get()); - seatState.touchProcessor.reset(new CInputProcessorTouch(m_inputSurface)); + seatState.touchProcessor = std::make_unique(m_inputSurface); seat->AddRawInputHandlerTouch(seatState.touchProcessor.get()); } diff --git a/xbmc/windowing/wayland/SeatInputProcessing.h b/xbmc/windowing/wayland/SeatInputProcessing.h index ce1f0ee759c25..e24d0e3247bc2 100644 --- a/xbmc/windowing/wayland/SeatInputProcessing.h +++ b/xbmc/windowing/wayland/SeatInputProcessing.h @@ -79,7 +79,7 @@ class IInputHandler * Multi-seat support is not currently implemented completely, but each seat has * separate state. */ -class CSeatInputProcessing final : IInputHandlerPointer, IInputHandlerKeyboard +class CSeatInputProcessing final : public IInputHandlerPointer, public IInputHandlerKeyboard { public: /** diff --git a/xbmc/windowing/wayland/WinEventsWayland.cpp b/xbmc/windowing/wayland/WinEventsWayland.cpp index 4345cf00d9fd0..da6e618b5668d 100644 --- a/xbmc/windowing/wayland/WinEventsWayland.cpp +++ b/xbmc/windowing/wayland/WinEventsWayland.cpp @@ -216,7 +216,7 @@ void CWinEventsWayland::SetDisplay(wayland::display_t* display) if (display && !g_WlMessagePump) { // Start message processing as soon as we have a display - g_WlMessagePump.reset(new CWinEventsWaylandThread(*display)); + g_WlMessagePump = std::make_unique(*display); } else if (g_WlMessagePump) { diff --git a/xbmc/windowing/wayland/WinSystemWayland.cpp b/xbmc/windowing/wayland/WinSystemWayland.cpp index 244b4b682079f..e226563a35a9c 100644 --- a/xbmc/windowing/wayland/WinSystemWayland.cpp +++ b/xbmc/windowing/wayland/WinSystemWayland.cpp @@ -47,13 +47,10 @@ #include #include +#include #include #include -#if defined(HAS_DBUS) -# include "windowing/linux/OSScreenSaverFreedesktop.h" -#endif - using namespace KODI::WINDOWING; using namespace KODI::WINDOWING::WAYLAND; using namespace std::placeholders; @@ -139,7 +136,7 @@ struct MsgBufferScale CWinSystemWayland::CWinSystemWayland() : CWinSystemBase{}, m_protocol{"WinSystemWaylandInternal"} { - m_winEvents.reset(new CWinEventsWayland()); + m_winEvents = std::make_unique(); } CWinSystemWayland::~CWinSystemWayland() noexcept @@ -167,7 +164,7 @@ bool CWinSystemWayland::InitWindowSystem() VIDEOPLAYER::CProcessInfoWayland::Register(); RETRO::CRPProcessInfoWayland::Register(); - m_registry.reset(new CRegistry{*m_connection}); + m_registry = std::make_unique(*m_connection); m_registry->RequestSingleton(m_compositor, 1, 4); m_registry->RequestSingleton(m_shm, 1, 1); @@ -279,10 +276,10 @@ bool CWinSystemWayland::CreateNewWindow(const std::string& name, } }; - m_windowDecorator.reset(new CWindowDecorator(*this, *m_connection, m_surface)); + m_windowDecorator = std::make_unique(*this, *m_connection, m_surface); - m_seatInputProcessing.reset(new CSeatInputProcessing(m_surface, *this)); - m_seatRegistry.reset(new CRegistry{*m_connection}); + m_seatInputProcessing = std::make_unique(m_surface, *this); + m_seatRegistry = std::make_unique(*m_connection); // version 2 adds name event -> optional // version 4 adds wl_keyboard repeat_info -> optional // version 5 adds discrete axis events in wl_pointer -> unused diff --git a/xbmc/windowing/wayland/WinSystemWayland.h b/xbmc/windowing/wayland/WinSystemWayland.h index 62720c5e188ff..a2bd12cb4e606 100644 --- a/xbmc/windowing/wayland/WinSystemWayland.h +++ b/xbmc/windowing/wayland/WinSystemWayland.h @@ -45,8 +45,8 @@ class CRegistry; class CWindowDecorator; class CWinSystemWayland : public CWinSystemBase, - IInputHandler, - IWindowDecorationHandler, + public IInputHandler, + public IWindowDecorationHandler, public IShellSurfaceHandler { public: diff --git a/xbmc/windowing/wayland/WindowDecorator.cpp b/xbmc/windowing/wayland/WindowDecorator.cpp index 54f52d8987719..a0ae2ea2e4c83 100644 --- a/xbmc/windowing/wayland/WindowDecorator.cpp +++ b/xbmc/windowing/wayland/WindowDecorator.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -885,7 +886,7 @@ void CWindowDecorator::ResetShm() { if (IsDecorationActive()) { - m_memory.reset(new CSharedMemory(MemoryBytesForSize(m_mainSurfaceSize, m_scale))); + m_memory = std::make_unique(MemoryBytesForSize(m_mainSurfaceSize, m_scale)); m_memoryAllocatedSize = 0; m_shmPool = m_shm.create_pool(m_memory->Fd(), m_memory->Size()); } diff --git a/xbmc/windowing/wayland/XkbcommonKeymap.cpp b/xbmc/windowing/wayland/XkbcommonKeymap.cpp index d0aed35c95580..9da74e377f27f 100644 --- a/xbmc/windowing/wayland/XkbcommonKeymap.cpp +++ b/xbmc/windowing/wayland/XkbcommonKeymap.cpp @@ -12,6 +12,7 @@ #include "utils/log.h" #include +#include #include #include #include @@ -220,7 +221,7 @@ std::unique_ptr CXkbcommonContext::KeymapFromString(std::strin throw std::runtime_error("Failed to compile keymap"); } - return std::unique_ptr{new CXkbcommonKeymap(std::move(xkbKeymap))}; + return std::make_unique(std::move(xkbKeymap)); } std::unique_ptr CXkbcommonContext::KeymapFromNames(const std::string& rules, const std::string& model, const std::string& layout, const std::string& variant, const std::string& options) @@ -240,7 +241,7 @@ std::unique_ptr CXkbcommonContext::KeymapFromNames(const std:: throw std::runtime_error("Failed to compile keymap"); } - return std::unique_ptr{new CXkbcommonKeymap(std::move(keymap))}; + return std::make_unique(std::move(keymap)); } std::unique_ptr CXkbcommonKeymap::CreateXkbStateFromKeymap(xkb_keymap* keymap) diff --git a/xbmc/windows/GUIWindowSplash.cpp b/xbmc/windows/GUIWindowSplash.cpp index dadd61eab9808..a385288511bce 100644 --- a/xbmc/windows/GUIWindowSplash.cpp +++ b/xbmc/windows/GUIWindowSplash.cpp @@ -14,6 +14,8 @@ #include "settings/AdvancedSettings.h" #include "settings/SettingsComponent.h" +#include + CGUIWindowSplash::CGUIWindowSplash(void) : CGUIWindow(WINDOW_SPLASH, ""), m_image(nullptr) { m_loadType = LOAD_ON_GUI_INIT; @@ -26,7 +28,10 @@ void CGUIWindowSplash::OnInitWindow() if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_splashImage) return; - m_image = std::unique_ptr(new CGUIImage(0, 0, 0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), CTextureInfo(CUtil::GetSplashPath()))); + m_image = std::make_unique(0, 0, 0, 0, + CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), + CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), + CTextureInfo(CUtil::GetSplashPath())); m_image->SetAspectRatio(CAspectRatio::AR_SCALE); } From f088b0c9435590db1cac75512cf610eb6519bac0 Mon Sep 17 00:00:00 2001 From: fuzzard Date: Thu, 12 Oct 2023 09:06:07 +1000 Subject: [PATCH 396/811] [cmake] FindOpenGLES add IMPORTED_NO_SONAME property Some linux projects were using a fix library path for install that caused runtime failures. As per https://cmake.org/cmake/help/latest/prop_tgt/IMPORTED_NO_SONAME.html CMake may adjust generated link commands for some platforms to prevent the linker from using the path to the library in place of its missing --- cmake/modules/FindOpenGLES.cmake | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindOpenGLES.cmake b/cmake/modules/FindOpenGLES.cmake index 49bad7027ff70..5dbb42e514ced 100644 --- a/cmake/modules/FindOpenGLES.cmake +++ b/cmake/modules/FindOpenGLES.cmake @@ -42,10 +42,16 @@ if(NOT TARGET OpenGL::GLES) endif() endif() - add_library(OpenGL::GLES UNKNOWN IMPORTED) + if(${OPENGLES_gl_LIBRARY} MATCHES ".+\.so$") + add_library(OpenGL::GLES SHARED IMPORTED) + else() + add_library(OpenGL::GLES UNKNOWN IMPORTED) + endif() + set_target_properties(OpenGL::GLES PROPERTIES IMPORTED_LOCATION "${OPENGLES_gl_LIBRARY}" - INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES_INCLUDE_DIR}") + INTERFACE_INCLUDE_DIRECTORIES "${OPENGLES_INCLUDE_DIR}" + IMPORTED_NO_SONAME TRUE) if(OPENGLES3_INCLUDE_DIR) set_property(TARGET OpenGL::GLES APPEND PROPERTY From 3dcea03c915f2062d4f8740d66abdf033fba9d6c Mon Sep 17 00:00:00 2001 From: Manuel Lauss Date: Fri, 13 Oct 2023 12:27:15 +0200 Subject: [PATCH 397/811] add missing c++ headers Add a few missing headers which are no longer indirectly included by other headers, fixes build with gcc-14 --- xbmc/addons/AddonUpdateRules.cpp | 1 + xbmc/addons/binary-addons/AddonDll.cpp | 1 + xbmc/interfaces/json-rpc/TextureOperations.cpp | 2 ++ xbmc/utils/EGLImage.cpp | 1 + xbmc/windowing/gbm/drm/DRMConnector.cpp | 1 + xbmc/windowing/gbm/drm/DRMPlane.cpp | 2 ++ 6 files changed, 8 insertions(+) diff --git a/xbmc/addons/AddonUpdateRules.cpp b/xbmc/addons/AddonUpdateRules.cpp index 7d9d43a7c6fdd..6c620f01d71b9 100644 --- a/xbmc/addons/AddonUpdateRules.cpp +++ b/xbmc/addons/AddonUpdateRules.cpp @@ -12,6 +12,7 @@ #include "addons/addoninfo/AddonInfo.h" #include "utils/log.h" +#include #include using namespace ADDON; diff --git a/xbmc/addons/binary-addons/AddonDll.cpp b/xbmc/addons/binary-addons/AddonDll.cpp index 7f867dbf83a43..2c03541110734 100644 --- a/xbmc/addons/binary-addons/AddonDll.cpp +++ b/xbmc/addons/binary-addons/AddonDll.cpp @@ -27,6 +27,7 @@ #include "utils/Variant.h" #include "utils/log.h" +#include #include using namespace KODI::MESSAGING; diff --git a/xbmc/interfaces/json-rpc/TextureOperations.cpp b/xbmc/interfaces/json-rpc/TextureOperations.cpp index 326093b53edd2..d6ffcc8761028 100644 --- a/xbmc/interfaces/json-rpc/TextureOperations.cpp +++ b/xbmc/interfaces/json-rpc/TextureOperations.cpp @@ -14,6 +14,8 @@ #include "TextureDatabase.h" #include "utils/Variant.h" +#include + using namespace JSONRPC; JSONRPC_STATUS CTextureOperations::GetTextures(const std::string &method, ITransportLayer *transport, IClient *client, const CVariant ¶meterObject, CVariant &result) diff --git a/xbmc/utils/EGLImage.cpp b/xbmc/utils/EGLImage.cpp index 36757995f4e2a..623721fd57744 100644 --- a/xbmc/utils/EGLImage.cpp +++ b/xbmc/utils/EGLImage.cpp @@ -14,6 +14,7 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include #include namespace diff --git a/xbmc/windowing/gbm/drm/DRMConnector.cpp b/xbmc/windowing/gbm/drm/DRMConnector.cpp index 94f8e909ff82b..1b2fac4d068ba 100644 --- a/xbmc/windowing/gbm/drm/DRMConnector.cpp +++ b/xbmc/windowing/gbm/drm/DRMConnector.cpp @@ -11,6 +11,7 @@ #include "utils/XTimeUtils.h" #include "utils/log.h" +#include #include using namespace KODI::WINDOWING::GBM; diff --git a/xbmc/windowing/gbm/drm/DRMPlane.cpp b/xbmc/windowing/gbm/drm/DRMPlane.cpp index 90b5660ed576f..865beccacb415 100644 --- a/xbmc/windowing/gbm/drm/DRMPlane.cpp +++ b/xbmc/windowing/gbm/drm/DRMPlane.cpp @@ -13,6 +13,8 @@ #include "utils/StringUtils.h" #include "utils/log.h" +#include + using namespace KODI::WINDOWING::GBM; CDRMPlane::CDRMPlane(int fd, uint32_t plane) : CDRMObject(fd), m_plane(drmModeGetPlane(m_fd, plane)) From 4857c6b265e5ec0dbd7a1fde7a77fc2d88235ede Mon Sep 17 00:00:00 2001 From: arnova Date: Fri, 13 Oct 2023 07:55:14 +0200 Subject: [PATCH 398/811] changed: Redact paths in audio/video error toasts --- xbmc/music/MusicDatabase.cpp | 6 ++++-- xbmc/video/VideoDatabase.cpp | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/xbmc/music/MusicDatabase.cpp b/xbmc/music/MusicDatabase.cpp index dd54a6379abda..171e5a4e8deb0 100644 --- a/xbmc/music/MusicDatabase.cpp +++ b/xbmc/music/MusicDatabase.cpp @@ -12021,7 +12021,8 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CLog::Log(LOGERROR, "CMusicDatabase::{}: Album nfo export failed! ('{}')", __FUNCTION__, nfoFile); CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, - g_localizeStrings.Get(20302), nfoFile); + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } @@ -12165,7 +12166,8 @@ void CMusicDatabase::ExportToXML(const CLibExportSettings& settings, CLog::Log(LOGERROR, "CMusicDatabase::{}: Artist nfo export failed! ('{}')", __FUNCTION__, nfoFile); CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, - g_localizeStrings.Get(20302), nfoFile); + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } diff --git a/xbmc/video/VideoDatabase.cpp b/xbmc/video/VideoDatabase.cpp index 087232e4f2cdd..9284994e222a2 100644 --- a/xbmc/video/VideoDatabase.cpp +++ b/xbmc/video/VideoDatabase.cpp @@ -10153,7 +10153,9 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t if(!xmlDoc.SaveFile(nfoFile)) { CLog::Log(LOGERROR, "{}: Movie nfo export failed! ('{}')", __FUNCTION__, nfoFile); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } @@ -10298,7 +10300,9 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t { CLog::Log(LOGERROR, "{}: Musicvideo nfo export failed! ('{}')", __FUNCTION__, nfoFile); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } @@ -10398,7 +10402,9 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t if(!xmlDoc.SaveFile(nfoFile)) { CLog::Log(LOGERROR, "{}: TVShow nfo export failed! ('{}')", __FUNCTION__, nfoFile); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } @@ -10494,7 +10500,9 @@ void CVideoDatabase::ExportToXML(const std::string &path, bool singleFile /* = t if(!xmlDoc.SaveFile(nfoFile)) { CLog::Log(LOGERROR, "{}: Episode nfo export failed! ('{}')", __FUNCTION__, nfoFile); - CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(20302), nfoFile); + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, + g_localizeStrings.Get(20302), + CURL::GetRedacted(nfoFile)); iFailCount++; } } From 05fddadd27ec6560aca5e3b16a0dd3f9e1783138 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:13:06 +0200 Subject: [PATCH 399/811] [video][music] Hide play-related context menu items for non-existing party mode playlists. --- xbmc/music/MusicUtils.cpp | 16 ++++++++++++++++ xbmc/video/VideoUtils.cpp | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index 0ebee30c01c65..5471fba8514c3 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -851,6 +851,19 @@ bool GetItemsForPlayList(const std::shared_ptr& item, CFileItemList& true); // can be cancelled } +namespace +{ +bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) +{ + if (!item.IsSmartPlayList()) + return false; + + const std::string path{item.GetPath()}; + const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()}; + return ((profileManager->GetUserDataItem("PartyMode.xsp") == path) && !CFileUtils::Exists(path)); +} +} // unnamed namespace + bool IsItemPlayable(const CFileItem& item) { // Exclude all parent folders @@ -894,6 +907,9 @@ bool IsItemPlayable(const CFileItem& item) } } + if (IsNonExistingUserPartyModePlaylist(item)) + return false; + if (item.m_bIsFolder && (item.IsMusicDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://music/"))) { diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index 2b30b2e57221d..e6b56398494bf 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -25,11 +25,13 @@ #include "guilib/LocalizeStrings.h" #include "playlists/PlayList.h" #include "playlists/PlayListFactory.h" +#include "profiles/ProfileManager.h" #include "settings/MediaSettings.h" #include "settings/SettingUtils.h" #include "settings/Settings.h" #include "settings/SettingsComponent.h" #include "threads/IRunnable.h" +#include "utils/FileUtils.h" #include "utils/StringUtils.h" #include "utils/URIUtils.h" #include "utils/log.h" @@ -518,6 +520,20 @@ bool GetItemsForPlayList(const std::shared_ptr& item, CFileItemList& true); // can be cancelled } +namespace +{ +bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) +{ + if (!item.IsSmartPlayList()) + return false; + + const std::string path{item.GetPath()}; + const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()}; + return ((profileManager->GetUserDataItem("PartyMode-Video.xsp") == path) && + !CFileUtils::Exists(path)); +} +} // unnamed namespace + bool IsItemPlayable(const CFileItem& item) { if (item.IsParentFolder()) @@ -574,6 +590,9 @@ bool IsItemPlayable(const CFileItem& item) } } + if (IsNonExistingUserPartyModePlaylist(item)) + return false; + if (item.m_bIsFolder && (item.IsVideoDb() || StringUtils::StartsWithNoCase(item.GetPath(), "library://video/"))) { From 8ba26d546bc475360b1699df3eff64c122479c38 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Sat, 7 Oct 2023 19:31:56 -0700 Subject: [PATCH 400/811] [windows] add winsock2.h to PlatformDefs.h Signed-off-by: Lukas Rusak --- xbmc/platform/win32/PlatformDefs.h | 1 + 1 file changed, 1 insertion(+) diff --git a/xbmc/platform/win32/PlatformDefs.h b/xbmc/platform/win32/PlatformDefs.h index a7ff4ba2616e5..213bccc84893f 100644 --- a/xbmc/platform/win32/PlatformDefs.h +++ b/xbmc/platform/win32/PlatformDefs.h @@ -9,6 +9,7 @@ #pragma once #include +#include #define __STDC_FORMAT_MACROS #include From 771a867dee47c55b14330f94cfcb3d5aa7a4e9b5 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 3 Oct 2023 20:13:30 -0700 Subject: [PATCH 401/811] [TEST] CNetworkTest: add basic test for PingHost Signed-off-by: Lukas Rusak --- xbmc/network/test/CMakeLists.txt | 8 ++++--- xbmc/network/test/TestNetwork.cpp | 40 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 xbmc/network/test/TestNetwork.cpp diff --git a/xbmc/network/test/CMakeLists.txt b/xbmc/network/test/CMakeLists.txt index a323d1835b078..05eb260c0ba55 100644 --- a/xbmc/network/test/CMakeLists.txt +++ b/xbmc/network/test/CMakeLists.txt @@ -1,5 +1,7 @@ -if(MICROHTTPD_FOUND) - set(SOURCES TestWebServer.cpp) +set(SOURCES TestNetwork.cpp) - core_add_test_library(network_test) +if(MICROHTTPD_FOUND) + list(APPEND SOURCES TestWebServer.cpp) endif() + +core_add_test_library(network_test) diff --git a/xbmc/network/test/TestNetwork.cpp b/xbmc/network/test/TestNetwork.cpp new file mode 100644 index 0000000000000..df4e848b11759 --- /dev/null +++ b/xbmc/network/test/TestNetwork.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023 Team Kodi + * 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 "ServiceBroker.h" +#include "network/Network.h" + +#include +#include + +class TestNetwork : public testing::Test +{ +public: + TestNetwork() = default; + ~TestNetwork() = default; + + bool PingHost(const std::string& ip) const + { + static auto& network = CServiceBroker::GetNetwork(); + + return network.PingHost(inet_addr(ip.c_str()), GetPort(), GetTimeout()); + } + + unsigned int GetPort() const { return m_port; } + unsigned int GetTimeout() const { return m_timeoutMs; } + +private: + unsigned int m_port{0}; + unsigned int m_timeoutMs{100}; +}; + +TEST_F(TestNetwork, PingHost) +{ + EXPECT_TRUE(PingHost("127.0.0.1")); + EXPECT_FALSE(PingHost("10.254.254.254")); +} From 6ae7593b0439b9be1ad3c2678980f426ad1b0888 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Fri, 13 Oct 2023 23:20:35 +0200 Subject: [PATCH 402/811] [cores] Fix external player not recognized as default player. --- .../playercorefactory/PlayerCoreFactory.cpp | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp index 0d01c13cf1343..f9ac98b65550e 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp @@ -147,14 +147,12 @@ void CPlayerCoreFactory::GetPlayers(const CFileItem& item, std::vector -1) { const std::string videoDefault = GetPlayerName(idx); - const auto it = std::find(players.cbegin(), players.cend(), videoDefault); - if (it != players.cend()) - players.erase(it); - - CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding videodefaultplayer ({})", - videoDefault); - // default player must always be first vector entry - players.insert(players.begin(), videoDefault); + if (std::find(players.cbegin(), players.cend(), videoDefault) == players.cend()) + { + players.emplace_back(videoDefault); + CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding videodefaultplayer ({})", + videoDefault); + } } GetPlayers(players, false, true); // Video-only players GetPlayers(players, true, true); // Audio & video players @@ -169,14 +167,12 @@ void CPlayerCoreFactory::GetPlayers(const CFileItem& item, std::vector -1) { const std::string audioDefault = GetPlayerName(idx); - const auto it = std::find(players.cbegin(), players.cend(), audioDefault); - if (it != players.cend()) - players.erase(it); - - CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding audiodefaultplayer ({})", - audioDefault); - // default player must always be first vector entry - players.insert(players.begin(), audioDefault); + if (std::find(players.cbegin(), players.cend(), audioDefault) == players.cend()) + { + players.emplace_back(audioDefault); + CLog::Log(LOGDEBUG, "CPlayerCoreFactory::GetPlayers: adding audiodefaultplayer ({})", + audioDefault); + } } GetPlayers(players, true, false); // Audio-only players GetPlayers(players, true, true); // Audio & video players From d215d08a4a0ddf66f41caa1aaa975b1f656cd2d0 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:22:08 +0200 Subject: [PATCH 403/811] [favourites][listproviders] Favourites: Add support for action 'info'. --- xbmc/favourites/GUIWindowFavourites.cpp | 7 +++++++ xbmc/listproviders/DirectoryProvider.cpp | 6 +++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index 15b8b23afa5b5..22c3f05305d05 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -173,6 +173,13 @@ bool CGUIWindowFavourites::OnAction(const CAction& action) } return false; } + else if (action.GetID() == ACTION_SHOW_INFO) + { + const auto targetItem{ + CServiceBroker::GetFavouritesService().ResolveFavourite(*(*m_vecItems)[selectedItem])}; + + return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*targetItem); + } else if (action.GetID() == ACTION_MOVE_ITEM_UP) { if (FAVOURITES_UTILS::ShouldEnableMoveItems()) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 5f22353667494..8b7804c1eedc7 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -626,7 +626,11 @@ bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) bool CDirectoryProvider::OnInfo(const std::shared_ptr& fileItem) { - return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*fileItem); + const auto targetItem{fileItem->IsFavourite() + ? CServiceBroker::GetFavouritesService().ResolveFavourite(*fileItem) + : fileItem}; + + return UTILS::GUILIB::CGUIContentUtils::ShowInfoForItem(*targetItem); } bool CDirectoryProvider::OnInfo(const CGUIListItemPtr& item) From 24f3cd254b03c7d94e39d03e29f03beec617bf6f Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:41:08 +0200 Subject: [PATCH 404/811] [favourites][listproviders] Use CFavouritesService::ResolveFavourite. --- xbmc/favourites/GUIWindowFavourites.cpp | 24 ++++++++++++-------- xbmc/listproviders/DirectoryProvider.cpp | 29 ++++++++---------------- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/xbmc/favourites/GUIWindowFavourites.cpp b/xbmc/favourites/GUIWindowFavourites.cpp index 22c3f05305d05..47677667341d7 100644 --- a/xbmc/favourites/GUIWindowFavourites.cpp +++ b/xbmc/favourites/GUIWindowFavourites.cpp @@ -111,20 +111,24 @@ class CVideoPlayActionProcessor : public VIDEO::GUILIB::CVideoPlayActionProcesso }; } // namespace -bool CGUIWindowFavourites::OnSelect(int item) +bool CGUIWindowFavourites::OnSelect(int itemIdx) { - if (item < 0 || item >= m_vecItems->Size()) + if (itemIdx < 0 || itemIdx >= m_vecItems->Size()) return false; - const CFavouritesURL favURL{*(*m_vecItems)[item], GetID()}; + const auto item{(*m_vecItems)[itemIdx]}; + const CFavouritesURL favURL{*item, GetID()}; if (!favURL.IsValid()) return false; - CFileItem targetItem{favURL.GetTarget(), favURL.IsDir()}; - targetItem.LoadDetails(); - const bool isPlayMedia{favURL.GetAction() == CFavouritesURL::Action::PLAY_MEDIA}; + const auto target{CServiceBroker::GetFavouritesService().ResolveFavourite(*item)}; + if (!target) + return false; + + CFileItem targetItem{*target}; + // video select action setting is for files only, except exec func is playmedia... if (targetItem.HasVideoInfoTag() && (!targetItem.m_bIsFolder || isPlayMedia)) { @@ -145,12 +149,12 @@ bool CGUIWindowFavourites::OnAction(const CAction& action) if (action.GetID() == ACTION_PLAYER_PLAY) { - const CFavouritesURL favURL((*m_vecItems)[selectedItem]->GetPath()); - if (!favURL.IsValid()) + const auto target{ + CServiceBroker::GetFavouritesService().ResolveFavourite(*(*m_vecItems)[selectedItem])}; + if (!target) return false; - CFileItem item{favURL.GetTarget(), favURL.IsDir()}; - item.LoadDetails(); + CFileItem item{*target}; // video play action setting is for files and folders... if (item.HasVideoInfoTag() || (item.m_bIsFolder && VIDEO_UTILS::IsItemPlayable(item))) diff --git a/xbmc/listproviders/DirectoryProvider.cpp b/xbmc/listproviders/DirectoryProvider.cpp index 8b7804c1eedc7..603db8afa9824 100644 --- a/xbmc/listproviders/DirectoryProvider.cpp +++ b/xbmc/listproviders/DirectoryProvider.cpp @@ -13,7 +13,6 @@ #include "ServiceBroker.h" #include "addons/AddonManager.h" #include "favourites/FavouritesService.h" -#include "favourites/FavouritesURL.h" #include "filesystem/Directory.h" #include "guilib/GUIComponent.h" #include "guilib/GUIWindowManager.h" @@ -546,26 +545,18 @@ bool CDirectoryProvider::OnClick(const CGUIListItemPtr& item) { CFileItem targetItem{*std::static_pointer_cast(item)}; - bool isPlayMedia{false}; - if (targetItem.IsFavourite()) { - // Resolve the favourite - const CFavouritesURL url{targetItem.GetPath()}; - if (!url.IsValid()) + const auto target{CServiceBroker::GetFavouritesService().ResolveFavourite(targetItem)}; + if (!target) return false; - targetItem = {url.GetTarget(), url.IsDir()}; - targetItem.LoadDetails(); - - isPlayMedia = (url.GetAction() == CFavouritesURL::Action::PLAY_MEDIA); - } - else - { - const CExecString exec{targetItem, GetTarget(targetItem)}; - isPlayMedia = (exec.GetFunction() == "playmedia"); + targetItem = *target; } + const CExecString exec{targetItem, GetTarget(targetItem)}; + const bool isPlayMedia{exec.GetFunction() == "playmedia"}; + // video select action setting is for files only, except exec func is playmedia... if (targetItem.HasVideoInfoTag() && (!targetItem.m_bIsFolder || isPlayMedia)) { @@ -589,13 +580,11 @@ bool CDirectoryProvider::OnPlay(const CGUIListItemPtr& item) if (targetItem.IsFavourite()) { - // Resolve the favourite - const CFavouritesURL url(targetItem.GetPath()); - if (!url.IsValid()) + const auto target{CServiceBroker::GetFavouritesService().ResolveFavourite(targetItem)}; + if (!target) return false; - targetItem = {url.GetTarget(), url.IsDir()}; - targetItem.LoadDetails(); + targetItem = *target; } // video play action setting is for files and folders... From 50b89f997705e40329cf5c731d3dc4c5765a2e90 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 11:59:28 +0200 Subject: [PATCH 405/811] [favourites] Fix context menu item 'play' and 'resume' not working. --- xbmc/favourites/ContextMenus.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp index ee8a1aaa64f63..1ed7b54931696 100644 --- a/xbmc/favourites/ContextMenus.cpp +++ b/xbmc/favourites/ContextMenus.cpp @@ -146,8 +146,11 @@ bool CFavouritesTargetResume::IsVisible(const CFileItem& item) const bool CFavouritesTargetResume::Execute(const std::shared_ptr& item) const { - FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *item, "resume"}); - return true; + const std::shared_ptr targetItem{ResolveFavouriteItem(*item)}; + if (targetItem) + return FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *targetItem, "resume"}); + + return false; } std::string CFavouritesTargetPlay::GetLabel(const CFileItem& item) const @@ -166,8 +169,11 @@ bool CFavouritesTargetPlay::IsVisible(const CFileItem& item) const bool CFavouritesTargetPlay::Execute(const std::shared_ptr& item) const { - FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *item, "noresume"}); - return true; + const std::shared_ptr targetItem{ResolveFavouriteItem(*item)}; + if (targetItem) + return FAVOURITES_UTILS::ExecuteAction({"PlayMedia", *targetItem, "noresume"}); + + return false; } bool CFavouritesTargetInfo::IsVisible(const CFileItem& item) const From 960502111ce1b6a220d7e7bd2ced68bb02acb687 Mon Sep 17 00:00:00 2001 From: jjd-uk Date: Sat, 14 Oct 2023 12:11:58 +0100 Subject: [PATCH 406/811] [GUI][Skins] Bump to xbmc.gui 5.17.0 --- addons/skin.estouchy/addon.xml | 4 ++-- addons/skin.estuary/addon.xml | 4 ++-- addons/xbmc.gui/addon.xml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/skin.estouchy/addon.xml b/addons/skin.estouchy/addon.xml index d70635632c733..9b85890ee0daa 100644 --- a/addons/skin.estouchy/addon.xml +++ b/addons/skin.estouchy/addon.xml @@ -1,7 +1,7 @@ - + - + diff --git a/addons/skin.estuary/addon.xml b/addons/skin.estuary/addon.xml index 07f4d0cf7c42e..4341d3324a4db 100644 --- a/addons/skin.estuary/addon.xml +++ b/addons/skin.estuary/addon.xml @@ -1,7 +1,7 @@ - + - + diff --git a/addons/xbmc.gui/addon.xml b/addons/xbmc.gui/addon.xml index 91a934008092a..6f914372b5d58 100644 --- a/addons/xbmc.gui/addon.xml +++ b/addons/xbmc.gui/addon.xml @@ -1,5 +1,5 @@ - + From 645313bd2944b27f1c377c515a93476fd964ddb2 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 12:39:13 +0200 Subject: [PATCH 407/811] [utils] CGUIContentUtils::HasInfoForItem: Let PVR check the item. Do not make assumptions about PVR's implementation of OnInfo. --- xbmc/pvr/guilib/PVRGUIActionsUtils.cpp | 6 ++++++ xbmc/pvr/guilib/PVRGUIActionsUtils.h | 8 ++++++++ xbmc/utils/guilib/GUIContentUtils.cpp | 5 +++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp index 28e55820431d8..5831b59ec6543 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp @@ -16,6 +16,12 @@ namespace PVR { +bool CPVRGUIActionsUtils::HasInfoForItem(const CFileItem& item) const +{ + return item.HasPVRRecordingInfoTag() || item.HasPVRChannelInfoTag() || + item.HasPVRTimerInfoTag() || item.HasEPGSearchFilter(); +} + bool CPVRGUIActionsUtils::OnInfo(const CFileItem& item) { if (item.HasPVRRecordingInfoTag()) diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.h b/xbmc/pvr/guilib/PVRGUIActionsUtils.h index a8805482e2dc8..7758c5b2286f3 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.h +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.h @@ -20,9 +20,17 @@ class CPVRGUIActionsUtils : public IPVRComponent CPVRGUIActionsUtils() = default; ~CPVRGUIActionsUtils() override = default; + /*! + * @brief Check whether OnInfo supports the given item. + * @param item The item. + * @return True if supported, false otherwise. + */ + bool HasInfoForItem(const CFileItem& item) const; + /*! * @brief Process info action for the given item. * @param item The item. + * @return True on success, false otherwise. */ bool OnInfo(const CFileItem& item); diff --git a/xbmc/utils/guilib/GUIContentUtils.cpp b/xbmc/utils/guilib/GUIContentUtils.cpp index 0c2c6ef12ea47..d98fd66a2f643 100644 --- a/xbmc/utils/guilib/GUIContentUtils.cpp +++ b/xbmc/utils/guilib/GUIContentUtils.cpp @@ -30,7 +30,8 @@ bool CGUIContentUtils::HasInfoForItem(const CFileItem& item) mediaType == MediaTypeMusicVideo); } - return (item.HasMusicInfoTag() || item.IsPVR() || item.HasAddonInfo()); + return (item.HasMusicInfoTag() || item.HasAddonInfo() || + CServiceBroker::GetPVRManager().Get().HasInfoForItem(item)); } bool CGUIContentUtils::ShowInfoForItem(const CFileItem& item) @@ -39,7 +40,7 @@ bool CGUIContentUtils::ShowInfoForItem(const CFileItem& item) { return CGUIDialogAddonInfo::ShowForItem(std::make_shared(item)); } - else if (item.IsPVR()) + else if (CServiceBroker::GetPVRManager().Get().HasInfoForItem(item)) { return CServiceBroker::GetPVRManager().Get().OnInfo(item); } From b220e19c5b8d69e21886cd9c3d1ce4ddccef2740 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:21:01 +0200 Subject: [PATCH 408/811] [fileitem][PVR] CFileItem::LoadDetails: Factor out loading of PVR items. --- xbmc/FileItem.cpp | 27 ++++++----------- xbmc/pvr/guilib/PVRGUIActionsUtils.cpp | 40 ++++++++++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsUtils.h | 9 ++++++ 3 files changed, 57 insertions(+), 19 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 476018cf6b3f7..52d297735c62a 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -38,6 +38,7 @@ #include "pvr/epg/EpgInfoTag.h" #include "pvr/epg/EpgSearchFilter.h" #include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsUtils.h" #include "pvr/recordings/PVRRecording.h" #include "pvr/timers/PVRTimerInfoTag.h" #include "settings/AdvancedSettings.h" @@ -3747,28 +3748,16 @@ bool CFileItem::LoadDetails() return ret; } - if (URIUtils::IsPVRRecordingFileOrFolder(GetPath())) + if (IsPVR()) { - if (HasProperty("watchedepisodes") || HasProperty("watched")) - return true; - - const std::string parentPath = URIUtils::GetParentPath(GetPath()); - - //! @todo optimize, find a way to set the details of the item without loading parent directory. - CFileItemList items; - if (CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) + const std::shared_ptr loadedItem{ + CServiceBroker::GetPVRManager().Get().LoadItem(*this)}; + if (loadedItem) { - const std::string path = GetPath(); - const auto it = std::find_if(items.cbegin(), items.cend(), - [path](const auto& entry) { return entry->GetPath() == path; }); - if (it != items.cend()) - { - *this = *(*it); - return true; - } + UpdateInfo(*loadedItem); + return true; } - - CLog::LogF(LOGERROR, "Error filling item details (path={})", GetPath()); + CLog::LogF(LOGERROR, "Error filling PVR item details (path={})", GetPath()); return false; } diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp index 5831b59ec6543..44c48d8cb64d6 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp @@ -10,9 +10,12 @@ #include "FileItem.h" #include "ServiceBroker.h" +#include "filesystem/Directory.h" #include "pvr/PVRManager.h" #include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/guilib/PVRGUIActionsRecordings.h" +#include "utils/URIUtils.h" +#include "utils/log.h" namespace PVR { @@ -39,4 +42,41 @@ bool CPVRGUIActionsUtils::OnInfo(const CFileItem& item) return false; } +namespace +{ +std::shared_ptr LoadRecordingFileOrFolderItem(const CFileItem& item) +{ + if (URIUtils::IsPVRRecordingFileOrFolder(item.GetPath())) + { + //! @todo prop misused to detect loaded state for recording folder item + if (item.HasPVRRecordingInfoTag() || item.HasProperty("watchedepisodes")) + return std::make_shared(item); // already loaded + + const std::string parentPath{URIUtils::GetParentPath(item.GetPath())}; + + //! @todo optimize, find a way to set the details of the item without loading parent directory. + CFileItemList items; + if (XFILE::CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) + { + const std::string path{item.GetPath()}; + const auto it = std::find_if(items.cbegin(), items.cend(), + [&path](const auto& entry) { return entry->GetPath() == path; }); + if (it != items.cend()) + return *it; + } + } + return {}; +} +} // unnamed namespace + +std::shared_ptr CPVRGUIActionsUtils::LoadItem(const CFileItem& item) +{ + std::shared_ptr loadedItem{LoadRecordingFileOrFolderItem(item)}; + if (loadedItem) + return loadedItem; + + CLog::LogFC(LOGWARNING, LOGPVR, "Error loading item details (path={})", item.GetPath()); + return {}; +} + } // namespace PVR diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.h b/xbmc/pvr/guilib/PVRGUIActionsUtils.h index 7758c5b2286f3..199e53607a47e 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.h +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.h @@ -10,6 +10,8 @@ #include "pvr/IPVRComponent.h" +#include + class CFileItem; namespace PVR @@ -34,6 +36,13 @@ class CPVRGUIActionsUtils : public IPVRComponent */ bool OnInfo(const CFileItem& item); + /*! + * @brief Load item details (create recording info tag etc.). + * @param item The item. + * @return Loaded item on success, nullptr otherwise. + */ + std::shared_ptr LoadItem(const CFileItem& item); + private: CPVRGUIActionsUtils(const CPVRGUIActionsUtils&) = delete; CPVRGUIActionsUtils const& operator=(CPVRGUIActionsUtils const&) = delete; From dce88a0a77e7fdf62b461afe356bf3aae4ce5594 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:24:01 +0200 Subject: [PATCH 409/811] [PVR] Add support for channels to CPVRGUIActionsUtils::LoadItem. --- xbmc/FileItem.cpp | 40 ++++++++++++++++++++++++++ xbmc/pvr/guilib/PVRGUIActionsUtils.cpp | 21 ++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 52d297735c62a..651500326f64a 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1740,6 +1740,26 @@ void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/) *GetGameInfoTag() = *item.GetGameInfoTag(); SetInvalid(); } + if (item.HasPVRChannelGroupMemberInfoTag()) + { + m_pvrChannelGroupMemberInfoTag = item.GetPVRChannelGroupMemberInfoTag(); + SetInvalid(); + } + if (item.HasPVRTimerInfoTag()) + { + m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; + SetInvalid(); + } + if (item.HasEPGInfoTag()) + { + m_epgInfoTag = item.m_epgInfoTag; + SetInvalid(); + } + if (item.HasEPGSearchFilter()) + { + m_epgSearchFilter = item.m_epgSearchFilter; + SetInvalid(); + } SetDynPath(item.GetDynPath()); if (replaceLabels && !item.GetLabel().empty()) SetLabel(item.GetLabel()); @@ -1783,6 +1803,26 @@ void CFileItem::MergeInfo(const CFileItem& item) *GetGameInfoTag() = *item.GetGameInfoTag(); SetInvalid(); } + if (item.HasPVRChannelGroupMemberInfoTag()) + { + m_pvrChannelGroupMemberInfoTag = item.GetPVRChannelGroupMemberInfoTag(); + SetInvalid(); + } + if (item.HasPVRTimerInfoTag()) + { + m_pvrTimerInfoTag = item.m_pvrTimerInfoTag; + SetInvalid(); + } + if (item.HasEPGInfoTag()) + { + m_epgInfoTag = item.m_epgInfoTag; + SetInvalid(); + } + if (item.HasEPGSearchFilter()) + { + m_epgSearchFilter = item.m_epgSearchFilter; + SetInvalid(); + } SetDynPath(item.GetDynPath()); if (!item.GetLabel().empty()) SetLabel(item.GetLabel()); diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp index 44c48d8cb64d6..24d918f7f177c 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp @@ -12,6 +12,7 @@ #include "ServiceBroker.h" #include "filesystem/Directory.h" #include "pvr/PVRManager.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" #include "pvr/guilib/PVRGUIActionsEPG.h" #include "pvr/guilib/PVRGUIActionsRecordings.h" #include "utils/URIUtils.h" @@ -67,6 +68,22 @@ std::shared_ptr LoadRecordingFileOrFolderItem(const CFileItem& item) } return {}; } + +std::shared_ptr LoadChannelItem(const CFileItem& item) +{ + if (URIUtils::IsPVRChannel(item.GetPath())) + { + if (item.HasPVRChannelInfoTag()) + return std::make_shared(item); // already loaded + + const auto groups{CServiceBroker::GetPVRManager().ChannelGroups()}; + const std::shared_ptr groupMember{ + groups->GetChannelGroupMemberByPath(item.GetPath())}; + if (groupMember) + return std::make_shared(groupMember); + } + return {}; +} } // unnamed namespace std::shared_ptr CPVRGUIActionsUtils::LoadItem(const CFileItem& item) @@ -75,6 +92,10 @@ std::shared_ptr CPVRGUIActionsUtils::LoadItem(const CFileItem& item) if (loadedItem) return loadedItem; + loadedItem = LoadChannelItem(item); + if (loadedItem) + return loadedItem; + CLog::LogFC(LOGWARNING, LOGPVR, "Error loading item details (path={})", item.GetPath()); return {}; } From c2c0ac8661bacb51e9fd76034b58c65f448d5933 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sat, 14 Oct 2023 22:42:19 +0200 Subject: [PATCH 410/811] [application] Fix bd selection menu shown when external player is used. --- xbmc/application/Application.cpp | 14 +++++++++++--- xbmc/cores/playercorefactory/PlayerCoreFactory.cpp | 5 +++++ xbmc/cores/playercorefactory/PlayerCoreFactory.h | 1 + 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/xbmc/application/Application.cpp b/xbmc/application/Application.cpp index ce5b77c8e88e1..bf1feb60bfc86 100644 --- a/xbmc/application/Application.cpp +++ b/xbmc/application/Application.cpp @@ -2450,9 +2450,17 @@ bool CApplication::PlayFile(CFileItem item, const std::string& player, bool bRes if (!(options.startpercent > 0.0 || options.starttime > 0.0) && (item.IsBDFile() || item.IsDiscImage())) { - //check if we must show the simplified bd menu - if (!CGUIDialogSimpleMenu::ShowPlaySelection(item)) - return true; + // No video selection when using an external player, because it needs to handle that on its own. + const std::string defaulPlayer{ + player.empty() ? m_ServiceManager->GetPlayerCoreFactory().GetDefaultPlayer(item) : player}; + const bool isExternalPlayer{ + m_ServiceManager->GetPlayerCoreFactory().IsExternalPlayer(defaulPlayer)}; + if (!isExternalPlayer) + { + // Check if we must show the simplified bd menu. + if (!CGUIDialogSimpleMenu::ShowPlaySelection(item)) + return true; + } } // this really aught to be inside !bRestart, but since PlayStack diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp index f9ac98b65550e..8f5d0ccd7ffd8 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp @@ -254,6 +254,11 @@ std::string CPlayerCoreFactory::GetPlayerType(const std::string& player) const return m_vecPlayerConfigs[idx]->m_type; } +bool CPlayerCoreFactory::IsExternalPlayer(const std::string& player) const +{ + return (GetPlayerType(player) == "external"); +} + bool CPlayerCoreFactory::PlaysAudio(const std::string& player) const { std::unique_lock lock(m_section); diff --git a/xbmc/cores/playercorefactory/PlayerCoreFactory.h b/xbmc/cores/playercorefactory/PlayerCoreFactory.h index e2963e8a368c6..50f45c624b4bd 100644 --- a/xbmc/cores/playercorefactory/PlayerCoreFactory.h +++ b/xbmc/cores/playercorefactory/PlayerCoreFactory.h @@ -44,6 +44,7 @@ class CPlayerCoreFactory : public ISettingsHandler void GetPlayers(std::vector&players, std::string &type) const; void GetRemotePlayers(std::vector&players) const; //All remote players we can attach to std::string GetPlayerType(const std::string &player) const; + bool IsExternalPlayer(const std::string& player) const; bool PlaysAudio(const std::string &player) const; bool PlaysVideo(const std::string &player) const; From 2b7f122ba9a07b7ceaa79b1bf6a8619a6277866a Mon Sep 17 00:00:00 2001 From: CrystalP Date: Wed, 27 Sep 2023 14:45:32 -0400 Subject: [PATCH 411/811] [Windows] const-ify system clock frequency --- xbmc/windowing/windows/VideoSyncD3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 564df55fddf5c..5583d79f4859a 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -63,7 +63,7 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) int64_t LastVBlankTime; int NrVBlanks; double VBlankTime; - int64_t systemFrequency = CurrentHostFrequency(); + const int64_t systemFrequency = CurrentHostFrequency(); // init the vblanktime Now = CurrentHostCounter(); From 0f3e65eaf08bda890154ee467960ac35d4642e78 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Tue, 10 Oct 2023 21:40:41 -0400 Subject: [PATCH 412/811] [Windows] Remove unnecessary sleep for half vsync Windows 7 is not supported anymore in v21 and the sleep was necessary only for Windows 7 as back to back WaitForVBlank calls would otherwise freeze rendering. Sleep is not needed anymore with Windows 8 and later, and detrimental with UWP, which doesn't have an API to increase timer precision and frequently overslept. --- xbmc/windowing/windows/VideoSyncD3D.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 5583d79f4859a..2e4ed78a842f7 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -93,14 +93,6 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) if (fps != GetFps()) break; } - - // because we had a vblank, sleep until half the refreshrate period because i think WaitForVBlank block any rendering stuf - // without sleeping we have freeze rendering - int SleepTime = (int)((LastVBlankTime + (systemFrequency / MathUtils::round_int(m_fps) / 2) - Now) * 1000 / systemFrequency); - if (SleepTime > 50) - SleepTime = 50; //failsafe - if (SleepTime > 0) - ::Sleep(SleepTime); } m_lostEvent.Set(); From d8401a7d41fe79d7336942039402341d41e25613 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Sun, 10 Sep 2023 14:01:31 -0400 Subject: [PATCH 413/811] [Windows] Retrieve the screen refresh rate without using the swap chain The video clock runs on its own thread. Previous implementation with DeviceResources::GetDisplayMode() used a swap chain method that sometimes gets stalled by an internal dxgi lock held by the main thread calling Present(). The additional information is not even useful here => skip --- xbmc/windowing/windows/VideoSyncD3D.cpp | 36 ++++++++++++++++++++++--- xbmc/windowing/windows/VideoSyncD3D.h | 3 +++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 2e4ed78a842f7..aad10fe707e9d 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -20,6 +20,10 @@ #include +#ifdef TARGET_WINDOWS_STORE +#include +#endif + using namespace std::chrono_literals; void CVideoSyncD3D::OnLostDisplay() @@ -54,6 +58,10 @@ bool CVideoSyncD3D::Setup() if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) CLog::Log(LOGDEBUG, "CVideoSyncD3D: SetThreadPriority failed"); + Microsoft::WRL::ComPtr pOutput; + DX::DeviceResources::Get()->GetOutput(&pOutput); + pOutput->GetDesc(&m_outputDesc); + return true; } @@ -74,6 +82,7 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) // sleep until vblank Microsoft::WRL::ComPtr pOutput; DX::DeviceResources::Get()->GetOutput(&pOutput); + pOutput->GetDesc(&m_outputDesc); pOutput->WaitForVBlank(); // calculate how many vblanks happened @@ -112,14 +121,33 @@ void CVideoSyncD3D::Cleanup() float CVideoSyncD3D::GetFps() { - DXGI_MODE_DESC DisplayMode = {}; - DX::DeviceResources::Get()->GetDisplayMode(&DisplayMode); +#ifdef TARGET_WINDOWS_DESKTOP + DEVMODEW sDevMode = {}; + sDevMode.dmSize = sizeof(sDevMode); - m_fps = (DisplayMode.RefreshRate.Denominator != 0) ? (float)DisplayMode.RefreshRate.Numerator / (float)DisplayMode.RefreshRate.Denominator : 0.0f; + if (EnumDisplaySettingsW(m_outputDesc.DeviceName, ENUM_CURRENT_SETTINGS, &sDevMode)) + { + if ((sDevMode.dmDisplayFrequency + 1) % 24 == 0 || (sDevMode.dmDisplayFrequency + 1) % 30 == 0) + m_fps = static_cast(sDevMode.dmDisplayFrequency + 1) / 1.001f; + else + m_fps = static_cast(sDevMode.dmDisplayFrequency); + + if (sDevMode.dmDisplayFlags & DM_INTERLACED) + m_fps *= 2; + } +#else + using namespace winrt::Windows::Graphics::Display::Core; + + auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView(); + if (hdmiInfo) // Xbox only + { + auto currentMode = hdmiInfo.GetCurrentDisplayMode(); + m_fps = static_cast(currentMode.RefreshRate()); + } +#endif if (m_fps == 0.0) m_fps = 60.0f; return m_fps; } - diff --git a/xbmc/windowing/windows/VideoSyncD3D.h b/xbmc/windowing/windows/VideoSyncD3D.h index 8c706dd88d4e1..aa9d80e6afb44 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.h +++ b/xbmc/windowing/windows/VideoSyncD3D.h @@ -12,6 +12,8 @@ #include "threads/Event.h" #include "windowing/VideoSync.h" +#include + class CVideoSyncD3D : public CVideoSync, IDispResource { public: @@ -33,5 +35,6 @@ class CVideoSyncD3D : public CVideoSync, IDispResource volatile bool m_displayReset; CEvent m_lostEvent; int64_t m_lastUpdateTime; + DXGI_OUTPUT_DESC m_outputDesc{}; }; From d9bf5c7699badbf3dd9c71cf3224bf62edf5e630 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Tue, 5 Sep 2023 23:36:40 -0400 Subject: [PATCH 414/811] [Windows] videosync: retrieve refresh rate only on display config change. The previous condition was true for every frame and retrieved the current refresh rate too often. IsCurrent() also reacts to display changes other than refresh rate but display changes are rare. IsCurrent() takes about 1/10th of the time of refresh rate retrieval and mostly saves time --- xbmc/windowing/windows/VideoSyncD3D.cpp | 8 ++++++-- xbmc/windowing/windows/VideoSyncD3D.h | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index aad10fe707e9d..99da3d42c1eb2 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -58,6 +58,8 @@ bool CVideoSyncD3D::Setup() if (!SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL)) CLog::Log(LOGDEBUG, "CVideoSyncD3D: SetThreadPriority failed"); + CreateDXGIFactory1(IID_PPV_ARGS(m_factory.ReleaseAndGetAddressOf())); + Microsoft::WRL::ComPtr pOutput; DX::DeviceResources::Get()->GetOutput(&pOutput); pOutput->GetDesc(&m_outputDesc); @@ -76,7 +78,7 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) // init the vblanktime Now = CurrentHostCounter(); LastVBlankTime = Now; - m_lastUpdateTime = Now - systemFrequency; + while (!stopEvent.Signaled() && !m_displayLost && !m_displayReset) { // sleep until vblank @@ -96,8 +98,10 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) // save the timestamp of this vblank so we can calculate how many vblanks happened next time LastVBlankTime = Now; - if ((Now - m_lastUpdateTime) >= systemFrequency) + if (!m_factory->IsCurrent()) { + CreateDXGIFactory1(IID_PPV_ARGS(m_factory.ReleaseAndGetAddressOf())); + float fps = m_fps; if (fps != GetFps()) break; diff --git a/xbmc/windowing/windows/VideoSyncD3D.h b/xbmc/windowing/windows/VideoSyncD3D.h index aa9d80e6afb44..01664c08d425a 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.h +++ b/xbmc/windowing/windows/VideoSyncD3D.h @@ -18,7 +18,7 @@ class CVideoSyncD3D : public CVideoSync, IDispResource { public: CVideoSyncD3D(CVideoReferenceClock* clock) - : CVideoSync(clock), m_displayLost(false), m_displayReset(false), m_lastUpdateTime(0) + : CVideoSync(clock), m_displayLost(false), m_displayReset(false) { } bool Setup() override; @@ -34,7 +34,7 @@ class CVideoSyncD3D : public CVideoSync, IDispResource volatile bool m_displayLost; volatile bool m_displayReset; CEvent m_lostEvent; - int64_t m_lastUpdateTime; DXGI_OUTPUT_DESC m_outputDesc{}; + Microsoft::WRL::ComPtr m_factory; }; From b2b852ab9ce406fd08948d255fc4092314ac9206 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Tue, 5 Sep 2023 23:39:16 -0400 Subject: [PATCH 415/811] [Windows] Handle short WaitForVBlank This happens when closing the lid of a laptop, during refresh rate/HDR status change and for errors. Before attempting to detect another vblank, sleep to avoid a busy loop. Since the user can't see the output, no need to attempt to maintain perfect vsync with an expensive accurate alternative source such as spinning on performance counters. It's better to not send updates to the video reference clock and let it interpolate from the last known good timestamp) --- xbmc/windowing/windows/VideoSyncD3D.cpp | 27 ++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 99da3d42c1eb2..5fa9305d4f7b0 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -74,6 +74,7 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) int NrVBlanks; double VBlankTime; const int64_t systemFrequency = CurrentHostFrequency(); + bool validVBlank{true}; // init the vblanktime Now = CurrentHostCounter(); @@ -85,7 +86,31 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) Microsoft::WRL::ComPtr pOutput; DX::DeviceResources::Get()->GetOutput(&pOutput); pOutput->GetDesc(&m_outputDesc); - pOutput->WaitForVBlank(); + + const int64_t WaitForVBlankStartTime = CurrentHostCounter(); + const HRESULT hr = pOutput->WaitForVBlank(); + const int64_t WaitForVBlankElapsedTime = CurrentHostCounter() - WaitForVBlankStartTime; + + // WaitForVBlank() can return very quickly due to errors or screen sleeping + if (!SUCCEEDED(hr) || WaitForVBlankElapsedTime - (systemFrequency / 1000) <= 0) + { + if (SUCCEEDED(hr) && validVBlank) + CLog::LogF(LOGWARNING, "failed to detect vblank - screen asleep?"); + + if (!SUCCEEDED(hr)) + CLog::LogF(LOGERROR, "error waiting for vblank, {}", DX::GetErrorDescription(hr)); + + validVBlank = false; + + // Wait a while, until vblank may have come back. No need for accurate sleep. + ::Sleep(250); + continue; + } + else if (!validVBlank) + { + CLog::LogF(LOGWARNING, "vblank detected - resuming reference clock updates"); + validVBlank = true; + } // calculate how many vblanks happened Now = CurrentHostCounter(); From aad05083b5d7c767e71f51eafcf859ab82bdfae7 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Wed, 27 Sep 2023 15:23:54 -0400 Subject: [PATCH 416/811] [Windows] Add GetOutput() alternative, using cached information first. Windowing events already maintain an up to date dxgi output member. Make it track the output description as well, and use those members from the vsync tracking rather than spending resources retrieving the current dxgi output every frame. Other benefits: - avoids dxgi synchronization with main thread Present() for GetContainingOutput() and output->GetDesc(). - avoids GetContainingOutput() errors when the swapchain doesn't own the output, for example with WARP device and sometimes with multi-screen UWP desktop that can't catch some events due to UWP limitations. --- xbmc/rendering/dx/DeviceResources.cpp | 23 +++++++++++++++++++++++ xbmc/rendering/dx/DeviceResources.h | 8 ++++++++ xbmc/windowing/windows/VideoSyncD3D.cpp | 9 ++++----- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp index 103809d591b0d..9a00894f57cf5 100644 --- a/xbmc/rendering/dx/DeviceResources.cpp +++ b/xbmc/rendering/dx/DeviceResources.cpp @@ -131,6 +131,28 @@ void DX::DeviceResources::GetOutput(IDXGIOutput** ppOutput) const *ppOutput = pOutput.Detach(); } +void DX::DeviceResources::GetCachedOutputAndDesc(IDXGIOutput** ppOutput, + DXGI_OUTPUT_DESC* outputDesc) const +{ + ComPtr pOutput; + if (m_output) + { + m_output.As(&pOutput); + *outputDesc = m_outputDesc; + } + else if (m_swapChain && SUCCEEDED(m_swapChain->GetContainingOutput(pOutput.GetAddressOf())) && + pOutput) + { + pOutput->GetDesc(outputDesc); + } + + if (!pOutput) + CLog::LogF(LOGWARNING, "unable to retrieve current output"); + + *ppOutput = pOutput.Detach(); + return; +} + DXGI_ADAPTER_DESC DX::DeviceResources::GetAdapterDesc() const { DXGI_ADAPTER_DESC desc{}; @@ -1045,6 +1067,7 @@ void DX::DeviceResources::HandleOutputChange(const std::function m_dxgiFactory; Microsoft::WRL::ComPtr m_adapter; Microsoft::WRL::ComPtr m_output; + DXGI_OUTPUT_DESC m_outputDesc{}; Microsoft::WRL::ComPtr m_d3dDevice; Microsoft::WRL::ComPtr m_d3dContext; diff --git a/xbmc/windowing/windows/VideoSyncD3D.cpp b/xbmc/windowing/windows/VideoSyncD3D.cpp index 5fa9305d4f7b0..084c34e0e0383 100644 --- a/xbmc/windowing/windows/VideoSyncD3D.cpp +++ b/xbmc/windowing/windows/VideoSyncD3D.cpp @@ -61,8 +61,7 @@ bool CVideoSyncD3D::Setup() CreateDXGIFactory1(IID_PPV_ARGS(m_factory.ReleaseAndGetAddressOf())); Microsoft::WRL::ComPtr pOutput; - DX::DeviceResources::Get()->GetOutput(&pOutput); - pOutput->GetDesc(&m_outputDesc); + DX::DeviceResources::Get()->GetCachedOutputAndDesc(&pOutput, &m_outputDesc); return true; } @@ -84,11 +83,11 @@ void CVideoSyncD3D::Run(CEvent& stopEvent) { // sleep until vblank Microsoft::WRL::ComPtr pOutput; - DX::DeviceResources::Get()->GetOutput(&pOutput); - pOutput->GetDesc(&m_outputDesc); + DX::DeviceResources::Get()->GetCachedOutputAndDesc(pOutput.ReleaseAndGetAddressOf(), + &m_outputDesc); const int64_t WaitForVBlankStartTime = CurrentHostCounter(); - const HRESULT hr = pOutput->WaitForVBlank(); + const HRESULT hr = pOutput ? pOutput->WaitForVBlank() : E_INVALIDARG; const int64_t WaitForVBlankElapsedTime = CurrentHostCounter() - WaitForVBlankStartTime; // WaitForVBlank() can return very quickly due to errors or screen sleeping From 91ab7c60d93c0c060b37c115863df21d3f9e4262 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 15 Oct 2023 10:14:16 +0200 Subject: [PATCH 417/811] [PVR] Fix 'switch to previous channel' not working if it is in another group than the currently playing channel. --- xbmc/pvr/guilib/PVRGUIActionsChannels.cpp | 41 +++++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp index 0527c1ba656d7..fe1b6522734a0 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsChannels.cpp @@ -77,6 +77,32 @@ void CPVRChannelSwitchingInputHandler::OnInputDone() SwitchToChannel(channelNumber); } +namespace +{ +void UpdateActiveGroup(const std::shared_ptr& newChannel) +{ + const std::shared_ptr playbackState{ + CServiceBroker::GetPVRManager().PlaybackState()}; + const std::shared_ptr groups{ + CServiceBroker::GetPVRManager().ChannelGroups()}; + const std::shared_ptr group{ + groups->Get(newChannel->IsRadio())->GetById(newChannel->GroupID())}; + + // Switch group if new channel is not in the active group. + if (group && group != playbackState->GetActiveChannelGroup(newChannel->IsRadio())) + playbackState->SetActiveChannelGroup(group); +} + +void TriggerChannelSwitchAction(const CPVRChannelNumber& channelNumber) +{ + CServiceBroker::GetAppMessenger()->PostMsg( + TMSG_GUI_ACTION, WINDOW_INVALID, -1, + static_cast(new CAction(ACTION_CHANNEL_SWITCH, + static_cast(channelNumber.GetChannelNumber()), + static_cast(channelNumber.GetSubChannelNumber())))); +} +} // unnamed namespace + void CPVRChannelSwitchingInputHandler::SwitchToChannel(const CPVRChannelNumber& channelNumber) { if (channelNumber.IsValid() && CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying()) @@ -115,11 +141,8 @@ void CPVRChannelSwitchingInputHandler::SwitchToChannel(const CPVRChannelNumber& if (groupMember) { - CServiceBroker::GetAppMessenger()->PostMsg( - TMSG_GUI_ACTION, WINDOW_INVALID, -1, - static_cast(new CAction( - ACTION_CHANNEL_SWITCH, static_cast(channelNumber.GetChannelNumber()), - static_cast(channelNumber.GetSubChannelNumber())))); + UpdateActiveGroup(groupMember); + TriggerChannelSwitchAction(channelNumber); } } } @@ -139,12 +162,8 @@ void CPVRChannelSwitchingInputHandler::SwitchToPreviousChannel() playbackState->GetPreviousToLastPlayedChannelGroupMember(playingChannel->IsRadio()); if (groupMember) { - const CPVRChannelNumber channelNumber = groupMember->ChannelNumber(); - CServiceBroker::GetAppMessenger()->SendMsg( - TMSG_GUI_ACTION, WINDOW_INVALID, -1, - static_cast(new CAction( - ACTION_CHANNEL_SWITCH, static_cast(channelNumber.GetChannelNumber()), - static_cast(channelNumber.GetSubChannelNumber())))); + UpdateActiveGroup(groupMember); + TriggerChannelSwitchAction(groupMember->ChannelNumber()); } } } From c94e5df10b36c3e7d51f73d78edf84464ef1c43d Mon Sep 17 00:00:00 2001 From: enen92 <92enen@gmail.com> Date: Sun, 15 Oct 2023 16:39:34 +0100 Subject: [PATCH 418/811] [wayland][input] Remove dead code --- xbmc/windowing/wayland/XkbcommonKeymap.cpp | 20 -------------------- xbmc/windowing/wayland/XkbcommonKeymap.h | 1 - 2 files changed, 21 deletions(-) diff --git a/xbmc/windowing/wayland/XkbcommonKeymap.cpp b/xbmc/windowing/wayland/XkbcommonKeymap.cpp index 9da74e377f27f..8d3e4399d6b85 100644 --- a/xbmc/windowing/wayland/XkbcommonKeymap.cpp +++ b/xbmc/windowing/wayland/XkbcommonKeymap.cpp @@ -224,26 +224,6 @@ std::unique_ptr CXkbcommonContext::KeymapFromString(std::strin return std::make_unique(std::move(xkbKeymap)); } -std::unique_ptr CXkbcommonContext::KeymapFromNames(const std::string& rules, const std::string& model, const std::string& layout, const std::string& variant, const std::string& options) -{ - xkb_rule_names names = { - rules.c_str(), - model.c_str(), - layout.c_str(), - variant.c_str(), - options.c_str() - }; - - std::unique_ptr keymap{xkb_keymap_new_from_names(m_context.get(), &names, XKB_KEYMAP_COMPILE_NO_FLAGS), CXkbcommonKeymap::XkbKeymapDeleter()}; - - if (!keymap) - { - throw std::runtime_error("Failed to compile keymap"); - } - - return std::make_unique(std::move(keymap)); -} - std::unique_ptr CXkbcommonKeymap::CreateXkbStateFromKeymap(xkb_keymap* keymap) { std::unique_ptr state{xkb_state_new(keymap), XkbStateDeleter()}; diff --git a/xbmc/windowing/wayland/XkbcommonKeymap.h b/xbmc/windowing/wayland/XkbcommonKeymap.h index 7daffc830f3ed..0e4da8ed462c7 100644 --- a/xbmc/windowing/wayland/XkbcommonKeymap.h +++ b/xbmc/windowing/wayland/XkbcommonKeymap.h @@ -127,7 +127,6 @@ class CXkbcommonContext * from this function. */ std::unique_ptr KeymapFromString(std::string const& keymap); - std::unique_ptr KeymapFromNames(const std::string &rules, const std::string &model, const std::string &layout, const std::string &variant, const std::string &options); private: struct XkbContextDeleter From 1a1660e939c35b22ca6a9e73e998d7d7fa31ff85 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 15 Oct 2023 17:53:45 +0200 Subject: [PATCH 419/811] [fileitem] Fix mimetype not set/updated properly on loading details. --- xbmc/FileItem.cpp | 21 ++++++++++++++++----- xbmc/FileItem.h | 6 ++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/xbmc/FileItem.cpp b/xbmc/FileItem.cpp index 651500326f64a..8184943a7ae71 100644 --- a/xbmc/FileItem.cpp +++ b/xbmc/FileItem.cpp @@ -1629,6 +1629,16 @@ void CFileItem::FillInMimeType(bool lookup /*= true*/) } } +void CFileItem::UpdateMimeType(bool lookup /*= true*/) +{ + //! @todo application/octet-stream might actually have been set by a web lookup. Currently we + //! cannot distinguish between set as fallback only (see FillInMimeType) or as an actual value. + if (m_mimetype == "application/octet-stream") + m_mimetype.clear(); + + FillInMimeType(lookup); +} + void CFileItem::SetMimeTypeForInternetFile() { if (m_doContentLookup && IsInternetStream()) @@ -1768,6 +1778,7 @@ void CFileItem::UpdateInfo(const CFileItem &item, bool replaceLabels /*=true*/) if (!item.GetArt().empty()) SetArt(item.GetArt()); AppendProperties(item); + UpdateMimeType(); } void CFileItem::MergeInfo(const CFileItem& item) @@ -1836,6 +1847,7 @@ void CFileItem::MergeInfo(const CFileItem& item) SetArt(item.GetArt()); } AppendProperties(item); + UpdateMimeType(); } void CFileItem::SetFromVideoInfoTag(const CVideoInfoTag &video) @@ -3781,10 +3793,9 @@ bool CFileItem::LoadDetails() if (ret) { - m_videoInfoTag = tag.release(); - m_strDynPath = m_videoInfoTag->m_strFileNameAndPath; + const CFileItem loadedItem{*tag}; + UpdateInfo(loadedItem); } - return ret; } @@ -3816,8 +3827,8 @@ bool CFileItem::LoadDetails() auto tag{std::make_unique()}; if (db.LoadVideoInfo(GetDynPath(), *tag)) { - m_videoInfoTag = tag.release(); - m_strDynPath = m_videoInfoTag->m_strFileNameAndPath; + const CFileItem loadedItem{*tag}; + UpdateInfo(loadedItem); return true; } diff --git a/xbmc/FileItem.h b/xbmc/FileItem.h index 464ce10a3548a..d5fdc4b3f8407 100644 --- a/xbmc/FileItem.h +++ b/xbmc/FileItem.h @@ -636,6 +636,12 @@ class CFileItem : */ void Initialize(); + /*! \brief Recalculate item's MIME type if it is not set or is set to "application/octet-stream". + Resolve the MIME type based on file extension or a web lookup. + \sa FillInMimeType + */ + void UpdateMimeType(bool lookup = true); + /*! \brief Return the current resume point for this item. \return The resume point. From 1768438a9a79c6e2f697512a21be96c86f09eb49 Mon Sep 17 00:00:00 2001 From: Rechi Date: Sun, 15 Oct 2023 18:00:46 +0200 Subject: [PATCH 420/811] [clang-tidy] performance-faster-string-find --- xbmc/video/VideoChapterImageFileLoader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/video/VideoChapterImageFileLoader.cpp b/xbmc/video/VideoChapterImageFileLoader.cpp index 5cb0704ff47e4..49bfd2dc063cd 100644 --- a/xbmc/video/VideoChapterImageFileLoader.cpp +++ b/xbmc/video/VideoChapterImageFileLoader.cpp @@ -30,7 +30,7 @@ std::unique_ptr VIDEO::CVideoChapterImageFileLoader::Load( // "goofy" chapter path because these paths don't yet conform to 'image://' path standard // 10 = length of "chapter://" string prefix from GUIDialogVideoBookmarks - size_t lastSlashPos = goofyChapterPath.rfind("/"); + size_t lastSlashPos = goofyChapterPath.rfind('/'); std::string cleanname = goofyChapterPath.substr(10, lastSlashPos - 10); int chapterNum = 0; From df4389f87d5b5c771013ab3a64dd3cfb3a9e8c50 Mon Sep 17 00:00:00 2001 From: Rechi Date: Sun, 15 Oct 2023 18:00:46 +0200 Subject: [PATCH 421/811] [clang-tidy] performance-no-automatic-move --- xbmc/favourites/ContextMenus.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp index 1ed7b54931696..544bec2a55e0d 100644 --- a/xbmc/favourites/ContextMenus.cpp +++ b/xbmc/favourites/ContextMenus.cpp @@ -83,7 +83,7 @@ namespace { std::shared_ptr ResolveFavouriteItem(const CFileItem& item) { - const std::shared_ptr targetItem{ + std::shared_ptr targetItem{ CServiceBroker::GetFavouritesService().ResolveFavourite(item)}; if (targetItem) targetItem->SetProperty("hide_add_remove_favourite", CVariant{true}); From aba2f8100aa084b6721390b1562ee7761bbffa65 Mon Sep 17 00:00:00 2001 From: Rechi Date: Sun, 15 Oct 2023 18:00:46 +0200 Subject: [PATCH 422/811] [clang-tidy] performance-unnecessary-copy-initialization --- xbmc/music/MusicUtils.cpp | 2 +- xbmc/pvr/guilib/PVRGUIActionsUtils.cpp | 2 +- xbmc/video/VideoUtils.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/xbmc/music/MusicUtils.cpp b/xbmc/music/MusicUtils.cpp index fe32d288a60e7..bbd1ba5ac4c4a 100644 --- a/xbmc/music/MusicUtils.cpp +++ b/xbmc/music/MusicUtils.cpp @@ -860,7 +860,7 @@ bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) if (!item.IsSmartPlayList()) return false; - const std::string path{item.GetPath()}; + const std::string& path{item.GetPath()}; const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()}; return ((profileManager->GetUserDataItem("PartyMode.xsp") == path) && !CFileUtils::Exists(path)); } diff --git a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp index 24d918f7f177c..907c645dd36c8 100644 --- a/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp +++ b/xbmc/pvr/guilib/PVRGUIActionsUtils.cpp @@ -59,7 +59,7 @@ std::shared_ptr LoadRecordingFileOrFolderItem(const CFileItem& item) CFileItemList items; if (XFILE::CDirectory::GetDirectory(parentPath, items, "", XFILE::DIR_FLAG_DEFAULTS)) { - const std::string path{item.GetPath()}; + const std::string& path{item.GetPath()}; const auto it = std::find_if(items.cbegin(), items.cend(), [&path](const auto& entry) { return entry->GetPath() == path; }); if (it != items.cend()) diff --git a/xbmc/video/VideoUtils.cpp b/xbmc/video/VideoUtils.cpp index e6b56398494bf..66a78477cb9dc 100644 --- a/xbmc/video/VideoUtils.cpp +++ b/xbmc/video/VideoUtils.cpp @@ -527,7 +527,7 @@ bool IsNonExistingUserPartyModePlaylist(const CFileItem& item) if (!item.IsSmartPlayList()) return false; - const std::string path{item.GetPath()}; + const std::string& path{item.GetPath()}; const auto profileManager{CServiceBroker::GetSettingsComponent()->GetProfileManager()}; return ((profileManager->GetUserDataItem("PartyMode-Video.xsp") == path) && !CFileUtils::Exists(path)); @@ -820,7 +820,7 @@ ResumeInformation GetStackPartResumeInformation(const CFileItem& item, unsigned if (item.IsStack()) { - const std::string path = item.GetDynPath(); + const std::string& path = item.GetDynPath(); if (URIUtils::IsDiscImageStack(path)) { // disc image stack From 1c692955a8722b6df1a5f8f8c3f0b45591e6d12a Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 10 Oct 2023 19:44:53 -0700 Subject: [PATCH 423/811] CCompileInfo: add ability to get enabled gl interfaces Signed-off-by: Lukas Rusak --- CMakeLists.txt | 2 ++ cmake/modules/FindEGL.cmake | 3 +++ cmake/modules/FindGLX.cmake | 3 +++ xbmc/CompileInfo.cpp.in | 5 +++++ xbmc/CompileInfo.h | 1 + 5 files changed, 14 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index a7acd26853877..c07106fb5c676 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -122,6 +122,7 @@ endif() core_find_git_rev(APP_SCMID FULL) set(AUDIO_BACKENDS_LIST "" CACHE STRING "Available audio backends") +set(GL_INTERFACES_LIST "" CACHE STRING "Available GL interfaces") # Dynamically loaded libraries built with the project add_custom_target(${APP_NAME_LC}-libraries) @@ -280,6 +281,7 @@ add_custom_command(OUTPUT ${CORE_BUILD_DIR}/xbmc/CompileInfo.cpp -DCORE_SYSTEM_NAME=${CORE_SYSTEM_NAME} -DCORE_PLATFORM_NAME_LC="${CORE_PLATFORM_NAME_LC}" -DAUDIO_BACKENDS="${AUDIO_BACKENDS_LIST}" + -DGL_INTERFACES="${GL_INTERFACES_LIST}" -DCORE_BUILD_DIR=${CORE_BUILD_DIR} -DCMAKE_BINARY_DIR=${CMAKE_BINARY_DIR} -DARCH_DEFINES="${ARCH_DEFINES}" diff --git a/cmake/modules/FindEGL.cmake b/cmake/modules/FindEGL.cmake index a8db654e12368..b62afdcdad954 100644 --- a/cmake/modules/FindEGL.cmake +++ b/cmake/modules/FindEGL.cmake @@ -29,6 +29,9 @@ if(NOT TARGET EGL::EGL) VERSION_VAR EGL_VERSION) if(EGL_FOUND) + list(APPEND GL_INTERFACES_LIST egl egl-pb) + set(GL_INTERFACES_LIST ${GL_INTERFACES_LIST} PARENT_SCOPE) + include(CheckIncludeFiles) check_include_files("EGL/egl.h;EGL/eglext.h;EGL/eglext_angle.h" HAVE_EGLEXTANGLE) diff --git a/cmake/modules/FindGLX.cmake b/cmake/modules/FindGLX.cmake index 066cbb8b40cf9..56555d4c44df1 100644 --- a/cmake/modules/FindGLX.cmake +++ b/cmake/modules/FindGLX.cmake @@ -28,6 +28,9 @@ find_package_handle_standard_args(GLX REQUIRED_VARS GLX_LIBRARY GLX_INCLUDE_DIR) if(GLX_FOUND) + list(APPEND GL_INTERFACES_LIST glx) + set(GL_INTERFACES_LIST ${GL_INTERFACES_LIST} PARENT_SCOPE) + set(GLX_LIBRARIES ${GLX_LIBRARY}) set(GLX_INCLUDE_DIRS ${GLX_INCLUDE_DIR}) set(GLX_DEFINITIONS -DHAS_GLX=1) diff --git a/xbmc/CompileInfo.cpp.in b/xbmc/CompileInfo.cpp.in index 6fb14de995dd6..30571fd881fd5 100644 --- a/xbmc/CompileInfo.cpp.in +++ b/xbmc/CompileInfo.cpp.in @@ -111,6 +111,11 @@ std::vector CCompileInfo::GetAvailableAudioBackends() return StringUtils::Split("@AUDIO_BACKENDS@", ' '); } +std::vector CCompileInfo::GetAvailableGlInterfaces() +{ + return StringUtils::Split("@GL_INTERFACES@", ' '); +} + // Return version of python built against as format MAJOR.MINOR std::string CCompileInfo::GetPythonVersion() { diff --git a/xbmc/CompileInfo.h b/xbmc/CompileInfo.h index 32542b1edbca1..5556b17d25318 100644 --- a/xbmc/CompileInfo.h +++ b/xbmc/CompileInfo.h @@ -32,6 +32,7 @@ class CCompileInfo static const char* GetVersionCode(); static std::vector GetAvailableWindowSystems(); static std::vector GetAvailableAudioBackends(); + static std::vector GetAvailableGlInterfaces(); static std::vector LoadOfficialRepoInfos(); static std::string GetPythonVersion(); static std::vector GetWebserverExtraWhitelist(); From a503b32dd6e7bdc4712b011f2ac212b45f1bf9d3 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 10 Oct 2023 19:48:07 -0700 Subject: [PATCH 424/811] CAppParamParserLinux: add --gl-interface= switch Signed-off-by: Lukas Rusak --- xbmc/application/AppParams.h | 4 +++ xbmc/platform/linux/AppParamParserLinux.cpp | 29 ++++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/xbmc/application/AppParams.h b/xbmc/application/AppParams.h index c96b95f5b7bee..6999249719b68 100644 --- a/xbmc/application/AppParams.h +++ b/xbmc/application/AppParams.h @@ -53,6 +53,9 @@ class CAppParams std::string_view GetAudioBackend() const { return m_audioBackend; } void SetAudioBackend(std::string_view audioBackend) { m_audioBackend = audioBackend; } + std::string_view GetGlInterface() const { return m_glInterface; } + void SetGlInterface(const std::string& glInterface) { m_glInterface = glInterface; } + CFileItemList& GetPlaylist() const { return *m_playlist; } /*! @@ -86,6 +89,7 @@ class CAppParams std::string m_windowing; std::string m_logTarget; std::string m_audioBackend; + std::string m_glInterface; std::unique_ptr m_playlist; diff --git a/xbmc/platform/linux/AppParamParserLinux.cpp b/xbmc/platform/linux/AppParamParserLinux.cpp index fcc2ddde4eba7..ec83e5f470f30 100644 --- a/xbmc/platform/linux/AppParamParserLinux.cpp +++ b/xbmc/platform/linux/AppParamParserLinux.cpp @@ -22,6 +22,7 @@ namespace std::vector availableWindowSystems = CCompileInfo::GetAvailableWindowSystems(); std::array availableLogTargets = {"console"}; std::vector availableAudioBackends = CCompileInfo::GetAvailableAudioBackends(); +std::vector availableGlInterfaces = CCompileInfo::GetAvailableGlInterfaces(); constexpr const char* windowingText = R"""( @@ -41,6 +42,12 @@ Selected audio backend not available: {} Available audio backends: {} )"""; +constexpr const char* glInterfaceText = + R"""( +Selected GL interface not available: {} + Available GL interfaces: {} +)"""; + constexpr const char* helpText = R"""( Linux Specific Arguments: @@ -50,6 +57,8 @@ Linux Specific Arguments: Available log targets are: {} --audio-backend= Select which audio backend to use. Available audio backends are: {} + --gl-interface= Select which GL interface to use (X11 only). + Available GL interfaces are: {} )"""; } // namespace @@ -107,6 +116,23 @@ void CAppParamParserLinux::ParseArg(const std::string& arg) exit(0); } } + else if (arg.find("--gl-interface=") != std::string::npos) + { + const auto argValue = arg.substr(15); + const auto it = + std::find(availableGlInterfaces.cbegin(), availableGlInterfaces.cend(), argValue); + if (it != availableGlInterfaces.cend()) + { + GetAppParams()->SetGlInterface(argValue); + } + else + { + std::cout << StringUtils::Format(glInterfaceText, argValue, + StringUtils::Join(availableGlInterfaces, ", ")); + + exit(0); + } + } } void CAppParamParserLinux::DisplayHelp() @@ -115,5 +141,6 @@ void CAppParamParserLinux::DisplayHelp() std::cout << StringUtils::Format(helpText, StringUtils::Join(availableWindowSystems, ", "), StringUtils::Join(availableLogTargets, ", "), - StringUtils::Join(availableAudioBackends, ", ")); + StringUtils::Join(availableAudioBackends, ", "), + StringUtils::Join(availableGlInterfaces, ", ")); } From e85777502d12180a4f45cc137f7991b1157b178d Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 10 Oct 2023 19:49:25 -0700 Subject: [PATCH 425/811] CWinSystemX11GLContext: select gl interface from command line switch Signed-off-by: Lukas Rusak --- xbmc/windowing/X11/WinSystemX11GLContext.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/xbmc/windowing/X11/WinSystemX11GLContext.cpp b/xbmc/windowing/X11/WinSystemX11GLContext.cpp index 7b3e9e40fec43..3d838632cc840 100644 --- a/xbmc/windowing/X11/WinSystemX11GLContext.cpp +++ b/xbmc/windowing/X11/WinSystemX11GLContext.cpp @@ -10,8 +10,10 @@ #include "GLContextEGL.h" #include "OptionalsReg.h" +#include "ServiceBroker.h" #include "VideoSyncOML.h" #include "X11DPMSSupport.h" +#include "application/AppParams.h" #include "application/ApplicationComponents.h" #include "application/ApplicationSkinHandling.h" #include "cores/RetroPlayer/process/X11/RPProcessInfoX11.h" @@ -268,9 +270,10 @@ bool CWinSystemX11GLContext::RefreshGLContext(bool force) std::transform(gpuvendor.begin(), gpuvendor.end(), gpuvendor.begin(), ::tolower); bool isNvidia = (gpuvendor.compare(0, 6, "nvidia") == 0); bool isIntel = (gpuvendor.compare(0, 5, "intel") == 0); - std::string gli = (getenv("KODI_GL_INTERFACE") != nullptr) ? getenv("KODI_GL_INTERFACE") : ""; - if (gli != "GLX") + std::string_view gli = CServiceBroker::GetAppParams()->GetGlInterface(); + + if (gli != "glx") { m_pGLContext = new CGLContextEGL(m_dpy, EGL_OPENGL_API); success = m_pGLContext->Refresh(force, m_screen, m_glWindow, m_newGlContext); @@ -289,11 +292,11 @@ bool CWinSystemX11GLContext::RefreshGLContext(bool force) VAAPIRegister(m_vaapiProxy.get(), deepColor); return true; } - if (isIntel || gli == "EGL") + if (isIntel || gli == "egl") return true; } } - else if (gli == "EGL_PB") + else if (gli == "egl-pb") { success = m_pGLContext->CreatePB(); if (success) From 742ad8f252548ebbed195ee2cd0e1dd33aa51b47 Mon Sep 17 00:00:00 2001 From: Lukas Rusak Date: Tue, 10 Oct 2023 19:50:10 -0700 Subject: [PATCH 426/811] kodi.sh.in: add wrapper for deprecated KODI_GL_INTERFACE env variable Signed-off-by: Lukas Rusak --- tools/Linux/kodi.sh.in | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tools/Linux/kodi.sh.in b/tools/Linux/kodi.sh.in index 5dc1dd52600db..d68e23516d808 100644 --- a/tools/Linux/kodi.sh.in +++ b/tools/Linux/kodi.sh.in @@ -189,6 +189,20 @@ if [ -n "${KODI_AE_SINK}" ]; then fi fi +if [ -n "${KODI_GL_INTERFACE}" ]; then + + echo "KODI_GL_INTERFACE env variable is deprecated and will be removed in the future." + echo "Use the --gl-interface command line switch instead." + + if [ "${KODI_GL_INTERFACE}" = "GLX" ]; then + ENV_ARGS="--gl-interface=glx" + elif [ "${KODI_GL_INTERFACE}" = "EGL" ]; then + ENV_ARGS="--gl-interface=egl" + elif [ "${KODI_GL_INTERFACE}" = "EGL_PB" ]; then + ENV_ARGS="--gl-interface=egl-pb" + fi +fi + LOOP=1 while [ $(( $LOOP )) = "1" ] do From 08064ed22d782d7a495febff3ddc3a6708eacb04 Mon Sep 17 00:00:00 2001 From: Wolfgang Haupt Date: Mon, 16 Oct 2023 08:37:06 +0200 Subject: [PATCH 427/811] Fix websocket pong and probably other messages When "handle" gets called it already creates a perfectly fine "answer"-frame. Calling the Send method of CWebSocketClient will wrap it into another TextFrame which may result in an 1007 Illegal UTF-8 Sequence or possibly other invalid websocket data frames. We need to call Send of CTCPClient instead which will just transfer the frame without further modification. --- xbmc/network/TCPServer.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/xbmc/network/TCPServer.cpp b/xbmc/network/TCPServer.cpp index cd055c4adee8a..75334245b429e 100644 --- a/xbmc/network/TCPServer.cpp +++ b/xbmc/network/TCPServer.cpp @@ -719,7 +719,8 @@ void CTCPServer::CWebSocketClient::PushBuffer(CTCPServer *host, const char *buff if (send) { for (unsigned int index = 0; index < frames.size(); index++) - Send(frames.at(index)->GetFrameData(), (unsigned int)frames.at(index)->GetFrameLength()); + CTCPClient::Send(frames.at(index)->GetFrameData(), + static_cast(frames.at(index)->GetFrameLength())); } else { From 7a7e3aa26647e7b0127093a4cf33e6b90ab79e8e Mon Sep 17 00:00:00 2001 From: Wolfgang Haupt Date: Thu, 5 Oct 2023 07:54:26 +0200 Subject: [PATCH 428/811] Properly add application data to the websocket pong The RFC states that pong should reply with the same application data that is received in the ping. Our code however threw away the application data instead. We need to properly set application data length so that it actually gets appended to the send buffer. --- xbmc/network/websocket/WebSocket.cpp | 2 +- xbmc/network/websocket/WebSocket.h | 2 +- xbmc/network/websocket/WebSocketV8.h | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/xbmc/network/websocket/WebSocket.cpp b/xbmc/network/websocket/WebSocket.cpp index ecec4efeee9a5..5051c27f22f06 100644 --- a/xbmc/network/websocket/WebSocket.cpp +++ b/xbmc/network/websocket/WebSocket.cpp @@ -325,7 +325,7 @@ const CWebSocketMessage* CWebSocket::Handle(const char* &buffer, size_t &length, case WebSocketPing: msg = GetMessage(); if (msg != NULL) - msg->AddFrame(Pong(frame->GetApplicationData())); + msg->AddFrame(Pong(frame->GetApplicationData(), frame->GetLength())); break; case WebSocketConnectionClose: diff --git a/xbmc/network/websocket/WebSocket.h b/xbmc/network/websocket/WebSocket.h index 8901edeca52dd..9b7ca7ff38614 100644 --- a/xbmc/network/websocket/WebSocket.h +++ b/xbmc/network/websocket/WebSocket.h @@ -121,7 +121,7 @@ class CWebSocket virtual const CWebSocketMessage* Handle(const char* &buffer, size_t &length, bool &send); virtual const CWebSocketMessage* Send(WebSocketFrameOpcode opcode, const char* data = NULL, uint32_t length = 0); virtual const CWebSocketFrame* Ping(const char* data = NULL) const = 0; - virtual const CWebSocketFrame* Pong(const char* data = NULL) const = 0; + virtual const CWebSocketFrame* Pong(const char* data, uint32_t length) const = 0; virtual const CWebSocketFrame* Close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "") = 0; virtual void Fail() = 0; diff --git a/xbmc/network/websocket/WebSocketV8.h b/xbmc/network/websocket/WebSocketV8.h index d86688cdcf4d8..021497527980a 100644 --- a/xbmc/network/websocket/WebSocketV8.h +++ b/xbmc/network/websocket/WebSocketV8.h @@ -19,7 +19,10 @@ class CWebSocketV8 : public CWebSocket bool Handshake(const char* data, size_t length, std::string &response) override; const CWebSocketFrame* Ping(const char* data = NULL) const override { return new CWebSocketFrame(WebSocketPing, data); } - const CWebSocketFrame* Pong(const char* data = NULL) const override { return new CWebSocketFrame(WebSocketPong, data); } + const CWebSocketFrame* Pong(const char* data, uint32_t length) const override + { + return new CWebSocketFrame(WebSocketPong, data, length); + } const CWebSocketFrame* Close(WebSocketCloseReason reason = WebSocketCloseNormal, const std::string &message = "") override; void Fail() override; From f6f5cd9352edac3a7f49f0879149a3c3f96d6071 Mon Sep 17 00:00:00 2001 From: CrystalP Date: Mon, 16 Oct 2023 12:31:16 -0400 Subject: [PATCH 429/811] [rendering] use default luminance for tone mapping of streams with bad metadata --- .../VideoShaders/WinVideoFilter.cpp | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp index 83f146b24873a..7d223d3bdea3c 100644 --- a/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp +++ b/xbmc/cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.cpp @@ -224,30 +224,42 @@ void COutputShader::ApplyEffectParameters(CD3DEffect &effect, unsigned sourceWid float COutputShader::GetLuminanceValue() const { - float lum1 = 400.0f; // default for bad quality HDR-PQ sources (with no metadata) - float lum2 = lum1; - float lum3 = lum1; + // default for bad quality HDR-PQ sources (missing or invalid metadata) + const float defaultLuminance = 400.0f; + float lum1 = defaultLuminance; + + unsigned int maxLuminance = static_cast(defaultLuminance); + + if (m_hasDisplayMetadata && m_displayMetadata.has_luminance && + m_displayMetadata.max_luminance.den) + { + const uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den; + + if (lum > 0) + maxLuminance = lum; + } if (m_hasLightMetadata) { - uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den; - if (m_lightMetadata.MaxCLL >= lum) + float lum2; + + if (m_lightMetadata.MaxCLL >= maxLuminance) { - lum1 = static_cast(lum); + lum1 = static_cast(maxLuminance); lum2 = static_cast(m_lightMetadata.MaxCLL); } else { lum1 = static_cast(m_lightMetadata.MaxCLL); - lum2 = static_cast(lum); + lum2 = static_cast(maxLuminance); } - lum3 = static_cast(m_lightMetadata.MaxFALL); + const float lum3 = static_cast(m_lightMetadata.MaxFALL); + lum1 = (lum1 * 0.5f) + (lum2 * 0.2f) + (lum3 * 0.3f); } else if (m_hasDisplayMetadata && m_displayMetadata.has_luminance) { - uint16_t lum = m_displayMetadata.max_luminance.num / m_displayMetadata.max_luminance.den; - lum1 = static_cast(lum); + lum1 = static_cast(maxLuminance); } return lum1; From f2c831eaad896d77dd902907a4c3af914fa8328b Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Sun, 15 Oct 2023 19:24:25 +0200 Subject: [PATCH 430/811] [favourites] CFavouritesService: Add missing locks. --- xbmc/favourites/FavouritesService.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xbmc/favourites/FavouritesService.cpp b/xbmc/favourites/FavouritesService.cpp index ccfe879d1b61a..ffff4c6d9dc92 100644 --- a/xbmc/favourites/FavouritesService.cpp +++ b/xbmc/favourites/FavouritesService.cpp @@ -147,6 +147,8 @@ CFavouritesService::CFavouritesService(std::string userDataFolder) : m_favourite void CFavouritesService::ReInit(std::string userDataFolder) { + std::unique_lock lock(m_criticalSection); + m_userDataFolder = std::move(userDataFolder); m_favourites.Clear(); m_targets.clear(); @@ -288,6 +290,8 @@ std::shared_ptr CFavouritesService::ResolveFavourite(const CFileItem& { if (item.IsFavourite()) { + std::unique_lock lock(m_criticalSection); + const auto it = m_targets.find(item.GetPath()); if (it != m_targets.end()) return (*it).second; From 04411840938ffece6fe54f66d2f36b68cd89efb6 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 16 Oct 2023 18:55:17 +0200 Subject: [PATCH 431/811] [favourites] Fix FAVOURITES_UTILS::MoveItem to ensure item gets not destructed during vector content adjustment. --- xbmc/favourites/FavouritesUtils.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/xbmc/favourites/FavouritesUtils.cpp b/xbmc/favourites/FavouritesUtils.cpp index 5c2bdb3dd8bce..ef3844fdf2b5a 100644 --- a/xbmc/favourites/FavouritesUtils.cpp +++ b/xbmc/favourites/FavouritesUtils.cpp @@ -87,15 +87,13 @@ bool MoveItem(CFileItemList& items, const std::shared_ptr& item, int int nextItem = (itemPos + amount) % items.Size(); if (nextItem < 0) { - const auto& itemToAdd(item); - items.Remove(itemPos); - items.Add(itemToAdd); + items.Add(item); + items.Remove(0); } else if (nextItem == 0) { - const auto& itemToAdd(item); - items.Remove(itemPos); - items.AddFront(itemToAdd, 0); + items.AddFront(item, 0); + items.Remove(itemPos + 1); } else { From 11e33f4990450d2748aebc683479df0bdd740e25 Mon Sep 17 00:00:00 2001 From: ksooo <3226626+ksooo@users.noreply.github.com> Date: Mon, 16 Oct 2023 19:08:27 +0200 Subject: [PATCH 432/811] [favourites] Fix CFavouriteContextMenuAction::Execute to return false if DoExecute fails. --- xbmc/favourites/ContextMenus.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/xbmc/favourites/ContextMenus.cpp b/xbmc/favourites/ContextMenus.cpp index 1ed7b54931696..49f4a7f65d3cc 100644 --- a/xbmc/favourites/ContextMenus.cpp +++ b/xbmc/favourites/ContextMenus.cpp @@ -31,14 +31,14 @@ bool CFavouriteContextMenuAction::Execute(const std::shared_ptr& item { CFileItemList items; CServiceBroker::GetFavouritesService().GetAll(items); - for (const auto& favourite : items) - { - if (favourite->GetPath() == item->GetPath()) - { - if (DoExecute(items, favourite)) - return CServiceBroker::GetFavouritesService().Save(items); - } - } + + const auto it = std::find_if(items.cbegin(), items.cend(), [&item](const auto& favourite) { + return favourite->GetPath() == item->GetPath(); + }); + + if ((it != items.cend()) && DoExecute(items, *it)) + return CServiceBroker::GetFavouritesService().Save(items); + return false; } From 300bb0a21d25a95083fbbea179dd8de26aaede15 Mon Sep 17 00:00:00 2001 From: David Vukovic Date: Tue, 26 Sep 2023 23:53:46 +0200 Subject: [PATCH 433/811] [Eventlog] fix eventlog message --- xbmc/video/VideoInfoScanner.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/xbmc/video/VideoInfoScanner.cpp b/xbmc/video/VideoInfoScanner.cpp index 835d9e86180f5..8f6b18849d8f6 100644 --- a/xbmc/video/VideoInfoScanner.cpp +++ b/xbmc/video/VideoInfoScanner.cpp @@ -501,11 +501,16 @@ namespace VIDEO auto eventLog = CServiceBroker::GetEventLog(); if (eventLog) + { + const std::string itemlogpath = (info2->Content() == CONTENT_TVSHOWS) + ? CURL::GetRedacted(pItem->GetPath()) + : URIUtils::GetFileName(pItem->GetPath()); + eventLog->Add(EventPtr(new CMediaLibraryEvent( mediaType, pItem->GetPath(), 24145, - StringUtils::Format(g_localizeStrings.Get(24147), mediaType, - URIUtils::GetFileName(pItem->GetPath())), - pItem->GetArt("thumb"), CURL::GetRedacted(pItem->GetPath()), EventLevel::Warning))); + StringUtils::Format(g_localizeStrings.Get(24147), mediaType, itemlogpath), + EventLevel::Warning))); + } } pURL = NULL; From e8566e72c58944518e6477d25c61b2b2a203f0dd Mon Sep 17 00:00:00 2001 From: fuzzard Date: Sun, 15 Oct 2023 15:26:50 +1000 Subject: [PATCH 434/811] [Omega] Beta1 --- media/splash.jpg | Bin 380672 -> 379662 bytes media/splash_webOS.png | Bin 146198 -> 834147 bytes tools/Linux/kodi.metainfo.xml.in | 1 + version.txt | 6 +++--- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/media/splash.jpg b/media/splash.jpg index da548b33d98a3ad9604a25fecedc48ce770f5435..c0a2e4be86a24afebfc962605abba9273be6a701 100644 GIT binary patch literal 379662 zcmY(r3tUY3`v-pJbjq2qW~N#zmu5~=lD1aDy47i%GNU52E-P}$$~Cg?U8Z(0wWygW z#VQH8%l#4|s}fS~*N}uHsiYhK_ow~-fB)Bi@**{J&gb$x@8|uzpXYPFKm7iZ`pIkM z+&?Hvqj98I>i>Iv|3FEFWwYk5qC^yfANx?$_m7mlXK?7u*;Mc){6u6zSurMN=A9|( z>H&(X8AH(_mXv8xr|(ZF9c4n37ydReH8C+UHDydp7*jJ-@-k!1I^bo-vSM>{b1~bg z{q+NRN&fF8GGQ1qGqX-C+e!9ArypdNfBx??|G#g(U!kNd^J@pK ziAYM(Qjv*N^!=)cr9>hTMVsKS|GminI$&t5nEVF6_`ly^inOVj3DZGB(fAJ2FkxsB zCPPtDk%X2jWG2in{Y*VA%y`anv8$E;^dCE@`rEqMb=3~APd|D|IZ#SbjEO1J!3-a! z%}mImDQIaw6N&s6p79?mbM>6D#K2kfpT1Ky&ek;ga+sxC_Do~UcxB0e#VIulb&p?xNlRMF3K?2ILn zXoGxb_Aj3)C5BmwLYA#)$2{|7`@)tKxJl0#S$rAyJ)$+ z!0Yuo-G*3+L{Ax{%50Iin~ali3R*_X{?8OMoeTW>+D=XH#4 ztVog|7D-u2s)Un?Y&kxJ^-ZO4dlP|CF@hj(|BGfqSglUYFa@y&nrPv)>?uIw0 zRKX0dE)+|wF*Hsh<1lDiVao|BLB*J98A`3z@@nHG#yE-h)k-O`SR%2uP~wA(V7%h( z*UgP-zXzjKr3UC37E7TODE~?NSUb#4ujMy(li2ctKnoa*To8B_&ykVIRkXLCh}$V; zwK7L6fHpxYWm#4$5lh60yo%u%nikXtgVj7%m(^+2I+oFE*-qM6sRZwn7+_w}EH;6P zl^R4cO2(AFC5!24r=x_z3`!!Aid;300?+Y`l2>UQjjuO~u(0xdQYn?bPbv|MGF+9s zQZ?PL8^_7K!j!llqvGY*2Haj{?r8XRG!&Ff!O0aI zCzml3IhBCx2=;Vp&WN+$Q^m5ah=xJfR%;&DCOiw0*#HpZ?TMwoMT`gaaROJ<0SNP zpzUE|!D=0)=JmQlDV886Q(*W$TbPI1XjV1~D+E-ML}RlsZY(3`rBVPvER@Rj3*-7U zZM-T7N}93vGUfGZrgJx$!j%i&*p0(3VE4QQj9mvKV+s;jHU*o(DK|?L%odSrNI{BB z;W>kriO7D*z5KBRGLdFFhr4hhH*a;KVWwP0xzQNBQZU1+8(^L0qhOa}sce);Oc_KH ztwC&%E0Z|4U_JJXSb}ey572|Sx`?cTRANo8FLyH&{3n$dB_fV&4$WW&#IjUM0ZW66 zW1eF3QTPO<)f%X1E579ErT2oxTv+~SF{}4*8XBfuRl=ZY?^=epR z;Z9o)AVc%^eiDhfk0b#G1^bpHa7S5Int45)xTlJ;-(Y|<0?5Qv`ms@B77hs$RAx!c z`yw*vVUJYwE;)x8$Hh<~dREW#+S`;Q72t+phft**VY6g>+IH{RrG%3Ke)JYTB3x9G zBD1j91G{)NOjV8LP&0bIvjn%1@61e+!^Si-lu05i2Bl&`3bZhJUZBkq{BL8p32i0&Ast_y-+x#rT@%b+8|{ zvjoT^*)u`Li8LVxtt>cfmHMEU6)niIXV{DBm&M0Kw)Qs?*l5u%4!D427X+q{6W*u_ z7CLi?93yL;IIrWIigI(z06e6RAjwZNbeA0H+j* z#DrIwX-(|_d$+r}(JHgfuqbY)2-auqO7%H5Nvl<>DK)_gGF={U+&LP?6OG*h_F()X z>=`Y>ci26cpk@U2idS+eWVPX-Fi))n_b1!N(F(aj4$PCwrQ~}I9=PDDjMLFdu@lxA zJ}ei22y*p7KBQ2(iwaTeW339)In7MVAMtKroPwh`H()FFqcbc^3)dyatd~+oA9xD> z18+ef5F0oQjNA-&;OLY9J))0ZJ8lv%1~1=KniEN2D>7QjQI?x=O=EVd9-%_VvOK~Z zfDuSkVm*35XE7!M3y!AyO8=TJx3f~gtmJSi2PZ}uLePNK6=qsWX>5fYP)u1Sk@y-H zZDF4<$v0L4`)fx{5*H{=ph8$Rs|C~wh4#rDuj1_m0KJ+iG5j|_csbS+7lTm{cY-x6 zbj_I_@`!jjULqi#AOP49Er6fK7)Hs=wemGc^il)nD#wg$?JsG0cm&3cwMif`NFt_E zq}VaUZ5lWM7nzBL2cj~{;PInGa2|MDY&vcZU(2Qe%MjF;ni6$VA%T43TX$>oFvu(t`~z~U4%C-EGI z%agq!^CT}4%{bS%tMVY6T4CY9?5nCUQRellW*bsnIR=+k zdiwz73F^V!_d>n5(q$k{poA$md#J$~>zecS8gcarm{;sFf?RAqW(5 z&=mL-zJT&HflMA4hE35G7!r9iEu&8eD2cX^hiJi+c5!^%RYA^&*#~d~u|0eUgE#On z7E?R?4dY>A?TnN30zoF`GX3^91-_x`;-Ak(=pxhP@42#-4Dri9lYA3Ooo10pQ#y6& zEi4#;x&%0pp3Sn#Y+xs`Cj$vkDKfbUuBlWawqk51;5BjP1}eo3IK?|SYZ(}z4xB=V zbz#Q^06AfWBxJB!!V<~8gSimeBT$%uiZRBJ2#ci+p@1}$fY0-5tu7@Ph*8rTPlfOP zIGun&G8h&RR96TH#6`v8oxc|7Shd(rUt$!2TVODPW($e-2*((K;Hx)x1P$f@QJh># z!|5e5g47@c|(_G(E3o)ddKk2B~cBObNV8B(g9A_!StqY!Q%st|=Ki;wDqr{g;ppsjx3O!k6Wb z3JMXAX1HJ-=p$wdv-=-}Qr-zuGmpYvw4e-NB?MbQr5MZ`T*^t=TN-T<9IV#j&RSp_ zG8uNOOu~tjn~_OqoCQA+8EL!m8)IVLitU&Zqbtx8o2kcUdD`&^=z3EtfG>uoD}+x| zdYPlx=oXBKW{o+JAgOT%rxsH^vYa#y9FO&ayD};{&4AQ~6eIuxB~n&&;_*6IC5bU| zH(sCtL9{CPHVgM)0a~DYNRNOPxR4fHfOvpZERH6T0stWr`0H0 zgNe5mORXpQC&*R;@}CqLAsB>aNi$3mHl$c!s>B41Ix=luPCGcXn=VMN-A1e{oB?Yl zl1Soak0a#D3;|L*Hc+oMuDP)3_*?gy_~##%?RfH`Y1oF=Czd)OVZzv%o-2cCnj9E67hFPOoHd1@0;dHIia1+)1R%Y- zv;AFKSsDX`VGwAP;QPTP26#2C@SKU5DY3L7Q3RiZODQ}(iB~Z&4x|>sNJCY5L(4xl z4;HySfAFr!;C)@jb$8gmF2;02(IlU4V9<>N)mdL%8n-hF^FWZZy1 zGL$+gXKM8KJ!q!k18>VJ!Yn-EOGAg$?07qD%&?b;6FfGK<%u%@5J=*RAblbUZc8%` zPHu*ht_4EIW^us?t2!-`Cx#)3J~A`qPD-nn>~&uG#l!Db z#Qn<2CrWLX+#T2_YrJ5{EMaYb4tBjfy|4FtE_kzlGPV2cX4C5xi;G^JSabN~o5t-w z-9{1x^1_3vn-ap1=xAV5IIj^2EYOAl2ti7b5^)5=5~Bn4MUqnL*kiiGK>$@duEHzq zlPpM<0|Wy8#PHrabvyHs`Rt;32S+3WVgv+MoLJl(IqgZ+s`&I4_t|dSEXQkhsqGWT z-JhpW-!|{NStBG3a9)rUn4Yo4Cvo84F@c8z0uN6tZMBIS8y3;~(5HH>P>>*&c9YTy z64(CY1ON>gr}30?me=2cT>xl+TnM2&WHl;g?kvjw5)ucst`L$)LUcL;Hhc=|jjIv? zpRsR`V2Hc04^RTYg_i&Fomz4ENn2Fk4{bTR#|teNl=eH}CG{F0osgcrR@lrP4|{dZ zKgoP|r-M7UX~NDPJmkFadhI@!^)DNi+1TD)-R19h?JI|%pw*GTKoYT*5YR9>-ah~% zWVLs#B(X-h1yGu>1@B8Nwh}ZSR1T?u_xO3r^aNl;LW*2UqL4$Dl0+L47_cP_uOmCr z=n;AP)avIWZ`9{$GE3*(Q zHs`{*{{_-cehj_J>K)k zv$%g^klY1OkG^?ZY#J+30c3OM= zRgg{blY?j7&sQ${POVh7ubrG=0z1mqm7Ho?3jKmds{!45=57Nm|OTG~{<{e2t+ zaBV;bf{%)Z-ysCy4<>*u+>i?Mfw(9~`Yf^a_OfI7FS(0yPc?rWdb7T+w9lSCdxrXD z7&6C?T>G-V)jl!fYlL^>{jY7vqyf&+Qeb4)Ta8VTWlM|Oo>zJTRZI)Fd0i=5+sXIn z+1XoaTiedMdF4{Tvaxv=-Jfd zJ|7~&H$R?h`MA%CyK(cbjhRvx5ivXct$Ww;XJ2gr=TUA3Z{}grR7uX!mf_ddA3OKM z(x(xEA+lIR|1XFTlw zam|U-kH%B66$&$N95TiL_!uGXDux*>Bn0q!{l=dZM1;h9V6FJS4r74AouZRRh!zJ?Mu+)WM-!NqF4SJ%q zBJ2@UCls68XH<}M3JeA}af`W1h@&caBLxf1IOYTS))5OVDDBk@ao6UJ7k8aG_fHp? zMVNh}=FEbp+gGk$7BskY*{QTSM!G#0lUr_%OCW-s++JNGi}=zw@=2RnV$#!;*>Trj zExi#fRVZaJydE}Y_(j@?x#WZk)?_yGDTlc*{B3< zH?Nss=|F9yHpCHA1NE%(XaWi2<{ep4wn(Ew1%gL@#Y4T2tClu@ZHx*nXmMFESoth& zpJM?TvN*m_hML=w;i1;``B7|2+-go@%A+FsD|x@fCtE26}ywUBNI zdxQSaZoVq~O)kJA%3wK5N3kpt7YbI)BBNGj^-Q}LhLCgS!Q6rQe{9`Xkm{#-7wM9l z+2nlw-pbXlVLUpf5PV;10xk;nB?oiwGNV5HLdC-{_m;PHSN>jGe!6c;($CjhC&aI~ zH|+iknWsO5B!y>i0H+XSX*Tm-rJ)auyQZz>yw{TP9+{~w|UDnSi2~VC<)mq1E z1(p>GjE-VaWA}}{TEC^~?tQqT5NT%8z#)Z6(U+!{;-lr?{(k?-?%$kwu2)`$<@h#F zjH~(<^}0ca_@I+b(osSIQWP*=AS|XKQvAZvJV(Pw+i4sTI2h)#HRrm3(qlc5!^6ix zO3VRh(Go5@QSHcZG?-^hMle<#p)%S|W0iP%O|PJ;Q#J!%FL=_k@W3GZ#5>_ppF})w;onJG0=v((AKMb9l z+}(LVp3`Cbpy3A}|M{I#+o3VH zeXT{RBsOS9XdD=gV>=v@kCo6qh)R+Kh^|DjL39LX<#sBwKYkhVt+wh!;CgP6pZ(U) z*0WxQmyPlK+;hyH+5%(;9890rIQ4lnyvy*XQ=U2NH|?t&vMH-{AuVsW0Sl$KzRo>< z+r>5RK98GkOjWpd9MNqtqt*rEM*R-TVvwRiS7^WE-DNJE!V-+;$Wcw8qgWHDBpQ#U7cg%jt*1Q5F zqYAmm(rV10cTFvorz!_0ZyA`sb*uovip})Bf7?KEk;)u5g@dT%l;gFT_ail7C&-3@hLI^njOg|d2YWt2<)U-lcLp(lZ^;ZKj0W|{^wW0Xr?RwA(gRYt%_k(t1T z8PIwaubQyp$g*oK@e5Wd@AT}=g=z9CtD0(Jt0N|Exp(d?<9+De@$Cy~Zz9V*`UHr5y3MqG-EjWn zrZ?w@ZTUqKjcnBZ5H721`KNO5dzq z>`ZHG?IuE*Acn$Sfb(|dTjz5cpRt`ubmck5{?P0Ch9uj`ZwzAUNNipB;^@qPVez3m zPmG$NyEbj`#U;O4Nq4`=dmjz;koPstoz5}84GZle)+-S|=R1||3%+>?bk!g+uj=vG zR8YqXx-J*quG;kP^S6z2?6$|Ac)a>MmH6@PXqWRZpiOO@9UdQ)uGY-zdPA+#zAox= zJ0h*(+XENJLVx9KrzOR&mz{sUz4Cl$mrk$TuBXfm(>ofrX`u`qyIWv&e_BN4!*9!~ zFNB|YG-L7E*-1eicD`#a^E`X&S2CFq^JqcF%jcCSqE{aM3@t9!N(-$CWDA(j1H_P> z>GePO)+LRigp94)K1V0%NU+6k1;hk+n_5rtYwcK}FsDs+$mwiRzC&k$mPGG z39+23Dg^3%x@EFGL@{+|3Mm9+V=Mtf-6nxRkE$f!U=U*t1jl< z4gXH5y%UcQarZddIHXH#Nqw77aRY}`NDC#H?OyalUG;Zr_?O&~Np^Wou6fQ*5kq`* zHQ~ct5rL6TAonL5qrGh$Me5ej?EriCCx?2dCt%bWnVd3-&q!A*vKA%>9O)2WY_!}0 zPeQFpK^g=^66;?=c+}KM+=g}fq1cRw4q=)Oeq^-ZyyHWU-doXoYX0Ybug|>q>-T!} z`N|>BPnG_OpomC`vctnE-^QG3e0sWav_*r*luiD7-?~@Uby-^dQdSUMH{@oeI_#g~ zcvq%$d6dt!<%_DeC%ZNr8T@eB^2FVQNdhzUV@^M>>$$gN>9Fy`Zs&|2<`VF^XYS2# zn*~Q%a7@Tvu|@?NN8};u&fOH zEC<#_uaCCU`UhZM$QGa^rNG~@A=rGCx6uAMzQwO*(4z}o24}rK;~KVSadF1VBPGLw zlGC%rQx42tYYN6Yv`a+6^7}1YZWPrW>D+weaQ?4}^@El)1)Zt?EZJjGG2ovLqw0D- z*tk)*X!x#@+MZj^RL;vVt?Cte{C%5?TB40LaFkR&EH^4K_k3vaz@kn^&&*z1bu0AX z*Wm--BRSy|t`3I<6q4*vTr;2jC*ysrDADPj^n^o};grQMcfCJf-wZ`4T52^G5g@v7 zN7%pq2RlT6ZVj9s@=%Q8vM3$Z{JEy>3pv)Gfh!^iGulTiY6wtBZSBFxdHo0X38#lErO_C*REPGs>7WR{|?jy{0%FgbI z^XJdYai7*>x_tr9dfMFv?V=JAqD3h2Q3h@&G)<;dC#W2MvGXC#}?*A+Rfk~B2q#FdN2tAAo1g- z&fe@PlPO33WbJpD=v=6CptOS`oIIO5ihm6#U6_ldnWZrdQe8EYM?JcjL_>F-g68nF ztylM1`vz{?wEmTccf+)4M91iVY{X=6l=lY?^3J1@``o?Q~Gyx@6#MXt%gXFi5RnzMJk(uLE=Q@)+Url|za#)x)84kqKR-0B_(JlY?z0M)JYMNx^Xzuc zoQQs#Q4T;>G_+$>;qo%iBF276QQcl~bMldV2a9@_Wld3M8tR(Nvr`4Pz;a8A#Ca{ z4uS|02FN$wekpD$tO8Nelt`CtmwdccRaZ0P-MWX>1M}aU?EZX5Me}Q@wxs{Yb>6Le z@6K$>e;P7V95r-t)V}7NF#n+27m6#McgZ{R&Uwn|+U!DFG$^TO;r+!y^Rw?3WXXk* zSLO~`wDV2N5B%D+g`&2B5KO<1)GvHA&5~Z8>3T!N_CYZV}sG5K&fZZ15~n$ z@&*;rA{?+fJq7v(GNMJq0LY45y$wlO_%H82Nk_7=jTM#8|9Mb(aq6}+)dPBu{~VE< zKmSxTOFU2;EjmAR*4~%ryD!YR^6k$T->HCxGZj#8yc3U}&N*#}KQd+RkU&H4wCcJq zxnr(I+|Tz9Ncqj(X5`&J%LYHFm`Umw@QDs~c~vWh{P6JI;6+H}TpP+<9+kGvS#}wc zjt`2~3JSbKhFYxWOyqQk1nqw-uozG@(E`GWY&E-InW%^yq=F`)5`rHY(EBG*29RHa z6Vn5g0%93neVhfY?te4dF?*X|cFCP#p%HaKy&uCApo$@F&`gO(>B(h!wOj7?Up?-H zi>FHz$4@pdvOTuE_3N@P>kqy^)OBq6%dN-OjM-aWy(2lU<>z(#k0u{F`#gXDi3m41 zxIp`l6Ypl@mo(06$v^U{MC4|HkEbBd;iY4 z@A=x=0^%&B`h$%`b^djUk=Q6&6i`Sfb>$ES?EnyQSY5&-KhZT+>LkLRAldlF8cDH+ zLQN536pd|^Lf0et3lu)(7P<0cuRH6SyBx20x;?pjr*js%HjBJ#h-N6GwL2%R8n`@O z^Y0hua?hJPtR*Hd+`i#vwbJc(RW zvD)`=x7B5dHSZP;J^3=^}J%cM+Bw9*8T zIqFA!9OZB?kYWcX`}S@WY#A^Fo1hSx05M=@KsaKDy%#PY7IZJ-JJr>;=TqOXw?A~K zAM=Bs6euHNHlt~U=D6iWc9?i~Sk1(C<3+uMlHxtK$#?Q| z&mXzf)NS_4N2@#kQNDN8=u-+xMq6;d1cVg8U?kQGETSBoolsRYOSZ2)`WRd}8iI#f zU^S*{xH$hGfsl%geh%mkMT;PM5FHpuY0z3B3aHS{_91fDmX>Gt4=h-IHQ)KK7apol zqzeSb<{L{2q2k9=M|OSGa+4NV^8)K>xs}a3{&~}U5QETWW=lm>bcURTRN>xI<1wtV z$a(PMqSq(Re?0VNH4*~78YdbU1|&<*l%j_aau*AzP{{=W8>)hiNeO@=9bX{MAa;Ps znDFfyuY)71E(|SrYoqP>jS&6Onnd$)SFz5$pZ?jSCZzuN;r*Wdo9|Mb)8VbVSwG+d zGtr&|68!mMGcm7u{F&A|Ljpb`?laq^(!FDSv0udwwOWt%a0DArXT!~^m(3;@U#zlU zIp^AiWjB`Dh%%p$sxd}q%!Ee^O0xoHLjpFNs7Fxh6t-v|OZP@^iq~{_D+k3I(;(=} z%jGJuBYNx$5hEqa3ABkIb~gCyZ$q!3?2|nGIqb}u>OBX}5BV6nv}fS5o~D-@ckD&2 zT%hq$2K!Ku@aXCAiQOM{pNax>pIH(clihF6O-Xt0D!}|{v>j?e3%U$?+vLHo2=}eG zE1!LLSm=z;=e|=1cz_^Hnq0gEJ@#GK8EFohY&i!>6$CT8UZE&bv&f@xR<}bupwb@V)e_if65fE4?p}kdc&(VK|7iv6cljw zH(Nw`0nHxbz%_~c%Uf*b9t`jbakM>B|4$d6r4cqiLA)flZtu@Qaqq#32Xn5!Oq#s- z<-6Ohz304GwG)Qh-c7z^bmb{Qror05TLLV z&~Q~^KY3y{a6;ws4nD=pof{{g8gizgxPR%g`+tW(tt9AV;Ls?&AtB-Enx*?+xAYFp zT5Iy6KBB8{-SFN(C@M2ukGCQ4YFT~Dj`cgLfB4&HZJx{e%Eq?2w-)&$sw-6jnhzK# z>LM$|^%5fhh`_W+fqlpKkmXevkYJ55p zIf4<88$G3cJ`S}0R`cO(q%gjszGX*h#cBsH*L@;Ux`>1%dF5#Dk?%;I|jjy0oh}HlVbY2b3KlgHY?j3E><)(`l|9*M?=hds$gdTWf=P2KDG-=en z>6!@KqjuIftTN46z&-&FFjohZob3h17wQ*!5ivocDd>@h>KPrPBwzwPqUfwem-)}46iv*UURX0W6Iy3pr(wtzx_-3@zodSKWcfo zrBLD&td24L`oo;4caa`{BJ(JLGwIDcibZICB+Xq?B^I+zF+(8Z=VP zkTO0Nh+)8%QTSj8Y=Bq#Q4(>(lV=_|EyHFiOw+6oZ%NKjD&%Ik(;puwx{^thmg5-OxL38rn4gAH?cvWM*)_;4iY5l~qj!2^ANCD^o*nr9p*p#w)gx#@eQWZ=h#lR>{PUoC_4w^?Q9h7Owlhu^jfL=O3@FdejW;f`ntlF@@*i<2Le&Ze|` zmTX_S>S~8AH3$x;f4*LSrNO1oz0mhxFO)ra^_x@hW{$GB)9Mj=W4?RIfJM{K+z3;u zm_s@tzyR~Ny1F5Czg)94IcZTJ5m>Kc8H;y7@Z$M7m+gwO&i4U(Q$e z4ym`k;gbGf!MQtLC#s(x-~Q}uQXqY4lvRiQU%%ZtTCw7}d*|P!+oG3b@ET9gDeV4@ zPcv;Nyz#!PNT2TLn-{QTc6s1TU=hbTboR$K2vRHmyTZJiC7&v<(F-Y891K|N7 z08L(6(sm+EClQ6+2uTdwSVXxkZEjkAWNJF-@O7Ev-5(eAwa`>u9CPI0;Ad$|8ggoe zd<}BBcYAx#*t~vf;^+FBE~hJ7evWMX{AKmQ*q#@@gtxsbZ>)DI`m$``>RaJCExFI0 zjDPaBUjwqW``=d8_i~?>?6O1IHotMznzC)xqt5JZ>f$kaW1fwns6yA)*u98bPhPxE+&_5q-j^qX77+XAd%ruh{@;hGhtq0198R*$i7ZQO7_%v_ z?m_;s)<^I5*R|z%1h@6CzUUV}`c;!}PDJkBw8ejX{uXt*Hh%5b_@LcQW2-8^?C_ZQx-7ZsUGc&RV;(%Qi663Q+vnaF4JorNlzkkOLWFzS_^1C& z&<7>nUF1G=YV}9=Tu1+|%KaUkk7r&y{<=AG&crXZGk9Ab;56P7N0px3+%fN}ApE$b z;)VD(sbsS9H!oq{$)vDCbA7Z^O2XDF=Zg9b5RPV=A9CI^uR4wU8q_=g5;<$B*Tdu6 zUyLu>eMtNz`IlDz&_JrRq;P!mUFG9vi~f4&USp{*v2{LJ>QMCHT+jSxp>C0Ry$^i- z_IzFWId`H0fChSu{ow9$@*axWu^0Y4H|BP3R8jNzH;*ENzkabvYg^Vi?Z|SE-p{H| z=l(sc_2T?H8{zm;)5EO1=(GwJqj^zs~fvNm`AuNVNs?h&FG}dcu>+JioTGL~> zxfYttTiwFf9sk%e_0;jX_nzkZeX@?qA$1IW=d`TlHa3smpNsl;$77GuvK2*T-zn!i zpNk8>{26*`pZm6|uivTFGaA1QeNa7Q*W0^t)tc|r-ikK#fET7d%O4XlB&e(`YJKH} zkt1$LHm_8_DK2h|dbEDrywzQ9L^g*$gdJV_-@@P^+&-yI=!Med#a>xOE`Q&xc0aK2 z;Ij+9Dh^bZKXc3}3srx(lD%P0<2c|XWZTzYpoHXl5f)Lw967%eG&Qi_~gMMWn1 znpxr~D0=WKi4^_#X!)XF=c5{#kZ{^|4xtG_^T?oZT!)% z7tjB8H`1}8H1hJp)=1}Fds+?EyFn{9q?%m+@~LKYnW64NuU!=_gWJAE**c)f*0R4Q zCDLWY*{X{x=0=loi^y?gxqJaQ30k0V^o7SaY8D5OaTcYtuaWZ-0+bW&c>zogKUb4F z9nT5~Bx*1iJxW9+=?c!YeY^UpzOU*FIxcjvc=m!Cha4$sJ> zBkyDSJu!Xfw)W|-uRP>2;&!h0c7;iU8P@U8m@!|sJ@IL=nDu$}&5@d>p-+5MM4aW1 z%Ie7TNiBY9`LW3+KR?=Hx!JUE%jh2cs}9eJ_-q}2ib$7a&?bcDTw+LZ8LBOLk*3BB zSI|$TnU*h1SN!U`O`#CU(e|Js6w`!9OkzNhab8(p+EJW20jUYtzg)RR#0@|Fr1nev z#EdUxBX?wETOOQcur;{FEt~W6omS^v)q1UtNvwaK_xfYm-^;!>Ru&zOSk`v0F+A#k z`@$pTHStyNb0dPDMdWrsQ7$d?YtXBf$U$%K@xrRXP2U<@etG-z=i)y^1q|=Ts_i)*F_s?A8@DpiAPIB({N0IFW5Y5k zmIUl{@TrOn-FslLUt{LiwsfzV7`BKUZdRbl5Ke&b`Co9QLnsm$3GcyzZ0F~?9R(ti zzI&W5cW^@NB}Y7o)}bBizcDi%?(tt~p5q3$4LjdAt0W@wPPv~f#A5C)^_~r@DoX=H zC%=shLRM^$Hm{jc+ji~1p4RbIyEPt@KDbZ2{dG{(`ke4Ko7~GSd#gV;b)NQ}`rLcZ zkxehez2Cazmbcl|e#{lOJ_@hAc)qc+>}69U^fsG|JKTalzYps&YdnB z+tLAMP(8+{Y3Ku-B94|;e=GYtXXp=e3a(d#?c8!x7AM`(`2Xc7 zERP%yB@ysL9!J<1ZJc8XQ3A=KZ%B19=^^EuXIRJvJ)$kjp!gzn2g2<*KrxH6?OOCm zbI?9;)|$cb%-(kw4Y*(^dDmJNI8J>pHSXpf!x)n{&8w$ud_R1}CQqc?%@b#QUf%1- zp4Kt%GSzj%hkd8!M~uJv;3EXElV$V2gxefX3!N5zzDx0#~#JFl_IPmZr$~HOr#%PPQiR*gMGP{u7(zA%~McPuybw zl|siruXi7sqL$RpT6@XId7`;&eTTHYQ{X9spnAW(1I96a#OJ*5wB?P1oCWx zwK%)apmR|wwXaK+h!o7`Uq2nY$|zT@8oeN5_)?0EpOd)MW8$W6hkBpC`SJOV(>*RO zwHfwE+WX}7YNMH5!^-k)GLwhF{faNoKy-dov{?&S)xFAdfc&TBg-Zq;cJGGvLfc>- z`3MqtC2f{4iRnzi8}`YyB64D|!^ror%RJTy|JrLilMP1o z^gnbUONnNCBpI{?XNJsKO$j(-gP2K_1n?4xPIBh{ue&DulKA^3%!{&UX|+BC+pxY&)+w7OS-dsNqMsK zr1V)mQePy`dpbTq_S(|)`kQ@T^&VZH-T%Gwkva9vUFKH1cHCk)YIdIM*S3MhFWjcs zbS-kgNq#stS%Z!U;yBUDuwGy`@OuVrv?K-BmJp@X&^fGmjbIA#<`2IK{r;uhP)z2> z`6DxDv66a{s$(tT9eO=LMdpPb{_J}7_{`Lelk+Oy z-OqW`@^;|wM@~Kt?=F;Zsp~RJK0Us0`u>My(-w_CSpOtX_j%idCnGD% zt6r~x*3fnTvn8v;zr|OyxbJ=6gcEORmF`iC3LjNvkM+$~X)|6l^>iWla2?4;Q<5ooL8)pRtSM<>ZhZ+B-){Qb>6RFSnSO_7l`d;)B(`2Gssz z6WqXfCJs7sJKAdQY_osEL(?i)^!)*((j_9aqQqPc7GRGonKgj}H2_kLGhM!hbQ5ns zoZN%uP&-DRj(@zU!DIfpdrjY-qmlggF02P*b#ZPjbE#s z@4Py3-lb#yucY;YA$tL#7VbzAJ`?qRG@FQaarOesdztD0UKlNgdSF#^x|c$pa?N3@ z%oemxZgwL9)+#}317e+KrY$+*CC3pAyxBOZ0OwmovS~RY0l%+YgHQ0Un@2~+A^-cYi-W) z4YQJFL#6V>fkT3$a5yX+4~=9h(Xm)dxAp`YCduM zkkbhOBAoO_MI~;OHA=^vNI@x-o0S1L+(dzRN-S(^8VX_`3D=zLW|B@ZVa3} zMfr5Z^i+$|`ClTsU0aqLboS25=ipXjPuvUtc79Iz$_V8B^Y=Dw99{ObZJLdA@1w>! zOY+*fJb3AJ)~?g_u@31DXOc@>JysOHUmnzF-=meidS$_7k#k5gvqdZ#?fXNbgTv>>K}!@qa~NEVDoM5_0|0z6*; zmqeSXlvQT;3d&u-<=xuO4vsKSY5A%(DGw0&_KzMGHviYM{$+VZ^_Or! z#yfyc8g1hdWRid1LK%W*#6;s@5KZ%dDm-8ZBD(>Uo)+R>-)1Qs1;r$|qfkY}%CpjT z?GK)={E>pFOSD525>Y>;Q4oTDOA3KF;Xy^&o&6{1pNdlA@_jx;9!V}+ z;BK?3F+8&7iZW5r6`ndZ$|pVV7}SJ!NNTc6wY$=h#Tj+cbntk~BSsqd`J zM*F(Rn!*DIBwi}$6*4@DBBUTa8w+N_L)V6|28+?+KpEf()jyWe@r5`{PmP;IsxMeP zfTw|qmO=N!vy7nX;Eg)1sTIz;XmPq!uR1A#nvVx5xdE?9HyX3Fvhu^lBikOgMGeY^ zxu605TU74)w3>?7%}t<}HGOs-?eAdy?l`feqP%mc z&F_<**1yW@G;9V<%VhGwOX_YG54^T#*wz_S{!L4#jVDHoJs8o=FgdSNxqg7kYmR;7 zvSLqSGPT4Xm#avz`G ziIuN>E?0e!=X`DYqcT5Czu?Bv%#*rfVY6y_L`DXKJKUXjI&Jr)qN_=RhFk4^T3?>` zL+_aMwVU@(IHTwN%GWu2WuJ|=|FvStyRA}bXL{ZrH+J9pbxx02_PT-x_L0%sbFTW0 z@eZ7|Y01B&Qi+qP_5h4hYM)eOj>mq0beNCQwijKc5?Nm<;x3{R2)Wo`xf#_o3|L9J zG*ARWl|l|MheosTpV|V`z{5?U&W@)7R-gV2D@%Q|tlKaPQ z9vt>+K>vWj{k~ky+xSXr5OsF*(iB&g=STG0F}5&1ZBSZDSnY*~beYI;lkf18!)^aA zF8*m%tYgY=)|FqH2ZX&UeslBMCQVgN#F?DduQ{#8q%(1xBL`M}%*Re+pBe0s?ED$XYKVL%!bkfD57_9Do`E7G!nc@L{iyY9v%yE z6EC@2NJOpWPue&x^AFzwweDcvPa(Puse6#gjS%EszVvTQU+e2tPRH^GPuCixUSl;5 z3wOK;j{aGgccw-^K+A2GkwhD2j|#*!2klrK=o9NSP@!p+x~fG>FYAsd2*RutRiEQ%we9L zbi!-`k==v0=65PrqB5hIF(CewYb{Qev>#Dq6d7E1@r-@q@snCKoiX4}>Qo>q9{ALb zps0sh(Xcd}?n($S_%myLZwmaZd-oKxk-I?K7k@v2=s8Z&bfu)2>M`}az zZmaq=1}N7(ck$?vJ@3$joX~Dysd`MB%o`zy05kCxoC1<@mI0`_iSQ&V_#JU2k>@xO z=gJ8*&8P+kryQMW4c<2)_@J@8$AF}9m#&_<3IGGGNZi5h@fB}Mo-=d=*eI-b1 zR055jenv3U`l7K(2{TdQ2M7sr73z1G362LUA*c)V0&o-^!-A-g{u+cBN%VnbAt`6l z1;)X0dC`+M=do)FA%h9yG z|5SOlGK&Z@qVAL{jZMW1k)Avch_%*);n<}k4@cdJSyTv$}OZr%_PJ@>x z!Z?xwObp%*{*24g8Yd;TfJUbXNnG>@3nIo!jt6+rc=90^H&KR=+?&S|Bb7|VbSTcu4_p}V zf<9eN*=}?^aF-YP*gfFa-}*4WeF(oyE-TIehPJ#bL!ilu>T5@VpvHt0#Fjd6)2 z(Ywr0#6#CPq&cWt5(7D2hNmH+9Ih{bMBt`^?~!#Rf&i|8E5I;R*g0%KyC20O>t(RS zdayJy9WB}xiL3?i!+BBMh6GyF_dqnzITwN{k=2DJh7YL8_t;Y$DcMw*McZ-UJZJ{rdxU5iSyC&k`d_t}bIYC>hJml9+HKLMCL1 zv2Rz%Hb!!jAr!6{Qc}rIVa9IAQg&mlY!kAj_e{V4`@VzcdFJuVvwXkj`#I-)&iS5! z1p9k1Q${onT~a@k)U_!WjO=g8WZt&OuwTGEO8*p|QYxQ6_07Mt8%bvl`1M#oljX zjE{5r%xr*btdNv+?|1MuV47_7;}ZaoP(Z5ioUB1CE3hYhjF>&|;}>9J{{!_vpizLN z;A7@xW@zKzpt=^b%ztG9QUDGt0A4V@pj9q0LP^jr5WPQYd#>NhB_Rd!J!GtZ6|vGk z2hjEp>pu)870w0|6Wtk*1wse7bQ4D7xkCa8P^<`AKNDOHCTWf`aQC1^^qppv!SR4% zgZ~hrjiI$p$O6bfQa89uAUtqm38Lr0M}!(f46hy<_rx#E0#&ENdUHg~$sN*G0L)pT z`WQG|kP~wLx@j5YyTmb4lvs^Zpk@Z_1t8*Jc6S1!Qu_WoxH3at?%&a8WxB~{pRN3> zYgi3f-A^*b#&Q4vCY)o1HOLLTV1xn*!toE}I>V@eL1E&|at=tebC7|_dJcTPzD!7U zI?9ap1L=4y&Bp|YbCkiO^GY8(RV>2{ga6#G;&jFktYCsifC0%EwkK3v0j0qCfi{H= z&`5w-%q-APP0$MZfGw;r#-QXR1f>{VbX5M{(KC!dFhr$bmG;|XXyM;}lLD;Q2St)? z3)Byc%YV*6joDvjwP*VRB4_SbB#$&;Y15!WI7_A8s2P0gC2@tsn zMik&N$Q?t{K%4#(d+5-w`g~H%h+ob2YoLLc!C+wJ_{V@d;a9gN-sj`zKl&R?4+fB6 zozQ^p;GoIdXhfMbD*lTLstAY{ib-UkQ-YXDMP6PP}x{l*Lk zAjQlt2nH9!=unn1#6!V4252)TSENBbIzTA!6CsEQx(C|-1S8p+5fF(8@Mn8lx7H?M z$pC$@LSz8H0IA(oN?P=9!h?Z}Zw#QB4_9O6XC$P;rNC2*m1dT?QwX;W=mq`94BPr( zF)*|01HkLURCc&^R#uKg0aKX8@`RZgj2AE}z@!4mXNE&g0M1IY8T5f#g!_Oeg*Joy zbz{b8NPsv8aQ3o8rp4|mk2&r!_yo{0jLKMyxlE8V32FjbAC8RWFiiwHftRm97Df=P znG!gD%ZIW?2x(x2^%*dTPXwFh9(7jf^0)IWXeB< zFBo;@z?uOs6`%*^$0ej3H!bJrch?7V74#(Zl7S2>q}+lq0EWTLIop)uCg+YadQjFF zy4mD99LOvKsQn9tz+z#AyM)rI42=piWq{g;lpi2Ue*0C70s0ImAXZkxbb0WNneH>{ zC5tKp)Pi_1S{)i2Fo}`H8Ou8I=)`Hti)f&NLDw?tLq9Q}I0144D$0opL+(}o9cEW! zK3i4*A!8`dA#I`$zzbF;B-nuU2Hkr85a`)hR!aWkXkO4EybPrd7E2m%>dXxG?w3sb z#tTw>0!km;zr>iQ11%3PsIJ8I`_VJeFn%@uF+a!m8;l-?5@P@*03RDZ4aPm#^$2}1 zWcV({LRAb7px31UH<^E7Ka^vFVh%S!6|>q{59*UKuo)Ty zMiN-t@HvQ9AfVX|^cm=&&wLIf2j1Pjm(Bk#=qRZB0i*h#Xi-2fhP4Bqfa|H8IH!M; z>7Q8BG>+dnPSn78lw}|xITrAm(J~C{1WNRPX$6-Ac>;SzpV5Y_S@wxwPeSIHu`$D> z2AF4oN$X+62F-wLa;{DT5_vVa~A8eJI?#1aA<9 zF`XqDY&SOSh6h;EwdQs2;<{}8gO7_1sGF{v97PLvtu5Gp$q_&nxw#;b<+vUK5ncnx z{)T%f@h_S4#9=LLJW@-uP=rWr&q0>LAzd3tnXaCOe{typT3T*yZdlXxg}z=cViB&( z4~f)rLunG+iAa00eODJHxU$TIxRm4nZg#UL_jS_u*34A9A_u!A((d5(q+P7}%#z}r zr5+U%HWxB67qu(#Hf(BhmwPu!OKAwB&s&^>#?8$WKBlggbDqU8ab$Z|KFm1!#) zt>K@fa8#Z&ukcqwFDs#g6hTDS}ohubv7B*2YY4N^mrNUhMf0oFk#8%%+7F7eMgf3^sK<^R9ka?(axGnzV~I z4Cocx%upPujZnCej2vF%UHYu0H5*iPp;gnWUp6uc!Eed6=)&fotmNdt8dzqDd%C2f zWb)8)h%NL8OVi>^`W=tnziPCCj+ee88_U+$@DR$P31z|J4&um`FJmsp%ccUdf)@4i zBX~yk(#IS#v-WaUYyE@y*CVQpv|OZ^kKVn z&nQVr9DnCv(`EiNH2}k`G@+fY9+fvC3N*m)yB=Tvv2qDcb zU=!zRCU@jXj<)4YPpU+O+{T|Mhtj+YD2sy8Qk8(7hRkc+pBHyUa%#9&0^f@-<#HgA zrR4#*E`QT5vLR9{bm%Z<#gJ#jkM9fG^hKG7^Rj78N6ylNOFci1n8wE|gjmVnNQz9- z9#HVTVOsLgXqf%+;+G6mjaAB2Cs%9Q652F&eZl_4T$~L;K|aB9prhK7v!N1gOCSkg zSzE90Y}eloJYoCrJZFMkg1$hQrL*-$KHns>HW#x1MnC`~=-g6s(^@~j(8ZP)(rj@^ z0gPJ$#@gDp-dBNFL8AUpbOdL9A(d#;AXf75!l{C$8KO6BeHGRca!$h@{<{auMV9)e!ER@-@whXAy2=>{h<@K|^@z zgNEvP5}Oi%ovpyKc#!RaOY{@M311B{(Z_DZ;sw#4G*r(`4YG*w9KH}^Nn-Peu@)^E z`JjY%5fgn0C!Zm(vxtcnPyPCUuZAiQn+IGJj^bfMzgOj9XL|w%L=)Hz%n(HtJ!h}s zXKejj$D%Lm5<)$X3fa64lgL!wRYlv`B6n}ZqmZc$r&^Wn9Kj?8C=2zG!}%uFl&0^x z!Q2-c(_P-Rg%}hMe)TT9g;BhgmR60Sn@iKu1aV+M%;M_s+>b!TA>2j>3GhUafiCO4w3w6KF1+hH_@Gr^WW35~(z zTzZ+nOZaeEibFpg#d(ud2-xDx7zGNVH_Bk25*Krtla;l+8`eM`OHDfY!McaFWU{a% z2fj9=_=UNQ0^hK8Z^5EsL$G!P*AiOd5GJ_+wOidLwyQZjz}2$0D~_j>Irly$Jr#WP zSM?S14-|}TgfA97;1Irc zr$Jg%)**KLrO$3k-Tuv)3oNC`0uf?n zDR);-W!$tIC>7#VPc@Z*@8v2G?)u!-wKgC0URZ$l#S<=bl-)Ey zey~639jQ2x#5a+XUn;1t%o}HlkIT7K^59bGz5M#PzTPEm8|%Ef`tQ ztTB!(OdE<2TWe!SQy&iZRByn}97n@brQ$r}ZlTvVC4PTbGWk?P`Q2yowI4hmD4jf* zPBE)t_M+p?-fH2F$na0wy|OdC$?H>LyUHm=s7kjo}zW*|`aS%(GG@8sOY**&DERy3< zB1GaaN`!!S6{%aw6?D@S>-Hxj?*EB3g~mHKUp;^lnf{86Q34%b6wn2)46F&V(4hU@ z`lIgUFOh1iAJ-lozCWSZTJax5Bt`Pzk4VhB{ax}l5&pgleH3bg+`6gN<3E{1H6`S7 zT4fcJ7E+N~Tdg@ruq}L8bbPV)Yn?@IIRAK5$dHA-2!S}g0=wqjbPLK|gLVbtxY%zU z8@3gcsi-`=D1v+Qb}xINWqYMEm8KEyEX|ECNs}j9$$bArUbLgN}Yp<2k>7_5t8c%q-C*NiC9*lTZt^qI;(IH zAx1Dll%};8WBt`L$r@t#51JyGi)%{VVg%DRO)-L@W`Rh{`hLT$wQvctaJ)O|8)nww zHfg<`v_7VtV#7nj&^_J;@hpkAn7xPf^q5E5n8(|__}irEF>NvecFnlCX*LL@bZX|F zefTaz3v1Z5lsS(N_@JewY>0fNrbHxokPSVW=J77#V$&46LC%Uk>z)MLmx3qJ{QCLN zJu0q*S>ozFCrY2lJw-d^;+?Fm9Zni3w2B`}yE`M=6Upna~_JmzKMKyuadM#l2_2=B$aA~TGdC`^pzCd zj>BNM*i+WR3AffY#3BU}24vGs(~e4sFpO^A!Y(jt_!GLU&}g*%5|P+3bIY%cevLgW z(Wvi{VtwJN#oyTYe&XOkTU@KLyRlal{5BRxU!k$`7JwIaE@|8L190u{7DQZ8Rf|-$PZRO0B|T3 zr*rDXTXZz&H3P6s|dq zN$TPg=U5t@tTIB#hYoJMRF-|t*+R*^YwNN&cztF$HRk>J{OVd=T^8%;mSDrLuT_t66uX&byX0{>{z*G^TJwq zTmyCa{BrKcU%%A}wgeT4F}^BTHRm7xF;4g+*JchsF)=>TxHxIvJ>Fef=h&&Pwvf`;Hn-ee zl%R6{F_kUmeEwn9k8BC3yp+*}^_}BaA4QDu#SaKZoO&7=E57TnKDSZPkLMCC%l#LF zaqzLUyz${{VDl)mkWqs`3Rn+q`!c`ID5UaFkdG<(rmF)nVu92=u*W@Ed$R zqMJz^HPq(X`pQad)(&R0a9rKug1l0zUvpXpd-PNQF45@jj_xY&$BB=#%cnvl-A~W{ zm5z}AOZPlYPcn?9$ne6uDO@Ml(;{{ILKl%@95z%b#f9<}Nu}alV2o|d5dKuTu@SMZ zm`@UrPUgN5uViU;_OU*B;^nPdlZ~}a({}G@9F(}^2VJza!0^ULvms}mvPGcnM+j#o5O_X??4SHhrrxx*5UPr|Z2U#{&_{r%J^8QcusRg}e3Mqub{p z>M`~`+u+2M|4S9qb^FQ09@ZM-kSg~=fzBI;wn+2UIndM)JbOW_srW_NUrC-hVN+cJ zxSsnckpZoFa(n~#L9|mxHT9TjLxJuaYYbkZtdk96dqrYfS$nwE*L#7!wMFyp2D{{O zKf*ZmkGa6`Yh?9o%EGGR9c?FD<>&sb`&JKnN9j>~b8CUMmKdij1s#0q-?u}w>&zw( z&j*I*4xSV_8keK^=4`5JQ?Ah2{`${fmN51W@$zT^9qv?n@q@&Tdfk}ULPqtxaWRVV z9`Zr?O>upcPHvkI4yg6-g$XdDCUxIi;i_~RpAhGgG?mYN4waINOzECq^{-mWT-OR6 zsK0WAupb!0mh)$7+P)v{wSRLiN9zcNMpqB6Z9sH-h5SL}&oXhXL@Jrg2RlA&&CE=% zmqc$mS|I!D*MhY9(1Tqo3Ktjit~LlH+Mtlu@?{r4iwlJD#aGN|AiL-7yxuft@cW0< zMM+l9?q__zKO6LJap|Gq>9OtAw&#;IeJ`yv(vdR_K=D)R{D(ZaS z)HqJ%5v^xkPl$+)(R=D>^sw(nUxd2JYCoov`HNm5#X14(kOA356_b3JWdMeYOtv+` zoHZ0Do3K61tfo-|7K}-u!QYNJFS`X)!@~Vps~53S7+mF3vab5hp*LuFcg$1QWW6-Y zHbbmJKJaNG-DZ2jLNdzdK}BJvN|vi(y2(N?vSYf-{eif|IQJr91cTAu(4MjdH0smO zUn~>v!Y=_vt908Pv$^(OLI_dt>m?#?6gNm6R%MvqNftFp7H=~xjYZBLYZjRPY9W_i z>FTw7&wyxyn3M3dZXq1LAYSmwS3~?+^D!21=EZoZj77iYdT+`&rDi1g1X0{>iLOUz z);#v-4cQB?_DoY>?HRGWNy}VsUT;*|4*Qg!{->HrTADnqbKNDmn*s;CpzK+^+Ow$Q z#wjZ|JvPkb=XfMI@9G{c>eTNJ79ADDvhdA_vM%-9W6G7hn~(P_-k@(j6L|XR%pv!! zq(irosDGb9-+X)}iF)oCKj#2KYHCwZM!w8ge<@$7;Eh;~<;Vt7$JgSz633(*{(IghZ7t$>a3Ahh&4Mmv*K%{%i@)i(m0tQt8Tag~LP- z%i_5NZI#PJ%DTFilrl^Bj-KM@6piK*!~x$i<*RL)es_vOp#%abvcw>ynzsnt`1JxU zDVur$0Rcf3DYyJ$h7p@@ace+k#Qfu9uyPF;)-8;CYbi@E6;qrYF_-5lt9v=zc_O6lrT(z%` zudB<=3@h!*&HOylt;(TIsGy>R408zucF!0d2~w$BcoD3(fwyZFfV0{ILqxnw^Rcrl z;_vH%EWr^$O8b`$pg4=sKY%{tEg=%WTLIY%M$1wTZ>;G&%8p2L5L^19ks-D;6DSvz z<9SDxhFp;XC-Dp&snRP@zaF?49HgtxV2s_|E+Vr+R{yAM9;tC#A}x^=IJSH5=j42C zcXNH|z@HnGcwB6Wkls!(^bmU(l)t!3f3b^}RRg&5s7w&tk-GU>T9J%W@BQ*vQO17Ra=& zF_!{?D}rFxzP~YWW8&RO?i$ zm@H?>Y<2&Flf&YeffacKDr5u8MQMHd~E7AQtXLChq+SadWSGlWex2J4awpul- z+XcvHLfKG?TczEKXt!IXrBK$?kS9uQ_;oX5TBMB0F}e1A{0bco#e(6=SccuL9MgF} zmTpHfL|$q`y5w|>?TXk$i46o?>Rg-cWhMe^+BY3>QD1(kF$u9R! zJ0=&iJy+>Hd@S*0cxo#>NFf8I(1Tx%*K)fWFf=~1(@1ROs2zUqm$M z{@_QT#MdfX9h93#k30)EoUHA=Bm1I+UT!aEG!PuAHzgN2=k+G!RKh|~R!x4ytJO{mO^)~WX+B8G*Ztm&8z{^{(UZg zw=`d&ArZ)X(Lec)ZpZ zu=eBftnP|8QN={iJ~*ekk_-eYs+ag_ear9cewcmdSxbB?2 z#(Y?L5OXOGV;!iGonF!YDJLw{B$~shL&3(9!^j!IOv)ELIrN%`+Dsb26cvk!J`*o0 zHM^2k1}h_X(&4~HzX5yEsbThN_GQeWGG=d=#BRtXu1H-L= z#xtrqE>|t%ZL(tT`EY(Goje7i?IL{x$zW$>OH)EVr(5Gcc{j*ldR-5W)@qa&N*u1q{{wxnk z3~aV6zR|DLUW{`qu+9^kpR9lPUFN$h{K@3B>l%Ic9U~Yjr6MoQ6U;7%6c5VXW;oIS zcuIIT5`4-}Q>0YqGTh`K0(NQ&cN>53P~`>}d=QbART#wWhqT7Km8iBq|HIESeOzZo z-SxY>g2DA3(rM|Ynp3tYieD5^wWK)nBtIolI# zimwt;L>Ac;%rW`L8-=)wwf;YuHVLQGW?*W%q@(J*+J5BF){p(M(+5J^Ff9;8LIt#6e4z;w)X(wL>-d6ZnrQ3GSHvWa^lo!adS-+U`vp zonL7&Kx4PQHE#Y(S-*(^VR(8EvY#12I{Y!cUbU#_o;pGM@9%q4x&Dy5 z-XN6v+WEU|S6nsPztJdmuDOSp=XXlH??TGK*_BV#N+h{YU zP7^d-Tn{czcFYX4wT?bG+>g}T-0(THJ3og0qHK7Z^K)_DKD zJgYxZWb40+?ZdTiXdhXuSK2~mwVnO@hDrcl#JN+#0TVOOU$J3{7S>U2Ci<+<^hXRt zjbmo_vZ_a(K3OujkhuLlFJ*g!LU-I%`aEy@`>fD<$W?6FfJ=w}+l82KTRU9ta+IC@KQdQ` zfz@xBETBllW>0Q<_a6K;yB6xw$rHh|?eck;inS}ezI^A;8*#XL9i`prRonMtgc0Dg z=P)s^T~1HY)x2lk7FxCY1+C6wW%{+mOaFCFF8a7WDgUNNc59)w4y7l)HWrM35gzk? z)lZ>DF}c)jZqH`h;@&O#*7C&1x^D+A{)tr)!{b3$73?p*@_X}rOI^Aybyq#GD*y95 zwy((v>F(OMCRbKRyB*V0U%Loi z{Evr5`^hAceNjzg%SLNwHPw#tW-;5{V`Qjm zPwK*x)|V{~9EhoIZig>vpeE*1CHp#$NwBBjqt1_YR0sI&9=dblqS&T#>qPFWRq7*) z!eUaan*t{(!B#-fdb@siYa`8F@Zr@h`m=#{QoexlQ9eCxfbPegy2QlQQg zz$g$OogEFR^m-?FC=qxpK^d=u6_uzT0nB%tb_9k--xpbDogZaZ&Tsv6&sFg*=FZI* zz`I=utzW;f0bFcp6WMgJO{=06^dL2T%mqM(ULbMH1z4ZzPs-#mm)#@D-ijAa+zIoy znY(PX;eU0vFk32 zS}5_)V?%yrIXxp{P94wLpPHm4SM|-up_BZcKN;m8I-S99e#-V+Q_0l#`rJ@y)|;Ij zUTu@w5LS#!Qr6X`p9yL~m~tLsZ7l*?QH~P6HObkYW8=Ks@9?`uM)z2@v-F2h_n6tM zSvwn#)GQGX^)mK0>ESG#daA&;HS#5q3E)@qqjAieSx7Rd&GL6R3Qh(H4 zLtTRk4WpeKX4`vKD4m1n1NomJrQu1AxW7maQY@7%pbOW_Dl&zGOdn|W(gNZ6bQzP( zHZ9+?w6=}lJn`H6>ZyA1>*Tcq)PW>9@BQ}Nz8*pEF6@}lI5y#xBe9Q z&UYbI)1r092I7xcxqnBuLcRveYy^vkiT4_>_PODD+&}9=zxFmL2dBt4xs8;;yhkA*#{baRn|i|_T?gRwhjxqmX9g#V2lYxlWbjmndx?0a7s zKDdbRHai+#nW!B$XuXNi7ZC8z=PfkMa_23CjE*j#^T9jfE4+LY{BduU_98Q+%&2U3 z3X&~5MO{>8;C7Ljx~QT`k7V+C#EM6cQ*9M-?!Zw!zPj=?dX)A$(Wf=DVHnLrVmy7jSZtfp0SW`=F8dcv?+j?^4CL=h8l%fL7(Pga`+ zg_jllKlCJ(azZX+*Wx!nH!V|Yx-!bN%OzcjVEVajy@EivNj5@=;UOE~iV|f$l?5C! za9B`Smc-uF^^}->EFlX2Rgxy=zmIkL$s{tO*Y@TFWEQ|WkYjsT40h4-;J@v`k}V@^ z4TdX1isQqgvyC7cxkk<_a%UmHe%X^jNDO(Xnvl%gs#O^Q_?sWl^96QgL!S4&A+|32 z1ZXYcSqMPd$htkriU%^IgF(570{sR&ndigeLD_1Jt?|aj3iVsdVO6_4N^9g=Y@$AXXFMSv{18x|@ zNn<=JTwjJV%Wyk^MblI@$()B90xy-Zpv;1o3O*ei37%;8WjOd(0)de)&>}vp0YgWw z-66}BOKcM2Z%NeuQQx`cvmW`^g!iX(B?$nqMG z%-N6l=TIs=)XJb=`(=U3F>u%1A8!zx*qYnhOG+@9h5S#3s%ZyS57)y;7qwr?WwVG{ zRI*zgBgEo#{w&8^HT=F*#>dxZyO;2Be2lgd+=!^^9W_jYYjT>&oG)BhQ`;F8)A_M_ zZ1&bbwTI^ZMa@TE%cXCsOqVja*RU3w-`m11az5_Zywla8g`a6&TaEGA>HNvGy)aWD zsXDS_hZjBfIAuFxRvMvq zXiYN3Z{Ol4)A4=NpG>sWB$J;^WXWS^e=_~O|Mw!@@$)tDt)0fCom7@kzAtNz3Bh`* zGm!yntB0oRED~wA{4{m3iLJD2O3lT-9wN#!6{1gDd})cznaT;@>`SZnSNUj-?D$xB zSt{otwoGZNsCf_)8&}mn+4!LPfuNf{KPRiyxmbkMxySnX0zlcwms!PemX^mwUneXY zl}H+p4pL^U)2?WT7Bo8g1o?Xe>rwoBXTqubj#+w1->~b~ZXTTJKL7u{yLBMfo!7Ec zxt*uxw}0e*cBJO4>T;S~`!@1*qxrbrCUW2B(NCrWpP{_pw`CXZH+pyH>h|{NMhFU- zjt8+sx6{L)Cxz?9t*_{5Q+)RWhtr^TLoh*Q{9t0PdrWU@LDYL)60t7f*L!AqTLPO* z&%@Ir)q4<)ZH{Y>@;gPnZR$}wVT369cd^l*Oy0h3t4F^1u*{dw-rmv-bJW;2-S&L> zboZU!e$)?eT1Nh;xt0C>Y=Z^bxCQd1a~;pVk_e(i%1OpeRqws|$#iVt$V~XheVc5( z#BZ0P1m;s`_S^Sui16I(>(^WlkX^tY#1OIfrbcTi;}dC9xK#+jFWq~5=~SVaE!og? z&gR4B|8ZR)?(VW@*Z}d(7^x<|_R1J=p4RDlQ8_$N@CEWZMNm4sw{XGAk|xt1C&xy{ z)^!~{vz?aGz_M3PI)!@92E(|0njW|l#z7C))_mm9srad`C!P9}$%=e14o1^YrtT-2 z+eS^;Q0(YP<$|P6uHBdHpG*$>9+N^%JM6i%@F?<5uTIamvFal1k?f8spMANxq44+= zy5k}8w+8hGZDZ5)KYmR7_LJ%G!N-C9m}gavRY6Tq=cS`I`~Aqmdc(!(=WCI+AekOu z?*C+RcRx5%z>j{@Jscc)WZJj;#}|!Jl%nFogL(II9Hfw2AZE9=kB9B;J<56CXT1tZ z0HeiSyS+!MWouI|qv@W&*_NgRiI%IHD1WAw|H?D+on`R2fRX?-S^rfRwh1IFS#pwc zNF;n$%|+2gvYqqGy|0ZeA;ois!D7>QzIZi^XZx(xQ6A;H?&zg#eLqEtQ(bo4yl|km zF!_@yE7@(Yfp1OH9 zU17fr?xsrtLqP^JT%G}GIhKNgG<>iG8%imfI^Ba02>8liS@Qm<2mZUj@+i_y2tV<% z$8h-Q-u7@6|!Dp6xY3;L7bmbiq#J@__i79PIuMHe%Q3+p{h9E@WxiZaPr-W zPk?eYQDs}3`|%H}we7D(E{~2;@wm-xImHzlvfk)%wehB^@8O{f(@~>#{t$NgVbuxs z!jFIZkvXwVJ*cb&Kx+IX`6I#K{Cz=zaF6cb{<8_e);dMCYR$0Gsz(u}!L`@s+NxuK zVTA7{ldTR-@e8{j;-jf8pGk3!md-u7q=qxy-nX%y{yvc{Qe2!J%f({}i3vs&)Gfd5c4ynDL2XZR z*HxT<^_Zi08+5T7JqR@^l>QpJ?WL~#mxE`HJt3#Se%+D$KC{g(v7DOnlj)5*ZhJH; zD7w-p{wLEdhhwwFr3b|$*&2Gf6DfHq-}Qz@Lv^(e`s;(uANtcK&aA&#^8R281d*kU zzh8HD_|r$V_pmCPi&8k{-MR|XnPt;)|8m=iDL^#hh4Io(w*@7$uENpAmS+S3E^p2yxjgNlMN0uh!Xmd+dPRNN1AwA&5z@G4As zaO#k+QHu8r{gJ$n&HXr;keQBvkYie!sAs1xgI6_^#QhHbgzyrbsDI+N}f1U=0gxtA|xGAkcC7^ z==J((#7pM9?i?MIag=dn0HJ5PL%-+S?~wbYo`ThO$!?jLS{=|*A{a)Iyz|<2>cF&^ zK8%d-nw;{BJWbbmelK`6Gu+2>RzfeWqP}fnJGDdoK)Yw!e|zFW3f zBF*n35nh>Xs7K9gE?I7n^l93jNmjx6X=x%)^oo*Nz=WshH~%}xnas>v`hYj~RQ^(^ z)2fAexvj=n&Wh|p>bFs`bcdqS!-1mDR6n7IvYturnOzYrLp^1|w%=O)I!+$%l-w;x ztm#Gs<`J$ON#2hMq8(g}?;2US-8k}-Nig~G_HZW@b`=|6YgAi{bnEmPWJyRpO^^D0 zdU;20k1J=cotR~}x6fJi9`8DSOKeqddjCcv0$`_Svvzc}cJJSxO!fvw9GlH4DsoCcAg|O4e7^=5kmqYaaN!q==RKAc&FN8Ma|Li}c0TE2tOU z7>x3l^1IkjGwjh;of^CAk0jpexa;VWLz>nNWkP2aE*6)Jd{;l>kNe*u=+~A-5bk@B z1?&srD)JW8fl;00y(1|_dC|$%n?@H4J4+C1!)d;6j{lB|<=qciu?rY> zJaZ`bYWwo&e(yuQO!~q%UHjV35bc>SAB9Ip%;UP6we3MP#!Mk1fpKB;QJAaayvfKG0H0(^8 zHZHt;njZoTix~p(FoAAVw5_7m)iy%@#e(@ZP&b`VGk7+8NXcKWIFooG`OcElKaM2LYGe-`=ypk_zKqmu7mG+A`KTv(b3Y>eAx)#MzT7c&Zrj@^ zfi8Ad3QPMcnerl1w|RSy*GSTJ-z#Q)i-We_+eXVvCF@B#F?PINpxx3D`@`yW%Zq() z&B)0kHwk)T`;nU4dx!SI&0eZ)KO`SalJXqg44MhivcruWv8FsZ(Es9cQnA zg&bq?Ym5Ef{wT!`0- zZqKlgw>Ko;`gy7ft&UcSO{RN!i8UfR=P#BA!({%oL+MLE9lGl92+C8uYMt7o^PXMYk2guliO3S z5ZGB>4Z5qhj|bU`rN21AQtlHm&)c{LdLSA3lZkNM z%cEgD@5O@)62RsH#`eJI(rQqN>{}0$XZ2$}5;jO%)~O&P~PmR_k-c#U-ER z-ztkq-khSO*=V&%nj$weiPJcs%bp7(B-|nt+MCuFY>BS`CxUQqdIA2&aC4SvGHbR%HoK1CUm*~B(lPBXH=}ler5gczH)0r?Z zZ@8G!W7<8?@#0NLkWp=e`GWMo#Dp4rRaqb$8m4Q!6`uAr1o8i$Aq}nqC$5FB%A8PF z8~8|PXIwrvO!r*2<`B8Ip1rol0XI%HjL_^*k`H{Ji&~qr;#U0Q32(IwAg*#FXwA-Ik_H&QS2ZL+5 zK36~Brtb4*LdH8p@2uPK8A7mja7A-d0qgW_zV>foJeuu0s&t=kA>qX5|4HYRei^W> zS3)3BmUjdXIbN%I|HVF5eqmrWM$R-o4!>+#a&Jk+C7Q^C?_`OFfKUf;MbynAYYL?! z=dRYAeSEyshF_7ba;Z*W^`o4a9g7oDXL|MirL)s>jZ@`jQ|C}b39EL{vB(_@*{IXA zy>qCv{ipZAc7aSxB(c3pd9*69Jv)1BP{`Y0yt?-7)yU1X@1G3c;=2isXH)+7L7AvC zj1Lm`pQgj9jp&g;5u9e6J8eIYaVogjxdSW8@w$mC8$qSGjplow*J|oCdG5bJiIeS1 zGPrA=@3P;LNP5mCZg;OuYmUv(mj1jzm@Iy(pk?jcx`yx)OWR_bvbL61ozL;6?mR@t zh+3pkKv6FRjphK>D85YLQSjbhq__bAEpFTRG^S?#Rr;j?%Vn+XO@&E3l z$r=!oimyMvN_-WV9{6$fR`ZViO~r*Z zFGc!+a{ubR;LeK;7Vl&H=ajv}ALfNr&xdC$MILNTPbAgHS%*5=YIAYs3q+vNsR)!Y z%qW4-mA>4Pe|}R%1&nW2%1lO&jl+?8y9Z)Cc$jo3qC3v~?|4x!fjdwA zMz1^ELPG*N!t^LGW?w9<6*%<1caw2(rqP$~Z9A`r1^SfOAfZf%(Ze z%gauerMQY>70XauqpF?_B!!re+-MZFa9km_L|a{23D#OpEA)5eMTw9jseHN9rC z6?a$>K$$a=wzB4>PvkzL5@#S#e}#MPLR)c#;+tS(TaD%h^2Apln_-rRi|Vo4WeloS zotx1rhFW#721Z(>_=9Ui!K(llaoe`L%Cg*#eO)7pj6OyDOM$E$aghhg%0(B_uLzmm zz1xM4w+G5BTEWgr-b0L%qN1-dTu}^8IAlAha=QeJ6k8`m|NTIjpHoXdfntrJY>@s) z(1ldeLy%jV|LRH}HksKu~L>C39xytRGK%=KwbeU@~+4rS#(k1 zuVm3(Gx50rNY`;wH}1dOqfrHkhu+MpcpHAi?Ng#PqU6}{`B%7YKU1%SN3 zyb)7Ed0Frb&93+ZNYe-8mC)HC&ve^vzt<+^Ce?#f0-F+^@E+o?!ek>uXj9=fL+9vo zk1w4;Ge`gJdP=y>=!UzFv50hp07MoCF}!hc9Qyp1or|rPjf-ptkxehk?v(Y{EBlk_(0IOFkknR znX_rbQ;eVF|3lYz05q{|4SQ8Y1q7r@mEL=AqI4yoNEeU}(t8)UAiau67myZuF9AZ4 z-aDa%fOJ9?48>sZ-{8IPzW2ZP&4%IZ?9A-U?9Q2U=9C?mCcc$mGA%H`L`z1_!s>p~ zvCU1a-s_J8x{RL11FSIT%+n>ld;{#dbp+BNN>&npLjYH6WmXR>^9Nb@UtIx>&_0p0 z?sY474_8T{12C{F>@k`ae+$S2+U3UDf4kA)Z*L}uyU_r4F#x5yo4T86n3fhZGBGPu z?Ltdi9gBu;R&kZFcxrs{W`nBasWF(E&@gG}T)i;^Fq ztdGrNr zfC~q9G)}l=_mk{rzC(0+HC+PZT6*7+yiq;keqZ;sWBmmI)BM^m;8JRuIDmfwZ1M5| z-CL|OSvs&I?`*9UCQ%BQcLIzjY_!bE-96@BjCsoIF0gyTY!#9>Z33g9E76t7*p|sV zZN6>wjW?QJRa%0sFrbcUS{v1Y1XWp1M0ssLE(s0NFjbGx1`P29JAp@*qm5To&FvPX zIR)rO-YvaORGdUE5-H8~xvDr{LASAJr>M#TFn1^fc4mH8vMO&^yE{Kzf(}tYvX}=zsVD6Wq zW;~>DR8-VWYi$JuZNP}GID!9Y-52GmHFmdWcYNqBG3~u`86h@8b7>#*Pj@5qTKO$e z5}LeVI~EXfH_{`o7+xvEjvOUBkyt_Ppnazvy-S=eiGf*Jl>x33n&RGUuLJh9SmQ82 zO%iNQ=ZMu}yOEdh{+qPZEqCvWRXu%VtckYVwlBKujijBp_jx69iNr z2BWER$%OJKVN%+gJL`a;!CVNC7y)u403%ynS)6^V*5Rz)2p`w#*JCQ&iHSG#(S&L8 z7CIH^87t08lb|Hb;X4pId&9T_GJDCfnPvjV!feN0%qKm&9?Jq-Jc0dfL@o@q%z=4lCa zKur%2iRIgxCjuFPg#=rdDkYJ%IAQO=T_+yaV}6w~OHA#%B{l&8AfuBFR4GS(`&CBq7ub=vMy zU$f51{LJM@SLM1R$jHgGqZ!P#fw!F`rD3kE2bjL|W_*|p=Im9%eVVXhd!s~OvLPxOn5TSX!c@8b$c81<~r?O~tE{nY~AWYew zw_1Paxzh9}z`(??kRm@T4dsfae5fZ#gvD+E8rtJz0O*?%U@0yt0#xgw877)*XJP=l zz$%enh*Mx<)xF;vc;=GTafzXE71#@Ow}hssOY`X1lz;`(d>bGrke0C%7&hQm(KAg` z4TR66A2rr=k@{Ez_^E@-vhp=ZV(!iY7jHR)F`k{i*j3Vm0P^_Ej(X5D_gfg)(~i-deg~8JgM1%nhgJ@(4#E( zu^U3@_E*nc@9dyY{QPy4wDyGRAD(o;>9kG+`&hMx?#Xv8YQ*!#DJhga&_MY;FmboT z*W{EFtk5${)9zSG(}=@01ax*>D@*fNypMa})%R6KM3BBW`;%? z)w>6jDwaWF3?TOYYH-g1FUsbDco?;)V`FVRkxhI>d%S(hzLC?8Lx!%Qel;=X-lg=@ z0NBiBKHPo)(q}wzCf*GfK*vI=#mMK$e_eap+~2r3ml614^+A1K#AODw&!Q9axMrZ) z^E7uEd6A*v;-Bb*Je`?Fh+?yAwDmWOiGr7>M#{?-#b8Lw=(z!ljbSGO)E+xu< zF@DxYU%L-YM#M(KKwa#R^)`6MasLfQa$#Xwf$uXkV2*cvKq}?=c<;uz!QcnPh4nTo zjpM!zMjQ@@3E-7`uD>!G;BB);?zfrLelU3Z{MD|ZqXRN~$_Of=*3hl|a=*W>JwfOs zfxR}7-@qT@;Z(yLy*DKbcEN|&g3Qc2#27DCF56hB-+thA_vb;hYVfy%PDQ7It;gKS zU2tH|e3NK~--MTDpv`5Py+accl;;L~$5?q`-9zjYHHP$WXlg(ROthUhh>2MpBBlIl zZ5$@t-5u+DW{NSqYx_mp2aY|VXCqS>&Y{iun)7B!=?q!b278zcEds$ZD+{iT7j(2& zYgcplZa?2d(WmS=A{LNrKM!6C+uTfT-jY?Vtu@-u1~(uZJkJ=nx1HuomM2@IMU2MV zFajP^?ik+DQQG|^)Qqv{2*frn?4;QdLx3fwZ_A<8qRARJHJX}A0#dB4=}yO{92{-3 z!Z2~mlbKKtqw(d%K(*T1iKTh8L+ohmX)(G$aq6n1@v^j?YhQbF9@0~^*$>JE@PibZ zwiu20L=!A!ZoF9Z`vycIe8`#37~N;l6>drbG5#uLo=C)UKK9;Z_E1pw+G70zu*g#O zX#punftbMg+s1QJU~lrT$QUtfPC23kn1zc_#VX~!09I~nRWVje zIecIYp3@*5ihwsSX1KoXvFLCS6>$9@;@-#hBVM3rmYxm zD~%5JwAQpm4QNIuE~DjXXyQ#a85(81j69ldjCAnQngKrnUH)-_YZ zx$8tldY4J4ILfuvA%#2p;>s6C+*=p`l*vWbvtBRe0KE{`KcJheQj+y<$0o8 z_GfsCBi`CRh8fau4}@u#uWk7@<~tAf@_Q;2$Gm`mO)f2?d8ILpy!&Wnhwq^@5fRxr z(VGOB``Q_dPs@z?)%R?+yYuY)bmjeg{OQS^$B@is%h~PQr(T)X?Q)|6O?`SE1NG2S z^3!ABecsZ+D~q30>1iSe%j0Dx-vFpfKVb> zUI37ssH~;+uBRu3a-j7t&*8g-i@>MiwWl*r(XFvbQ_FMc#;(DA^wXXWW{l44!H&`L zj>F-*B#FxpE*nO{z(=06Yg z0#9-9AwI)cjh>eI!H0IeC(d+zwcGO-4QeVmh0uu1eLXKo%B%|$F6ZH-e{>RTy$^l@ zml}lvdCwM6HOG@)77T-E;q#6)?N$@fa%a$L4T?3!CjkybsW|2yYZCG&7G)>$-3eZR&Jo$Mr= ze~U!_bt;;jX!AofWf-(?1l~C|Ke+q=7<^yN0&YXSQD}-(!zN=O=hU%-EbD(pj8n%v zvVTQxKAnGKfWSZlVm1iwUk6|Y_9U0kk$~vUJNP>VP1`L8vAlhB*ni9(O7t5!;(Esn zn|9Y&wmLuMdfdOG20!Pk!*aY0&|AU!XZv?l^AFPb4N;sb%^)E3vAAnb&c_3U{~Mr& zN}8LysIfk9=eTULkf_){*l$gtkH=l>IR6$P{oep}vVqgKKZIjp5k@QV&dmw5w_LK| zYCneFq8dDWUA|DObs{DvoXCfggM$fL`R*^48Tl1*O?$=_njJ3>f0$spyLi%# zE}RZwaEMyLEIZJ7)R~vrq~)(`&wnGqPAq1pKmGVj6jJZLi5Dm5uFqVjNhO<(`lwE-C!#6I)b_UO#7Yw8Y`bW!J-qI|f3@2f4;DBDVZv^+Vs*I|(J7CaK2EJeh4>7R`G0N73BN?6udQ}{HoH5*v2)0a7ExUwYpm-V_ zGq|T{Ihr3XE(hiVG)9Ks5g=Q!(*p5^{}kh&cX4NQ%{S!6KhzVG^~4PWn)}vsVav&p zCoXyO=M#_bUPx6ypYlrIk}08oEBB~{R~gU|=tZk51LuET$i6HrZz6Z)NxYh8#t;ex zxaZ>jvcq4qJ5<5Kf(cj-<$&$Vzh+FHTS@Wao00q2sV{JXci}rS zP*7b}hZ25>uk}DIx^(y@sa%O3T(~+n{2>V#RrkQN3XehoGZyzfy*ne z0f8<6W3#UGZ=Kvi41I=tG-$OyIh*-FmpQgvg7A}VcOR}zYi$Cr1o95xVp)m zcz`LvlwzI{yU__)+2dUQ|A5;s)J~sBN8edbY!X73mZ&VX#Hn?=#t+v0j3)F23_w47 zQ+|a37NCcE0gB-pub#7KsdXP3Gi4tjSQBn*%b|sTqfNZKzy|QYFlea4Z&1Vi1(0a0 zM!Sp<%Akoy*_>R_X!PUl)0Uwgu&W}ej5h(xwX4Ep8sg(cRb!JmfdK%)29S7H8`Hmm zzjmqrzf>$?_iy;I@i1S?X(4ig|j_|I|t z_ksUEFJbBLz)Ehb%9+0?RzNdrQALhoH|Ezhw6p9LUwh+kzLuobo@6lC=VgH6R;v%7 z5(y|070V38`yP88r2cGTO0uDzc}jwAYJ%D;F6Me2CTd~upE_c`84U6u8UnQ|njf}* zG-`6ks6io5oY))AvtiU{`vAx1o?f`CZ_T>Rjc7QWhMzgqEiN}7ZhN}&29NYZPgr+z z2}TvhQ2uRC0b>UZh}bG#McQSe<#!uMD@8!gaGsm5HQEtSINKpO>#6qt?uEj%&eugo zVjdw7lK`p#K6;s9HMDv-HG)7e506bKA&!;FkHsxqxS7nl;B`~CQUPxegtn93rzG8V zSr4QQ2H?$)qTpaQJ1=A8%;`aDTHq6FsvkTn9geVSl$br7Wl@}OZJGXw!xTw9l%fyW zc5o1p=5Hrj(O4U1VA+6?0Z^iG7jKQjuEATllwZQzfb;`nFz@ixtwg|I6f5A^t==tNACm8(YlF-2Q-Vxz)x(MQ?0*xh7FtMxakr2K5-S zvVk}HbaQ^&-mnl=>AOLT$DILYWI{q9S+tq^#@ON@j%|J3aG} zaxg~M3;zO$qZl+auxz2z!PewF;;;JMCz>F+E_GFarrKFn0pyy!-)mzo*Mh2Jx|G@nGng zdm~&(2(?(=LWq-CvXntfIdECgD}VtUaSm(?02_C}CLRz35~t;enu~t7pD0dyes#Xq zs2_WZ_ZfH~76_cF4D6k(torC286NjOiU!W&@w=i|9nvLhBPBrC{{0@f-;nWiDt1o8 zfcQ6}2J&Y>q^eyUrtAP@0SI4zqlp)23I{mLgn-OFo<#9_ESAAm?Yh_3*JOD0CiG#j z{zLw~ZoG@vKQy9kM|AyZe>O%}IXW7NnXet4BtYy0`@cs)UjTVB>49#!9}@(Zb8& zk;@k%03H_!v^gAVCnSeikGuMaKa8Qc+&OAQ_iF@ws?e!Oq5g;_nb2F~fp3=%!*t3+ezkB#Cynl2N@?4{hO0^&^8T~Ndm z>#icYu>&GXcn$W6foAp{J#Z+}2iX6K$H|7GJ~(_5u;(j)hM%piAA(laoO&NLFfXl* ze8^w3SHOtwNs)d9_;}f;Vr&OxUg#xdmN*xeCh^t`0s++QsoOj2vmy-h66PfC*ERkA z^_}e_AA0>jdm26LC`kyM%}8O4nG#A1`_?f=h${VtZ? z3CYIzXpt^kyR|yE1pDr|04m11&cBAtK_#`h*}E4D*+zy$l5!dlu|^J^Gurc)h; zF{H>i7;0R*E7st?HaCY}0%7Xs!o84Uj6#)E0n6n}3zNXa>m5QLXnkE%Mn1w+EF4+O zRxHm=y?)e%z64=T7DAo2>#m%;Vtu>*`;X`;KR83M5`A(0;k2<7jWc-wMpUeB`u8pV z*AIavX`KB{Ez(_)eE`|`p8y2Py>5_cW*GPy(PZ#iiM4$;jqUer<<;t0nw9dG{At(hTtQ zZ=uE2=4nBIs1qUG!^NQg51IdYa047_q%6V>zZo_i$&;z!-)xm}fB#EKb4fH>^yjSAtW-+T zj>CS7=E--=yhm9xLTC!UI3+dH2!^4p%PIUXbb96thf@>`-VCp6nKh`ErFPWwF4Egb zxdjhglr^wUZiiq|&No5J5vIKcN0^KZR0I#r{p*_NupqKwYO!k8w4!UV>Wo`vh|u+4 zK<5ZtZC7|i)CuOKBfB?uXuN2Ei?JB+=x>qzi_ukpe4fosYkkR z!mn!|U}~jx2pHsYBU*p57E=*lUrB#Rhs*X>J3~V!EYOY6jFdFFyqMDfTA4(K>fIf>45PL zRME?Nzk$@rzdGENM&B)VaWB#Q)cDsmojYL;>c-GF3y0QwJ5I~S=(R#fJzAuxuGw>t zH%i|#?Qj}s-UlXa(fsE5NbuGHJlqonJ+cQ{uTg!$3w(;4Ap6u_)9%dSay0YICS28$ z5LspCg{Cprr&*9g#TC)tJJ-``9 zA+)K)Hvzxa?)ml!Kr?yN(i)YIXiP#bha9Gj{n_?Y6I~H698V)9h_k2*l%}~Hhu&O` zO}L$q6Bj`x0vH*Ci%bL2%~iPB^5mTc=5|N(V*BET?Pf_p^Kia8cz#OM2<*kJ&=p%e zI1jV$?7G+-GCVZg{M{1X;RvNO_^nw#S!-o4H;@PSJcha5LbVVQkQV7r9SFP;2IW?W zo)1Ii8CKgoP3dW(chjv@f~^%a%}>WLZ{$dm7or0?EvK%=A#~Ozx?t=Xk0sQFK7Ur9 zf?|K9Ri=DIhFW%SzUwXd;@a5)_v#*%nhP253`{0%8z!x+phkd&Q6=pxdo!}xS7)CX z4^{9R6Bx0HFRoY>_sp47Zc%4NUA;>bfFaj+JKlvq)d@BN%rgN@ z349j=E3pUW0zh(js`XqXSg1`gU0sLRT|Bv}9q+<%mvMDJEiM$oD8|z!CTg{mmEm5j zxX=n^1MF4@XP*1HPg$QjUSdw|4efhNA+^Z;y^M^Z9HJplm5BKLLJZWSnq|?|A+-%!Rt=U2*AO6Ul17{7r|o(Qm>mPrOr;y;(J^881s zx@a?5O>0NyED0%!M2ByH^KoEP;$OQj{{U$Tmd8px>-NMJ$~z^&o(^oWHs^h7hJF-T zu--`0)t5Gj@W#_hndq_J#y9~QKb3_#kvFi}a7!*XzSRFFZx-OVR)N6;`*7341iqSI zzN1%f|ID|`wf&-`J@2{NTJ~tx?c^agCj!sYuSNCxYL zdv;NG*Uv_FTwabuxfUB#XliDvajV3k^JZNMT4qz&rptBp6j=T=)bP@@8KKY@osv%v(A#H z{SMEQUSp4#g;dgGM}`x!VJiq&1X zpF1SHg;(jk@aYd)_vmnD=+<`dZdIB5pf@*n_rk4Sgi=!v9OyO=NtRYO?p}=ke&jcP zKXTU1MH2q{(v2{v^L)?u#Pvdgm1U-?w~I?6-xJMGWUIu)-`E-{#`Cu(zq3rfM}_Fy zp=aWl@tYykcPW`Rxx}fl3p>8;J#aVg++!`ClrQP6`$u@M409jdI|GqsO~dq1QW^N8 z$Ep?_FZL&+))X4#GVfso(%}pm@(p`hx}*g-QjP^`gkA$NL@d?kq<3Ox7Ln!cws?_K z-Nic_Y#{Xj2A$moVO50`cT$uHr%NzxuDM9CL71x6`+B+-@CjqqD~A_y z)2%+wg6eUk%zKo-CdOcVycxR-zrH9s*+^?ndel0lss~1Kl9T$KT zNK(8W`t8}PB=z%r!;b|;#p`w70*hY1CaZ$#c=L_#nw)c#%8o}e-WnN-nw4bB1cf9D z(|h*b19S@B$^R3caF4Aj;mh=>d+dDkgd$>ae7W|xNulsOC{EkA524(prtz%l-8tRo zY!tH!k;HINHyFq#Fqqq8%dyg1mUK_HOy(o@0?jiw*%tysG_8{^!J)L#nBb1UeZtg_ zy(Xi#BsAsvqE$M6s=PnBN&Ea~#`u$?`zggYX-&+NpsPwH_2Ye-w@E**LgC+A zr8$;EyP-5?g8XCh^4PoYUW*<7zPWo@qY*M=!s3}F(e(7XNLHGA4*qi zgvjzYee46Nd@WCG$1C;o91R;)B}cExOVB0}n&xB}l==%1;bj_%d@)J}8t|2OeA)L8 z&-;s^(@jz@VabAfp^a&`%_@8uT!{z49Z4Tf8OgzN5-e@0Ty?X5cs76SGU@F85~eLW z%oElX=gPn@Sr#{>mYd@c$GDZ30?M@zvlkguht1*nk}F$xSt&b!ZQQsbpr-sE(Dn*7 zQnLOD)y_KofnS|A663l-`Hr7eHBES```JO1*1Ei@cCHk&3o2`!XhXvV2XBPIH|USo zhHva2n3b1i8zl6;f1lKyWk${8#@{Y$mY1datyk4V{>yUru>Y%&Wp8v+Y=_ENU8e?L zHw$^nR~gZ>dvXUfp^E^3^Lc3nz{0T>1ceX6CF=2Gru`*D{BvxMC{E+4K~RS{IZBQi zP1COHqO3h(!VVzug~kDYS9%mFVuSwXGhTkj@|QRdgk4<%a#p~T0OL$}_s-gRM2vUw zm$Q1@@zQf~@3i|%&mNjdpazwBsLkWjrhDRa{i{2|230=^|GKs_WV zvM`T+b{=RKK4W@{^5elPV)Ef%sZHKhPwkLwhj}%6+XzFcp5PHXcEY{{ zeN=JLL_c48i3{-mql>v~tQ!2Ld)&Zx1jYy18{Eqes-?=Y@{FBvm*G| zLwE`IV4H>veq(nCeA7M-rpgqTI$+Ol8jAl>^r_6{UB9!qyFB5>Y8zh+??HDXH)h!5 z1;gXA+g4YU`DjDi)&<9#{-8GB6I>_X!V!$jesaCCjF)XVodNIL{jT?h68hXXA)}M$ z^pl9MNSG5|l%>4@gPDWctfm#qdPf+XQ!Bde?Dlspq|atv(h(DyM%<3VJW5YBD|UUT z9JWA=mlir*?Ffh(#b3Q9jO-fBeX-GtorRzWTD%!-}t z&lnzMuhbLTB~vZ{_VyqItcrKjPUGqEE>z^|36BKsesb@dEHheVmUJ$#sGyaMjii4P zFPNPluoqLF{7R0(+}=s)*eIFCZKB$-yt>EDEhnZkWpI?~tj{ipj_2pT`=pY!0>`68C_M$>NGut-q_heLuhjKD;!qVb}K-*5dK^wM@l8jvt23sY$#@e;JMA9Fu zO@p&0F1-rP^pbfg(q6PBvfhqRM={yJN)GOGbkk+;Uy}FlxW}>4JkQ?)QChCpt6co? zVkVnbHX}bsJNJO~Q_N!!2&6tA89}%>x&9U%*E4vj=hAHyPY{Vm@<7gNmY^<8?4bke z*eY>{Bn)w#!z1qgH;rv82bad;Y?4Q;?$=9|F=|L|wwFcurqjj~+_p5{>NQ42Xcsl(PR%g(`O9rut3_Mf|I}IQaj5~U14$NbVzW&Sm*Z|<}jeXI4BJc;$i z^VDxf=zFvFI^CQ>J?olz^D41uIK+v}mUqSFx}G+#=aPXs&G7qh^_#9-^;2e#Sq+W{ z<#BlI9^KcaWc?U@ZFj~U?=$3G>5HiqN1Z>KTkT9bb@_+zV4`T^@YOWy6#M+A&SEiDce*lodhwGtdU(5IICoN$vbW z%aah6HQznnv+FaAN{#7jkfl|dB!&R7@6G}Ov#a|mdLHp zKskPYP?b!OzZ*QqY{u}mPB?Ss;^nFZ`>8%YNqzulJo6hddUb=-B*HJ3uNQIUp1*9- zyORhJt26BaPs=wfF6`eY)imfZ7;xV|wv`CZ7|oIBea*s6N6$7~ zgjBofn1}!C+8;O6OOM)uJ%<>Yp=hrFi>NJTxeIwSr z^k$s+XB;1<80(?$+$a%;4So1iMp5%P`s?mJ)1Mr!NyaQKN*WPC#$QbnW+cd0s20NM zwIUQoMZXk|<7uAVQj)G4`%bSEO+EMcazE|gmHc1dDS;K zzEgjX*EO>nr+kKD+eDT8@ku)CB^YNw3bdx|8row0-IS%@dX_mQCzkddUa6V!6QahN z4ppQJteM(-M=MdQ=NST*+eiNzLZ`8X#9S_AywGF)IQ(@~bWp2G>)0UYD1C6aHpmy3opoo==cu<4nnhB_dp0 zOS6+;U4~>W5R$vzc2&hG+B@}eM!7 z?Y6g7u7-2TnewUdsNG+&`w?A9z&lmk*g?Bc0ISwH-Y@71lan}KDx9}3D>?XV?IJ*% zU*f#BAqD14Iinq9wVbfVQy0k`e7pk_Z#<+6%cbv{jTm9N$IIeEezVG^QAOohA0+n{ zenY39T&e=csgWSk)xyl^k7gPD^ETW=sKE)P!JE??rrxFWTwb_3C2!TG)=xj-=bN9R zLrRtdh>>-DRE1)1KQDe?71CQbdcGl5v3GC3T^b;b75kM?qGir6(H7A`&b@8}{+uoRI}cwu*kIJ7GP)K1_))g{6Ql*67ak?YuyuW7=+H@kYVfjuk{Q3X zhOD6&%)58%CI2hmtuDRxpH+lvU~!oHn6sGls~u9Mz$|vw6Zdpo*K%x*83MuK z1#|PLh5)@`br-I2VaCaup^yLSd0`ij_a%t^dwrawg{vvZC!H;$(W>u;P0q(Pdll!Y z(ym23HNzF{A|o@g=V}GwGk|8-8?hGP9s`Owby>fz-9bIB$R=SrPjw-UIOb%EN(QTw zcQN4$x6-i8SM~FRm}Q$90i-R@__jMfUmL7nvB>h_D>c)Ojlun|JxVDrbIrGt0xaA)2 z`^I3XeSSvdJ0o;=foPjZ?OPQvpb z`5R}xa(w(v@RrBpL!wi=_GvT?>>KS;F}yVdyt#KABrVVPRP-9{_JR?HlopdSj;^!|BfEn<+bQ0T;d;(K zOtdY}dhl!wWq7lDw6>-RMv*;nJd8Z+cZC`)jPIKFiBX&XnJkCiOtf|S5~moyXhF4q z#<|wp{U!91LfFecdFbl9pYtj^b;z(L*{L9o-}#NQx=AA#dCmy7#yPc?Jb5S8BBfdS z6uZq=*$Ip#O7<^pkyVY@Q9-q$;q0jGYBnXqp`s!44 zz$R&(>95jXVBZBid(lzQhdsU5sX?2GCQ~lyDjl@o)pO^ZkC6;0N*?m2aj!3!RZqHX z&|B^J!i`(#a0;5?W1`O8rkBM(l`ZT=6Y@xC`AJzFL~GW)%;o8KNL9h}^HPZoW}g0J zZqLCsGYANo^lr9@uOJG<`IXmNaN3{wAm%~`DF{F9s1u8;GYEBO>7lD)jL zwDDbuq?P&3Cu&#E(e(rGXE&EO+pOb182im&_>-FsAcz6=AB z5IC8ndms3|U(&*4$T=W&tgQUclBE`fF9y-oQ?DVGkNOo#I5+UY%i2n zIVd-r!|}qI{_WuU8}68y`gnU&i37I#;yH(;POZjGS4z+WfdAEbh5}YCm1nB}Kg__j zWPRazMCsRjhl*PBg_6C~liNN{G;x0)xBG58i@S=;2X%ey18NSMoFaxqS$S&?fHefx zTq$jQDW6tp-Y4|B6R+i)2cxSxQ7(~@qx%f}lb>eTd4KZk;hg!P(2=I9p+C>dYCa|O z3hQ-5@=FQ^;!Tawp z$j+Ux!O(FTT|(_Hn7~fzi6Iv3ux<41y-J%A*cx|fqv$y<}*3lhv8)L~!Y)p;lhdmhw&`vl}$L025F)mLJERUyb1EF({sgH3Ss8EBj9 zV^3l7F3}XHR-j3`Yr)7C;_xNLGo zR|-0Z2$n9}SOEzIe4bC)dXN&pIE7>Ty3Nn~F0Eu^0ynP!h6^vT%9xJ! z{q#ajFL_%I!XF>s(vqc@ONEzQ#0nK%vMce^LofW93N@o>dd-8IK0EDOc)x$xD`^GR z`5~+u-f3hWx;VPok*Z3tS#kdAUV(as?p$|A=rnO}Snn#>YHH_}*2xRmkHdjnOR`Ca z^h0Oy8-9Jqt!qDCAN_3iiQl*w4&+;sO*s_!Ip*!*oAj(Sx`i&TQi{dKHub4yM#JiB z)%1#ql07_Y6v0Ib!l0v&#ZoAvLldkpXrFMO^u{IZ)cU+Yr#evX))oy2Eide=kaY+~=}rJ6vCWt6*H&x0ouQu``EVp6^w+0OIImAv zFX^X$w7TkXCAR;+2je^a;J-%Z(b3PGpV{8AMz4NWYxHPuuI--kxUnQ7b{D8>8ird;8`G8wmCTDu_g=o%Ps&CSHz6deP^Y2a1X{bF{JLg)@uc%($;=NZ_hVz1X^PotQq`h&_34p=8IR0yi)JJqQTgB0 zr7oJ(mVEB?l=4`F-?1WF;4Q+eBE3{~m@yU@qNsG+0$fcYo3PVZ<^VY=^YcbW5@a@B zl#+^R`^K4ELBcUCB3~ND)ebEc?sAkz<_py@Rqqlw6Mt<5vsX291b#1yf_vsWl zv%Z_Pm0^^<9Y(|UVtw3$lLmA=F{VB+&ktd<=-$u#vBs;$pBK|mA*jE}|J9O-PSGt? z{oQhyy^BJeg>?I+i23t<{$%n7z$Y8r!&!t4)1`5K-TWD=EB;00lEGRg74F2sy}aSn z84&tL+x#iS896|&Sulfiuh$dTN~x(_Qu*7w zu3ABnebF-oV~Q6ZROKsv_wI2$U1kDl7a!M+EpkoEM)vtKsc-#c=@f>cpN`)Y4h!6W zKY5uEZRsxnd~&rYzOzB>gwkoZ+ksWBRs}D_}3y*t2+((SgVg-Uj7N7%ewuD&~4f_4?%RyWsomFlVi@s;=|Pl&q}Rh z^9SNlj=6?ZJNbLq|_#l|xJM2Rel)bEUp zNL*#L6>C)pLN(z_=*hxRT$)Pc`u&4FoOb-%Q?i7rxKpwes*XIhtryKWU%dnzyI=vI zF^&a`!pM)Hj_UU3DofA&(4(Cfuj`kOc0O^W{bVJG<7Jip2v#E0E_C|&st+(-wK=E- zeHRzm>OAbGu`t(E80uVGwKiR{ylY7z-5-5K<-jmf&K~N9rrT6Y18&f!m%Rbr&-@=Q%nPgf&ET~!O^g-sk_v~+y+d4<~W;qy_~c7 z02^fK`NH{eEMzF3Oh)6I*7CR+i^Vr1_1whw9SJE@kL0WQMTlIFI0%&QDEq>@jqdX= zH`v(*4ipT~N9I;UztpfBkc^R4F1DxmvcFEP9e%^KTVq-?kUCvKTQY{cR;hpI4eD+8 zy(GC6w-@~fDN83kw`wO$KaP197nKfy8bclIE}6b1ui}tUsgtbo-ij6U_O^B}fvJg* zM30i};HZUm&MaZ%+e{ z%}vXQn5tqZ24;ikL|fnIU+A{bPEFFE!C!bWJ;^@lwN5Wr?c5rUOC0R-$^GPhtt|hD zPD1zTqsJ%0vTL>huFQ|dou_>Ee<0a|ws-nal#B(rKFS3fov*(kbeER2@jn)c8b_?y zkWdbgetvv8b~=j@JSm}guH7sz`#vlaO(0ZCqT201e~QlRx?I+5eB1`8es_yjE^(4k z`3t93@FRF5Lwbd!_R(TTwj~T{Z*LMD=+eHIP5p{@^6H)c%tXG1(9PcO*+Rh!$-M(} z9-PpS2wEP2Z?&69{>GyLmf{R!lbmRp4@PUb+t&+?J2V%un?Dw;tVXM5X6C6Oj0(pa z&XU?zI&!C+{2ZS(~v>)@R%W_;d4WY}R z7Llk}%}ma4LT@IU-X$KZf)>SZ!snj?|MX1R@AdTuv@H@t?AZ^gUWQ2D3~2y?INkx1 zwD-hCp#vGvLry#jjSRfzQQ96mni1#Oa8r?jew70fMI6X`F;!Kp$(|O+6rS!T9{GNg zgoK1a);sM^#2lm3I!3L4N3>Rk?AzZs958P8oWk0XF9MOGm1{IbT&3qsx z84eV!{E>QTY5ZN_=fd))86iwt5M#x-7Vi-0bSqoqWrmkXuY9{#hD)pT(#>r476BQ2HZH_k~-1Yq}@(i%kX!27%V3z$Ed`76(kfwLSj_p zt^=oL*gxPB5UwC4mIGj?^#1(@Y*w?YbhNh>zim@>5Rd@oKHzKm`mNke@2P4hIZC`Ec-6#t7#J{F#7PlE^Zi2}Zg!FCsqv z)VaeyQE;qqkWj6)Yeyt@pOM&H&vIHpJm8bp!=prM_#fT9)rb)J&R!ZZVjS?&3O zhB>(aB|12+Dqo@JhK4iwMYvL66Fd3r)Bu~ghm12{Ae}emgUZ>YR$bn&^R5CtlYbAz zHs*I9fmVuVsWI~A-Wp+|)>sxMa?v_q<}$5bsbjDZWJv=(h*)EN%Cnk#?s46*yoK#M zX*d}!Np#7Nsgk+6z5^87eU>0lkj+WJT_~FH4~JvQ?IgGVN7a`{v(-lL_Uk;TA~jFd zexb&YP-5&zD?w57R5TPtBUNJTsABAAN@8rQsJVtB1XV#RhtyDNN-8R9h&0j~8hkh3 z{jGcNx>@<-T_-2+IcuHs?!Dhv6;)$hkxA(wg^GLm&|q7+Dr@pHX{x2X_xG;y)`XV zj*5Q}v1*+4CT3_(0h4PU4D}@Ji_6YeMhPt6A%wlvisr?CI%A)AG+}Qj+1371Y#o=* z)aM+qe0(*rmTUZ}WbAmW9Z#Nj@9U!w@5@c%PNzDe&cDs>m%iYI;0 zEA;+^I8<6*X|KAT`qZ?_ih1^f}ba-HNAJ`_+d~_qdvUhkn@%IUG zZ;Jn!tLpy@YG#*P;+49WzMyyER|_vH^jet7K0m4yrf>IWK7%{=K}k&ztp39M2jgH2`C1qs{Utz3l4~^QCp%=(*m3HpOFzP{9{PL zs{ilY*^eIG|55?wm{WkIPS~(%T0hf|f zs;ljtEQ0qA^x59 z>t}6fq$?mG-5m2Mm<>O^eF$FUePW*+=kA|-MnL3|>SH&kZzGrBGcUsybJC7_Yw4ox zC7fgTy7csVG#L39w09>dKA8F9Yt>(IoITH+{~?SK-tYJ1qMZMK4(vZ?<_l_*JIqqb zNHe~Oo9**|V^xR;8I#`3`*?oT{QY%keO^mhabX3iW3Le5eM@`fphDNjx{@b(3Qpi~ zXb|rB6OzG-DzkR?aJMqy2sSKZQuKX-Tpc*fNE%)IJnb}JTqtAVvvM%zSW=66oM%_J zrqDfV_1mvFoE!iZrP&<2Z>{iNc`PB&AP9TTkl}XIg@E!-$omT;(m^Vjq^=B4Gv2AZ zTVRtl-Njq;G^44>OWuA>m;QyVn^<~=zY{W?bE+D{X9$OOf2Sz&&t(cN^hDA z8YCTRqbYazTc7wjIUOI+Q3)^(eLpD}JaPSlJEbZoN-ChfBw6Ty=)`iel#k^~Xmw?v z-|!!n$i<$+X2vNP^<1k&(z^*!a#J;~E>kz}+Bv(M??b4{4wz#;vzlLTmQ>=?PJgq2 z2?-ME8g(OTg=5`ET~79Y_%kI-T{(%Vo1kX7U+zOnPszQ$Z|IB%>8n4VP$#CD%ZeF+ zbzUas)J!C%y+^K4C?EXBy)}pX5Z?HNi_Y2u-THe7wp}^BZi|BSh*!ei3B{X^Ur~5H zp-9$jLXL89sPZS|_BkJZ#9wXxT9XNb36@4m!3kE{8{d@zS6sf?K!j3sW*-Vkxd>5$ zk-yed<0MM{>1gWHZc{qpckxR{Q_08uUG7Q2k$3*5F7mH^f0-$r|HVCr<*C0OMeqEC`L5LC39rp`;v- zxn_M;6r(G*K_}Z0L7*Zz3+eeWEUIcTq*S|lDYi@k+5W0<28CW>+IA8+M)`5xtPCdJ<<)!+9fYJo@jZ9>Ces(FqHH(3}`X#e%AnZ z>5g5n0O@1&pRw$t^Swt`dB>)B>P2fOHMGZJ(3bLaof$WEv$h0r)%E-%q!fAO0V-&p z`eTn8$GeC&JvnTk%);a`J+%@?7l+h8^8HBk@ZYhSsT`cu#pST)H4O$m{q^IR23UK4 z>8gDL-3;r-N64}EyFBYfd+M^ts2jcP|0fTeNYDM@jD1peIt=S z>sCPEK)oqQFS?`Q`hynO1TJp6Tby<+cbsV7VJ4~G+6c#8(ASI z)-4BNbSI%t0(SFF@~*e9J6F|x)i$dpinmWat?rPhE>J-eD}c5}FCC0P!stPreoA7t zfZ>^N{S%KgK8*S!h?y>_=lIm+{hchO!I&#SuQ@yo*HN|?MdP*3W0rW~TbosnI55A< zLAu!$U!J-%KhA~hS%TaiW;)EJDeD57H@K82w057g-Z_r8{pQnB=^Qx#)IgW+7!gMO z+DJ1KWCicCOr}5lMX3)*HVB`= zJKKv6mIb&dSp_=q`;Vi4%qD!>+uA=+$g7O~sgDzd=IP=)uX&{829FoWJ~wyV=R8_=$Os*>%hL^`Z>N z;7^qJa8a0$VgDeqzoE>;ptaC~FkJ#+|;i=6$Sni_e_@u*kJj1hooN2n~ zA>aGNWPoDM@@=w(yMMPhNXkw(A~mJQ@wiMBt+7shiYo`JdAqWie_UJUX@33qj`2HkEglSrfUL1Orw-R0vh_WEdt& z4u92jpb&=?cn4`%%hUr;$BNSek5PI12d| zRt7zcny!tMvy2!!=OCzfDSpipT6LKI7SS-S7d zQ%E@78KFaR0x0rg_0NA}nw(r~fr?)G@Podo>x)n|2Cu9jaMtX9EopYCUE~NjmGBhVTuOMnXO6Uxc zYR`8%<;nET9QT1#_XuptzkA9@1J!q?;kQ#mapaaPLtI>xwfiQHrcIxB#i$Wd+&}L6 zL7=sG?3HQAosFzWLIf^px~|+pqX~onrgkh_TUfjn`=oqoT~S-%_@pzb8-}^_PJNC% zaL>?&d2C@Qsck(M@PehU-O_evuao@JB5^6c++$_~2*F-0Q$9p@R=&@HVN2a4R_2Wu z9+bF~X4z78Kv3KwncgT*g&0T4qB|osHCRl{OstHtUO@!X$nNeE&2Q#5 zZCWLIww|U=OTyQ4(iRtuCRys5ZFiB_oEum#zlaOXhceZl3EraX2vQKEQmpaPV^Y;%ALewKS(o9fVb4-$`ou&| z%}&X?JFq0Iaq4FPXz|1QA4Ctgu4y=Kc==lvFI`9X+1&mW zfVWR^v`VCn2J}Yr;G-R;zA!g|`&%^giw9Yub)DFS;HmOt8qn3}+i19}g8x$P5vMc+ z(Vc6oW>6T{g0?MZ=E!X~_1(jtiE~w5QyuGc+O2vIucCMqRi{YqEjkd$`CC>(bIR3KLhb zGZ#6|Q?0h?Bzmru2Z`k|8einlW%H-EKQsoBm(CFI)rc_5exo#B<}pW>m$k~HP;54P z;j<;hr%qi`yP$1QN3UotG%4S+C0WzY5hJ|a&mnf%<@SlPnqh3S`&Q8BdEx3+RpO!? znTYbI^Uzj;;!5zODr3?pY0u?84B;O?I19X_0QTQ}IHLEanG(1~spXo2l1)#^!0-}pv^8mE&gK2{lG~Q5-xkku%kAEXB?LwnyzFDEEB3w!f2zO}F1!3)qpB~S=uxNI z*LXX-+nsiJU-6_v=(lIvDo^@s0xT?XJu#L65AJd0@-|SyrNWRZ;WMVgx!t>0%%&@2 zEJGY!CX6qB=ujaxh|C}*27%{_p^yB_kFE92r4otEL((d!^{kbRf=<(?f);o~VXZ(J z50Xib*RG#tN;~-G<-&<4aNM$kIe_nefOcIEu#qFwtX6RXUs2Z`;YW` z-YyHv^y(9#MI@fks9@g4sOLBZ-zMr3zwieJ&c`=I;@IjT@gkaE)^jBs^+Y4Yvjqan z3v5HCXAvkWKOR)bL**81F&!Z$EJ2Kz(cu@nu)hkMVWI?yA zPF#uFz{}w<{qu3vdzVwsljj3>a$PsLcBkW5q1E%MvC42yh7{BTz#XS-sM-BAlD>-bw z2?=y1<_I3NFxsHE*?>I;0ri4av^C1iu;$^*W(7Zg~6|%j0{uNe#RUzQ1GS4qqe+LFGb3Xd4=`Ca|%aZd^#A zblmW_G@jNc^d)lBr+YkIIAYPV z_j$9&VP~Ep)>GC*CeHH0#Vi91 z>k&p&!SH5&_@cq?M+eWtp{6`AE7SoxybGS~P%%$acEDFr7ug!rt>wM%cp96zuK3`h z755FIp4QIGZn3Q^EtfQzZiA=Np#vH#Jv^;eo3FV!elp25sD|L}9*>aXm8`acK_jaM zw)l+8u$E6-`hC`YU-Iq#MS^ITsu2QYiVGI&>^ch*eg?>^Ao2=~&Z{utTuK+8;(#{N zuF)6$W=y%etD@@8BFmf57=YtD!UkpI)oC*# z_I}>wIt=OdbX``DK!`7(P$jRjpy+qDD|*=HPu~Jr>*eBRmQy@=;0z2J6X>^) z=&Mt|ds;&%Jk{Xgq<=LwE1hXO8h>Zb;jD(z%-sg++eCiGlEZ+wlMYk2?j!SPG2g!^ zM0rOE=+DpvHWMw?2Nwd;BNxOfaj8;8Tw^48^YNHN+^WhohQ1M;O~P?7>zOXKpkwfQ zJs>D0BPkcAXMnK-bo&;ns`?bRfqNUtMXRzifSzt{)Xq0Yo5tQb_s~XAYQOlhHh}Jf z<)ZvP<+#G}ZR0`>_^+pb`zK{hkC=%KiY;dqxTSLJxc-yQpnsg8nIcx~&uI=kh`e`Z z8MJ%fxejl)S~1Qob*bcph9-!qynFe zo-}V1UAU7=uyQY{zG5YVzXT~GHyjchZb9pFB*iyJ`AQt7i?=Q_fE1Cup3Q?%*gR1$ z;3sb{YWj7HJ**@VZld9r*6@?*31MqCG>UdoI?j}FiMXLREr8kgRt8fKVvv5PZG^4a zCh!bEYv2n#jd!M$9~$c$iQ(dcL!7Q-m7TDChH0!e`LgU^co|8d-i|Y5>i)>DWOk%dT;;KSvs5bD|L}x5%x>s~{~k&1mZPqK1>*tt@o)C9dwMHfi|y z>OIp_zJCX>A*E${LLAPIA%U_(OLNsdKL$%HVJ?Pm4ZXPOXJ~aV#`@8@4_kS@ERP5! z(>1>Ns~x65hss3;??6{$Fo+xFxjdsm@k8QvJD;XWl>KvI%b*L5U6xWBVJZQ4w+dKJ2hYq@BjDMDA;W)_%783J=!KmWom z4h!+OQ!!BVdLb`mX)EFK5zg@`jNxc_C~O71oVyGMH$&Xk^TsCyJ-xv)z*O8R9?;7W zJtzoMF=34}{qdkO6V})4D>_X_H#kSn4^ZRL-9vFO%yC-Gd6q64D%KW#kze?|iV`Za zccFhr4z=+xpB36EDtYmbT66iOa?ghFZ^TJ_RLX{L)!E&lr_GePsv5nstZ0SP^4=k+ zus?=UV|!0Uy+5tBk8hPtc|VA9p^GxpOH%6519@e0Bg> z4lC3jJF$7;zJ(MRL%SARQz~ozX)qjyaTz72Gzo!T!NxbyW?!y z+U9mYt0NLWvVZ3OH2Op)FgAw^8Z32X-C8`D%m>Cph9`m0*J2Gkp@3=D5=zHOY>wr0 zk+pBAAaYr@mJVj>4@;o6xDK!u-bN1ZXjsOd1#&m2dGdtCq(8c?Ya?gIdgi7vVka|= zSBM{adS@#Kt}I*C6&Ga_5Z-|*vq1J-BDW618^XFBG&i%L)cv%B@VxcpV8!pvVH`b$ zN2|BAjy0(_u(j3-ecqv8X-Kuhb7@R3O0=IJkE7QxdARTz@vc=7bi;saDngYa<5;9*J$E&D;FmNoi%mhT< z7yc z|CRQ$zsAc>w!WlYMT$?o@S%Uq^t5Dcq^F|Txc&p)p>2Ye{ZXW~J~S(Mlfb4se)PMC zgtB*ioAJ*71gTIEDIS6EFrC_U9B7cdAH*&lbWm|cF(7%m2erl4kUS9wcP1R zYY{Pd8@Fvup(+NL=Of=1`Z9>*)fed*hoCL__k5VEp(^C?gqIoVL@Q4}8*HjncI^~B zTuvSp)6JZ>XH>2ARF(V&XD6H~xQ~ybw84lBd6rnS$1BF~^3vP_vvP7?LW7Qu6L(Bp z)&F`$phARP=%fP?Pt4u*J9mj0_gk-lEt;`Nv!tG)2*OW_2PqirNUq zy&ru7lhZsOPio4n<2^>O3_DNoRHw6(LDt!cAEb1Rh>9PGanP*ns57F;KOIrC#$G)lfq|>G_RFTcs=QUY znd)FAjuXI-IHz4|m84uKNDc#(IkUX0kmx-M)mWqfmUOh@FeanVxDY z?<^P9*Zbf)^m}-(QKLSg>6%o_WwsvO#I7_Uh-QD3$XojRhSAap6z5XsaJT8RpOx?;s1Dz4C-8+DA(#<&?A!JhaQ-&ftsI^%{s}O{pvWV?N2Vp?<2zUF;z1BI=+AWM@E;zaRVE6Aa1-rotooXP zcj?z&5kuje16z12MBi*7Du@e`)!Qi8pwxuww3~i@3vMlKr9y4UV<`wLoBB{(rm}J* zZp_Oo?0(kDIx%G1GPsXCz4pN$&vY|b$M0UDxTkYs~06P-+l(?;_7?tcZ^?_OBeE~4zMdiF?1J+F<^`A zigHM9+N!k17U2zm}A4gY+0I zqkF&a*WYVs7>LeC`W~4Bb*qMN6cJ~*wjT9q`oL{82LwuAatc$`MZ%=xMt~o+I!AvD zeB16nAx2wLyf;?jxbtZL{v`pffC@4l5gPnH`vArep(*fymmg!>O@}IZErl~>QNZ4$ z4S!XIeYQ@IoCj{Q;!V6$yT;{Pfq>B(wUb_AxX=8{l?7hdNhcl7xz0AT`MwA{e4`Os zEALGn((`4Ga){gn&Vvkn#`9D4{3$z4rBG@KH+Q{Vv(uSM31Ghi0zB1%!apwn(&*kr zjZ4$YW4nIWms)!#$|rtIcGyd2N7mwOT`5-r?LWaQvi%zLN!B?5DT56zmm-;m0v;FM zOhqejZu{S*qm4w5<=@Nx5Uk^pO{H9H09F9osm9u=efsvHq7{ow8KDZcg2-8rzTAgG zUj82$?Z^W6{$_)0E~Shj;8%Ia$#~Gx|?4ZJHIpU*f6GW zgmnn({7_)u!ve3*5#4_=ed>rV^FnTJzYfMnh^cHH+DC{7kd4unu9R{QP;R!K!KZzt zJ9-=uU1U9BP|5v!(d|m&m;Rpkuj`x2dS`1gLnXBaGY=6IW>|)o?DBYK(AqP9aPX`M zcromR)>{MS(df5mTm4HcuO};41~fjj+f2#K7Oi=lR7k;+V&z{wjwkHPlmjC3)La52 z9e7#;Os{o;e{CqqPOb*H$pt+6Eqv0`zA|%{2tjti2}1OJ_J?4Wxa5w$6SjQ;US*Ma zbY>gr6{+Il52cAQ@Qp!~Z~7XP@1rI3(s6CqY55ie%{i&bd441HEy7@0ooHG9*p;)N zYQL|5YWO&+#VB9x_5!pU7qdX_f*B&TZg!-2f*-f0>eNIGd+k4e2j2-g!O~>jmh{MLB?+w|o9i&gJ6bs&aHVGvE{kFfs{$X!K~&pHgLIv}$KQvG z5x25z4v%=QwD$b$1ZEil);9waDs`mWBfo{XeqXx3a#c?{5P$u1!!b)naY* z4aNF|UbLU~1V@97!}G@dBfn(mZxHHajm|F;Rr}2EH50tbmG;XJHnx!H!4WshPy2VQ(7KBHKXe{u2@U4Jg|C4JKDpH`9M&u7;AGQJJp?Ubbg#=6fby*v)^FW6T-6MCj!sS zeXd-kEXyOuOqCn#Gu(iIQJ(s9;HA%vrVaX;>pY|r-d|^>)SoVeKe%l}}a{h|QFf-9| zysM zafEkPc>66neJwb9?4Bu|(3TrFal`MHlg?Sf$e|;{c5E*6f?w#QlY2xq&y?nl6bR~O znx2xpuH6j?XAnFF;z5(M?dN}37@*|_Rr{a`e^FHh2zPmS5#biCF^CrVL2A)kcT#rHsDFT4J{&u*hKy`SG8YoZwROHo`RftpWD+pyRX=|1 zN3OH>L}mrrrWGiElZ*1+E!E8b#W`CHtu=V|cT?Im_fdm;>2Bo(F<06)Gj?9xBX|p{ zdV9l<%;ccMkxi9sSv^?nd^cvAqg6z_A1~6oIqKLps3LD@4jqy(v2#|Aun$ID3tpgy zhOO@Yq`Ygmj!iZv+8BVpDF>cgkH~>}UK%abT_BX_@e!S?YC&RSJH$KM_?A`XNgk=#A=&o*xbs3KB61bPwU6lB|}PCPWZ0!CZ(-+wh(*{QNPJn zcxPxc35c>e1_fiS7IhH#)87C;98b)z1jLr)qzImMvVtT^b@Tu%@)6evn%C>-f&Kxx zGoWWW1GYC9^lvJY%3;8c&L6k1rI__xW|rM|{Nka)a`YbHniZlPRVL1s6ja>_>L6}m@Kg^ckl3Tpw!5}pJT>h@jv3lS``B0qL2p5!vWd!*~uVK)4|Z#lK5=( z-47D=B&LHtiF74s5>2!kO3PH>rL&v^olKPH{q0^2tcqjQ^(e4i>*$MXV=6K{9RSI2 z@l7N%ef(nvh{iqoWAb>ROQs{3cw&MK8yfuqSGwxQ-Yjr9djG~~hhcxWx=rKD3B;>$9y{F}D4qyM3KRS{EkkBVop>u`x)GD>v*b>#+91ly#6L@TKcN^2;$&1wL z^R~u*0bAK)GivAvSWSD0q`ZrB1&L@IIigm*o|BuaX;N~;f^;I>GVs1WRm({?LdnBa zsfZa;iRrLU35=uEFX@FJ6TF#;vz@?Vx+q2S@G9SO5PWI~LcBaq7XEqa-e}-+r7!*` zX~5K&I(yG9OWGyd!8O&ILuqQwG+t(_rNPe55*}hBb7_2aoeGi{C{k}yeCxDJV&Luw zg~30{v{f2qIy5agM%w^Y9@tcf!jM_az-Hm+B}i52n`sIs4(xuHCVI3ktNoj23-gfq zW;(9N626|*F0@rRzbnLdN@c5zo@oBY*zsL+GUFUv-|RPfznDE zSdVW1meeU$Yi@WR+ZT{e7amf8klSh-tK!Ad>?YR{_iDIRH(q*d2&iBs@gEib-9u!v zp&lXeA0kB6p>^BuwK7az=@k)9e|%;GGA;sE&>Zn7n$Ek5Tgz~a#(+*ozGcL9D{;-p zqF)ybDckNtcjn}Dl9*WY%wBZV4n)sidvCW8~J-iK5IA@MK` zMXdEB7rk%9N)%m{^`a!V7-R1G?k=oWjA&!}XevADgb6EvDKW=Zef@|MgV#-VN)rel ze_Mvp8n3kRk=Yy5Q?QQNJd(>NjqJiFrJh_(52=C|LZjXaMPh@fcXAA_`tz-9Y%NS1 zUfrL?xQ56%GsIE7JMd!R1n%a2<}CqOhuO&bos&tvJa#(1LFn^6mc)-Y!kr+RPzn8I zSK3dxGKGk+PNre;r2h_)6=Y1q)?R403@I*yHoVQRdxYYubqVHZZ+&0YsdFs#++Ynn zd$%|DAto{6G3ZjK&de1n!S=ez;7BMou$g)D6}V2pPfCZbY_qyg4d{>R4u>w5_b7_7 zDbrCL3iLnH8@WHtfm9vK)UZTS2jUx&5+c|~7mqRTK5wCktrOROIfjsY+Jh?aO-kfP{7`X~s3DYGCu}{E5h> z%}28X7kNe*qi>D`kCN`OPORYv{oW0-A|Oz%DKI6=(`Dd7Fb7Etm;nU%5|IKi2*We< z&_6go@3{m5k({~!>JKyt z7j`A3J#T+s-$t}Q2!t2drRYf73&HMNT93$X<$ul=q?(NDfDTiCXo}80JuFX~Gj`Kw zPW8Mz3RE@;ugUBv8U2LIWmCjmxck$+*1U|C;q~%JV{2MxX-h#1;hYUj{+}}Y6scVI zP8_>DHumL^Q0?3htm?88OZ9E43q+cacq8@ZbML?nN&Wrj+`HL^P6FJ;$uYdf|Nftz z4X{?Jt@W9lz*}FCUu5(2ZRjq$CCVNqtfqw=TPKo*7VF5B-;aoSy!X^vSY_A;yh%x4 zRmV>(0{_9&g{R1eTwoh|8d_a8R*@dsznhn*&5XPXp=84q6REJG6KjLQ)=Z) z@)$0Bj_`J05YS=BgVR|pqumXIe)(~JCMTDTmz=+C5D4Kdty{~wmk3|x`5#V@7mho* zpb%w{^JOVK&1uH%`I7t_fg2<&;6#C8j@En7Wko5>Y z^WT7%Mgu`~^mZv%Jf)$x498btfLvosVvtkl`|nR^)i(xJy}Ff#)`=P`nX7fV;`xbo zjpg$bi;rB?+?$+W!}MXJPloxDN5;zJ!AzFns{CnGEjG=6>b0zU_nWa4R*uQ|-r1tn zBWQ8vk#}ikq)k?YWs?sd5fUb;TPN8v|569&=4Z)Gt6yefYH|gL0-BYClux8d1!}@!r9j0D?%DAn$o>jI1gD*0TkkMVe zC`+xmX#8rPq1E6DHcySEfJ&@6#oGNjrUzBUGi*J2TKmz%zHS_-*}aY^-f>bsqRI^R z0l~!H$@*RJ`C1{09-G1|_lRV9{q04tQd@lLt8+>DCi7D~^EgFFAU4Bh;GBS(w)~Lb zy@gsXCfgrs;|lT?_0ox*%!9D?hwcOOl?6cK%98J#J$XUqS7RQtMAH89>=vOfF7K)0 z==yrjoAC_@IbcL5n$pG4w(FaRkz)R7pxll~dtP?M3buwsT=^#dMO zx1I&l7I1SSWorq`asDzYv4EH7h-{9do)}NTg6rD+GNW ze2%NZpcwAFaxh>;FFC4#E6ZR>+kd=E=gr3zhEq|hbMj!ck<^1JgL=}o&E&$d;fPMZ zJByPhC=NN|a?RT%t`^HH(s7)HNymEr*g$!09Q;El`JEqcR|~B!3tNu#b$}=tEJ8Ar zWM=bk3KFrf{Ep8Yox|`xGL4W+Im>kDh@wpt5UuBhiqi>z_*OFN()XN@F9Bpw7Kj7pDH~a6ET@z$)2F9<-1$sVY8$O83!vFxoeA_(_*R7BKW@DicFB*nOg-|Ef?bdy{sbczW36WwWzMuVZu$m~h<_Ip8= zwAGy^{mWsF3AYDHYGsomTXtr-KXwxB0v&v-7I<;_HFLM3u*Ge>&?_i)$ecKQWebqwN1cLbKe@bpgMsi$S?ZQ=XjWz2J*h{JwznNibC zl@xwvRg=kv_^Sa;sGtadbVOiio#_g}WD1;}| z99dc!)|P%&ifQu6<^-0(3>Q!{|J+&|B==KO`i%`QSFM`pE*lmR6vYZ#o{j*AC|x;i zT!q|w4{_%E=*oJc6_4u-o3*DaxvDkwm2$|eT60&NcyFtP!kHYK#OOqS?^rH`wcw(ed$RP-ZWPFt zJ7t2z!`=X^K2pAP+wzSc1A2f4irm-u-d|itUEViQJ*@69rp4=LHb=F}$Cer~d7^p~ z7x^?Ch=&nvj;aK>EfI%ESFMA%!Xv$bksFDjY#GZNrGiI9C0$4&_1qTVhKUmX%)PCa zKWNcxc)5ib;Nxo&t7<~Z9`=a0OK3kyBIr)n|5kdO{$64xrE<5}-2{;N`q>)1sc)(Ir={<9p`PX#zlRx<`O4JfRbF{O zS_Ut%rJ~dF3Mul%XoDywjPd=X3-7>VTGMwe&%EtYjR zK7%*+n(QM0Cc(v?S<|6+BOaLsdTM^s`ptu^BbJ4rbQ7kk6LU5yIjZQ^5|+3qacoRu zbPkRhhfc4m(&YW@#v7Zfyu6RrR5gm;X$cT2@F%tp)M-{c4T*`!?<98J{X(6vTP~vr znCm*tjRSM`$GKw?yJGXjzKYQDF^G>_f7l!yVlNtDbfXULXp~t8ZHYU*%+l(h3#k?a^a>Qwc^%>>O7?JYxtl= zEl5p?7Py&d&rwtjG2hDBx*+gK<%8;t?}UPR>I%NeDl7eq9rZJ@FIQ9heSCQW=;M*g zjFm*gR|A64#={qzxO6J$q@RP&{-D}U=maTr=~#VC?du*FoTJNeKk*vF{;Na4k`>a6 zAY3{<*H{INN2Yf1fg4n``a#rv8uS%MtOX_>XIom1!;_m9M`0Igcz>h$)5g7NdO!SL zIQd^gGSSA#`L+}zakYhhFGXn8_LVOCTsR9p;C@GM&wx->|G90nE=$Q-%fTedf5?dG zfBBBT(bfYRE1Z9^(eeer=iBIf@%zht^B1fDI7yAUmz~GD6&)f!Hh)>!fHGmdBg7?O zM~h25Ula~k|J5@}g}!Q89-PJc-Ka4a=x?I5)E4$wT5VGuAjI@Y zkG^$n<*#?7R#`#&`-sWUIB>RDN=$!gEyi&1>xXehej)k3?z1QTY9U3VGMid@0evi}HEF_);^h z9z7-w$A4hiPr}jXV9;fkoKaHr^4YrPd>X8iF4MC?I7Ms^&s`;;&ut@DSKa$WU)qaL zcY4dT#>dT6M;}0}PW+wTu)jE@Yf;wUj@~|>wmYKSF2o3_YFr;} zKNxcJWKT55gAy7P_|YMI$qg(m}}a_qD4V#$3ZgKWWXybgNXM2w~6L?^V0C% z=tfCWj*mlr+xgxTi3bl+-fb{Zl+e!GUcV3JPQUM`75Z6q3%C1L!@%ZO$L2y}N3Lio zqdz|-on~$-^8{^41c`ozb!KvTZkiqhzLhjkOd2}wA6J$Xp~cOtqMumup!QZ)&0!$3 z+1RZ#bR?mul^`gVK=4a?TfDK^ihWm2cUTv}fu4aZbwwB*HEQ;~5sdtu({s}pA~q&~ z6)VcF|I+D9MD^8U3YD92StR_1Wbj6E(&*IK?PjLtfvu-)lrl~2*oAnYt(MZiAU!%V zc8iFy`-5-?jEt>{1fjn5>4(c&>yd<%ng&d{T`T%WW#UK@(DZWbCkART+B9G0BLjYE z&1^P{ILO^7sALdli248@5pK*3^};^efFNa_J`D-uUw%Yx$R((8Vb5bi=Bw0vboPVYMQ;J_|DW zph@xV;#>l#YPIiP?1K2#Q=Q~xVK3+^YP7V%-fj~dQ)fhz_Wxj_3|%BWny7|}RZo`} z1J_4oDq}>Z3Mg>OOLwbT9!z6IcTpcA9xoC^n90ttw}3S+pzH8<*=Z^gJp#IFO4vZ# zDECkem@C0)9Q#Tyg3_BI-5d=}p`g@lRE9NCKYMOk{T6RCRgc7-RN28hi*&);-NT99 zu-xS92Uoq21H6OtZ8mh3bBO-1LhcVeZFc)%Kat44Kc#-W@R(UA{CA$@n*ZF9qbE;D z0nVEvshdMrCdQ@fVV#OnZ#olCHeu5WWUdo)2IA_$wOLL&*6V*8ws3WJ#CQ@;ky49B zBw~C297kAKng1cBbE>Ld_vQwk@@j;6qyqRJa`PE>dTzfuK)EkH7^mEZB^12;)XG_m zx4_1pu@7B-J1+jK9a!g~%R9`k%8cX{7-nlvi( zlMG`L#g>b=56-AHxw*d+o7wp&01%W3R3lp`^#4WCxyLj8_+PxcPbFl@UBzeaw{0zV zD#@p92)W-v+d>g@n`9sj)DVON2#jnGkEOj9k9I{r>gm9^UWw z>vdk|JWn^eH6u`e^Iq)Mn3ETlj`9~cUkpAu&yX-Kyc|n-Ay>cdqD3cq0P@v1~PdxQT%n|s=;4d!oymfY;B+Vg7*iZy%veIv*?H8$f5g2Gtf@M$GMVXd~_~uL>Z3jOyY(w&~~dUT{D5;wHQ{ zb7F1t{-h*1Sayoh7!;;kwdf4>-g%ZuTs^mVwd38%o{=W^Iks4;X&dCF9`jljZ`M(Kloje~ zdU@?+>WcF-AsNj@eJ_@zt-*(Xu4eFXL=D>D+V#)h@_Sp8#xqU1F{tP*I|FVAwpb}o z^_rPYwEZml5I13+_)#+`t0C%RkyPjH{CZ2Z7pXtjuyAj#gurP!OcakZUeyO9M-NgV zgR%%-#G23FXeIc2*7B(}g9O9G3?sdE)~tcayhogVI}w3sn+RS0veaD%4vd&-Kyt)e z;(rZ&2=(b;RE0#$xmtIt$0RYXy=zaPaJdL8^6g~s7T2;QV%((m$T z)uc~90hIfJ8ZD5=q9MTIbbv(CnP+wO$>7%TcaGgqZHji*d7jTatOw$=Mc1umv3lEd zs4;j}NG3&sHW3Cg^G_|EI?nu}N}0LNUt;Wof2P0+f^T*)hD9A>wtor9w0BgRoNE?C zjBkMN7KjOrKouh%W1Gc^(jC*>ZLNWVj&2T9oz64}u z!9DsdW%u$c#}}kg%&9Sq@viz;^O4ByvOOOcl8`P*OQ{X!4o?_ACi>g%1^*|e@+SM2MMv447nL51Y z8W)4G*%=O2S78KrJb5Jx0kQ)a%MICxS(@MKm~&C zw>KD&W0i}ZNaDdj%-M_Sxb&LhbZS7`77T)z+bK_=Xj8HUy1MSlQ49Dmrw(oeKJIK3 z56oq|9nLnsd%LYlp!;NNa2+qim;B0WnVmUwJz|n?bgH9pUJ2HZ#WywlDqv7B662)y z?3kMTsu1aoyU**|21pj~`y!41!N*!09JZcVp;2~hjMY}}_3iAK{D#d3UCMTw)P8Au zUuX71R$t=Q$UiAirDH=7nJNoZ9Bdh(MgpHq8dXxcY`d!9p_4nOACNV{N`1zROq|pn*mW zY13mmRSS36dtg@6tc?||!27t%Kv~k1IZWSQrv!+qg+4z{_15vW=DKR!Jmh@J&?N3T z2efK+D*?HP^C-o+C~`HfY+b2eDv=DjhVsyN6wu^ptFhaC<(S*c3pW2<{6L9V-PO40 zXGqyBuIN*ef+=}-O#)j=1cg4!o^AdE(%GT{pnNkhQ#*hJTyEOwT{>9GHZ|sfEmVd7 zfWu8x#%tPprS(`Ag2(WiTgQ`|&ENXv&G;^4*=s-K;bOwe_j1qB5=t^mQaF$oydzbJ z@0-P0*>)}75?Nreb2R+Gy!8CCPj}n}jxWlvoh?l& zEHuo}+WWBPgW88JTLrQ zJD279*Nv(hIW($B+zN2K?-St3O9UWFP+PsQf*#n>L8hR05S$iWP!{%Rd<#Z$OY z>AA7t>-V$b2SGJq5Hn6ok5t5*gW$vLUt8B9ea|&ji_@&utrP58)mJ4=LmsPoOS$pN z(_PzdfUeN7u>EFDh7=vS0y5V*1GBUWdckN|zd*yJZxJp6q%;ZMY_aW{;pAypdp*qF zop4DHKo>1> z6+);AW&5dW-&S&~dV|XTa78bSh*W^Sh60qYz2vmwV#PgHASB&tAm~**oR!T3sDW&trr`eHL)Pyn z>EJPs!ZrG;o-;-g+(9SA5B4RUo`lXRu@-O-c}Hs8Xtcg-AUq*?K+jE6wEB^@D_x>Q zu^rvPz?idJPY)}nfgpY(psKHy>f7WWZJ|=| zA>UMv74G3g#mvQOi$zonepzdE(tlTj?6D%1SXlrClae;2v7l664wJ{gOe>PprreU$ramvTi)MInkEoi_Ds|`Y(nq0KLM?pGfsP3 zy>-K`N8u513csTaTEXqLCPm{i+rP<6k}9c2qP&1ehdNvZZNJ&(>Okc{!UMO7AMrm2 zzhr}sOupwe2H9^EvD98Hi9e|msK^Z?xo9+_99uO0u=st@2t)f!m$e&T%RN1oc-()Z zm^=kQJpKUD{+UdHUj7RU*+^HMx)n|SfEnhR8%2G`RsX>1BPR z&I%Vh<9(>Z`o@TR)^yd`i9IfTf%+x6bE`a6vsRUHHJ35Ww&8kAAtCz-4;NcQ1i&%# zljk7-p*j?qW#Zhmmhc++HJ$=EY2B8`Hl)Nyr_xev~)6B{QyP?KI8qhmA-e+w$Im56ki~x(QMhC9&q3M(@TV+{!HeH zHApF_XHBFYn=Kg>t``JI>JGMT7O|0Y189R>ms2+;vH9Jmu#k$DA>8Oz9}{D#j^!eQ zqwkIjNz=4iZ-^CXNw1wY_eaUOO*@8qzCQ6QwxIn!QCC~1A=s#~v=IOJuj;Wme9545R1ANJaO)vm#G-Mq{{^vW`ppNz!5>SI1A7RM4ZxpH4lulLrpm|{!;>I1gNZ}+aA`lGi*4w=IonQ$)iO_q8Q z_4JgXvdVR-yJv%se%QQU4?P-wRBm&aWXjM%ux2DPN-Hxpe zSO9g3LX|lmVBedu>*VO?*?%w7 z!}~*PJq3_hzW7=GO04J>L75B{-F}la9EJyn8Hkt6Bw+5J^@kN0>dI&gS0gOJfEd-dxXWdWz5;9pIPYct@9eSl|J0OXzY=Mn`eBit!;=|X_PI{S zX4(G;+F|JpKx~&c7CI zeKdUkzZJ?z%MX-9py|p!zTxhIceye$66c9arHa3xru+n9< zSTd6S-@~-$_d-ugLdRURN1`p4wgR-w!=)BSyEP=V(GFV@mu{i1im^4LxrgE}byct8 z5)jq@oib;L38lXTx?Yk{yNJQyAv=TmI=4cdwcW0yDa8=bv2Wf1b}O?Nrv8|{ z>>@vcW6G`uVutu2*Wqs=6@9VzFmio>Y~;|07nysi8HLVRfk2^lgY+$cgca`bVA3>x z-#8l;TWDrvrGyBBtfSs8u5qI_iZ~g0a;1UfqArt1b)hTdF$tYpeX=FAMd{9Kphm14 z8M}?}?cxUiT+;v9>Kr=Wt7x2I8!MA#+iK2aRzdk^uMN$TvvMCXy8W?fvBF2a`%f`K zV{&BMUsstF9Ug(Fgr32>{C-H$f5hSIFDTq|$Jt9JBgm-hrdxD{jjYqb3QOKhL;~Gj z14h%W=Ia=l>>{JsW&*3IvxN7BVfuM=sk*#P+$eczRk3d{v6G=5K~bEtN9mXq!1UO+ zT5;aj_DxpDoQe%zuv-gB20tqBo|n%*IGMl*b6TnOK}FXj?R=qcq1Knr{mREK>ArHA z^AYmfmeYV!LALQ}(}_`9$A+f+Ld&Z%H+=&qMea)fbFzkI{Fy=%VNXB1&@E zd7lk6*iXvmEjd5=@r9e|XLLJQP`lg>$XwX1J2b`P+;|TIaOuAd4dyS#D;BBJl`Vnb zoS+Z#E;Bx{`7X<<4SrCk73yttv(Ceo7?(Lci%(8A`cylLnKa+&H(m3sYZFOW)Xwlm zi$c>21|_j67^~w?N5K6LC+H)fC0Se+JhZ479a020n+TL#^?mq9N+m&alGaiQCxGBpSu!`%KtEUf~*{ zW8%I~q3gD~Km%Mv0Wu2GIk61{_fIbwoGid6KR>#Q{%g&lB|NiMSTR)_46*vwX24sz z)2lUhG!vVClTX`ERhcf1IyZ5ug#y(x-@xaOz)pNy$cLPxaF14Wq64oaTV@tw3~IPujiiu_TU&6bqwn>f8f*W6@k_6j zv!O=<^_YJ|pc`~*_nCL(N!(b3^Lnu`;!mP~j_&(fRHjMEIYt}0!y?(}sOxaXu3FG1 z@5IGZDSxH8UBAZVzxr>GPx#3VnUyRGPEjL{B{vJsrc_uJp`1QCXs$K>m{7AFwj%q4 z4Cg20A$>DWQI8>u$!<~tChAl7ucA4#LA3Zy9}s>2B=UXnnnue0@>^FN-uh3~QU2O8 zEt78&s-`{b5v9^g*hdi40TefW-u*%jShIFXW!wVh>%)AoY6#=*VL)PY_Ki{P zKbG*1(G`iabCN&sVaedZG;q}}TE6>AE=j`*G`N9N_MX{468jxq;9@PLNi3TR*&b0Q z-&YKP7G)e?jc`LV(TRdeosmxluZ|1E49nYNuJga2J8wh}x_-y<7cSSwAt>>gAM0yN zM|X1(*QbG(;JsmwBDkL2P9xE&nUJ zV2Hd1dWlbq6$sr=zH^?!IAj|d$8FBrR~-Qm%y?q&DEraSxwta z1!2m06JtO&Zw@J3J3Qv)qXJ8;tm)Ak@jYtzDq>6Wrbk|xKw?lWWFZd>`C#}NxF3c+c9H(a7lJh^mZc7r@r`isaX znHWC77l)pD>t;UmJ=CuSLnb<_zpo+B^9}~)<03iWUrB219zh@(-821aO%~i@_^}C( z+l7y2dH_ihPpbbI>j$EZ^I0-h?TILFQ9$_}kW)uR6mtEkC8?pP28$`D=_%1$NAXe1 z(PYvGMh~DQFI@jI$%5mblW~~sv+M*99Y{mt-0p6_tDs6WH<4RJ(|VdfE2@7Xw%z(+ z1BK63J^+QAB-adkyRPsIz^rMR#RLTF2RmlVhWVmma#7j!oCr3~jCfovREV;=Rd*Hx z2rWU5O7PD-szEejv(}<4k-jMGR1no+S`+GwC_A<6He=BxI~kVoMFjE^ITVma5Vrq_ zaQ{w8V$}yhqm7v*-_Q?-#fKhlsZN=RLV1*zP}VOANzpz-k=y6Fj>8B>w zNx*Hs&RzT@WxW7$w#tka8Ux9^$P%T>y3c$RH;c*+7Z%c`PFn4ee#vXUD;FA=1l0=P zPdB_1{WpTu^}jt@w>e{2216&I^zDuNx?RwBY*)3p{Z@LgkuODIP887|nn5)3&TT*SYgw?J32-nWjD>5z_sR&1>$72PUyeR)!aZm->FkKV zYLB5I$h^d-K7UsC8V*^a)^@Q3amsGyOS9ahpd!jpQD$2*H9(7sf0%?IfoxyjOb zKt`}LsQx!$#x0?V1-s34j-u6x7OuGyCim=%kpSmgA~9RKc^fwT*6)vWD<90gs$Y$P z?3sK@|L>J}+t7ZC*kbX1?zyI}1YlHY2f1<0_4JSRqLBZW>*U!Hx)aqRcWZ+SIAlcircuvesM zLXIUNUFA5U?yz5cuy$2wRq*vpw$bio%54WF2NgkiRTnm4G;Hvnf z#h2IgdxeAnGZzQ0IbtPYkQ32FCiz)`l%y2#&uut9e+QEvG*q>p`Iv!cbfmd3>UcBG zG%42Ivm-RQ#LbfMw-0s%nt|g5O<{XxFGO*Z7Q;Or<~#d1N;jQhD^h^giE{ojrKD2KNH zq&Le9f#~g~k@&?~oQN+Zv6U^~I2dM@jhz^=I#k^v0AV+??D)RurpZNpwDRfoDz__F z|E&naJx;e?Rp4u)DigW-LGV>)FVf5wK-Z5JId?|1&@>38pDD$!$t5sP$N$EEj*ruW zD5A;q|A?H2r1-EVT(R8e{?AU+&OiL+E4B7 zKHJqa0Lu&FxGv92bWwI*7WW^k=qhEcy=sZ2+U#3TKm8o@X2~wE4 zK_=vIZ&J95D^Qy1=qFLXkC=mnKY-Ey-uO)7d`rYdv6xS|AX-D?oD)kF-Y%LiqWvlJ z_qFp`e{S74%@>BLS#~ zIXesTlY-+}i5B)KQi6>zKeJkxuQ z(gObe)O7F*XG^3xK-`>xIx8Es*W-6$RU+4x?b6PeSgtGtk@FNeAIP;r5{|g4#VQ}U z;_<4U(RO%*Z}d-=Yxz21zeVTYeQm{;Zd>{~-dE*UCv0g|n?#trnjGG6{bWvjYFnAI)v^<8|3+z$d*{7C^8n?S!Xgu% zN%2j>Wz3OAS{dx{*~dyJb5I&`FiV1(jcX%%hyc zJ)F|>HA0=4Yf@dFKfz=LQWT?B-;Mgo{}5z#e?)s%Xn^2*ZGxyvQ{$hLw$4d`)NMO? zM*nWy<;n88?Kk7P*@=r}(PcHpq+b-9$XB?oxT~L8*4_vMoXtBkoDkouXrXQ*B;?Rq z8>wQXm^6y{S{ON{=gM3t*W9|A5rlsYjpnLS4v`Wob;C%9(N@x#(@MaiK}0+y*E#FF zTw5Aq7zWstg4g)7J=)WulO8ouWaQdSxd)A8hXZZZ&NMi3kl&ubs;H>WC=QVP7ch7@ zzAZNP(W@Y<&l6v5WCQoaJBx8TY6=8#&Yr)*RG>-9n} zn#Ya73#xd}-7}yO1>)#t2`Xz7S@Pla!lFu=cHM=I?aQxWvjW{I-`sK0!s1>c-lfJj z5HVO1uR}_)5B-8rPk(`1Z_GmuZ>B1;qmfAIVTjfwW@;wTDb6^%z0PCzJLcWJm6Km^ zId9dypPt-=)wloQ9(c8p?H184en}$xrI}rHs`J=`1KzgjUk`qo$3}IXzY#TQGI6}P z@5>e}mRMx@6W+_yUC9uMGUj}7t@ZpcuE8OXepAs_^qd;!2RsmSc2^^rw#0aCAU%XS249gi zEAp{EBScm5GTMquNk6S0&R;1^r5&2idSOPqWy?Jj$JAk02F4iC8KcRdFr2qkCy7NJ z%^26Lx4$wH8<_;W_SEm+iU-WUq37f>co|U;b8CxAUrBPmrRux@WFsoBK>o~PD@PFW zd#3XP(A6F^t(r8C3DH-y5%;;q_p9aS%J4}yyqr=cW`6<0D9?g38&P*Y`AN6cK*-X* zS(LMlxBcT4(Jq8zv6x@823x6yQE`JJKoJutyKiqnA@^k2(f388N#CAgaQ#)m!;u7> z_ckJx@Q_3|bPHpgb;&$)PN}G_yz_$srJCl#0m!>F*T2={0gRlL z{K(dj){I|c0d*rm)e3l$_h3lljAO6Z2xI<+t#nfk&f8WWXijgPbJk)U6kVP>FO6{3 zq1himT<3;X%}@)dC&3}sO80ws0yzCaIrk1CynR#?BZc&Ord3Oy(^eU9^ue+Z z+W0;~nvr+Qg24r6HwiYqH;!BVfIwBY2kVDAo{u+@<3dFV(b-W~2PqNhE1tV2+!(jI zpX_b=8&l3;hxi?@r$iaI&7hsKerH!NDDTe!i@wT>Ircx5MXzxS^ojG-K|^(@X9kw+ zuzU);)o2ry^IvoTlJ(sZnOHfep^)C8`oBHl-bu*l-v%cUMez>ie@Oq^BVU4L?|~YL z#ysq$)78{EKlkB|_C#0_Qzs)TBkarlwVf^R;Su z{9x4!EfN zm^Y-kQfX)L!qInF!ZU5Lkh_cQvoD&hI`gOA=~SHx%3{Sd2DMfE6HUjx!Z+vg*m7)O zazrMF>nc9Hh>L?5(Z*-%hOHnovL3yV5(nJJ?~6_woVxxO|Gr`_I-3&@h54Lr6w5~j zQ$ZmK7pFZVO9C%*-2jQ5_J{i`=YqW&DCT@s?*}|elJ=EmTk-Bu5!bEfX5^B1t3u2P5rARF$MMdBp|C)o z59Wb*`&vQQ81w(7FuLRHStIf&bs$5KVXhlD)bAG*7UX^}J}WFkMWC$h9!Os0Dtnfg zlxRTQjUGOm}4Z zd7_fM?(GwCpE!`t;`1pDro)jL(%9e6Vpb)i-gvHO7#%*Mf%D)Dk8qvA9l=Rz6WqX7 zYD|fL+RIYPaqzPL?!~?(bNksP@iX!4tF``hlT6hA_B?nB(kW~Yo1RLnIwGmR{RDr@ zK{`9YyX~y1;@OF(e1#QAu0Q|q*2O?c^8LMn=O0}D?w|8Bza%e`R#oyScV0Xam8hR3 zc%&U0R0#DmB`twOyELa2;2X#-pQ+N3!~2RD?FZzk@8n}>bv80t7fF;EM^hu?5n9z+ z71j3En_#7Gft*~LQr^K`;O6oEXDQ^g_5j!Ej50TA-kmrYFR?0iT6=wni;4}7PC}y6 zx$efCjH{a;5sIjD{gpY}sc0UEPJ1__tiBvrq~6Jpiwls6!3Z5eXxh`=E=Y}g$2^E4 zBY&Xd4oq6{R+7Rx8wPO0ajGJA|tji@9Adk`h-j*C>qy?6n+?IkyELPmcHplo5cYAuyBrolzzB zqzv0&=1X3w5+0~d)?ppY|3p7W3HY+Hl8T=&{y&5~G-YZ|thx*&YF?X~LJrZmj3u7&#@~Ci*Y<-yRKH2A#BB#Qa&g&G<4HgIlvR z5`k8_7W6FCgM;cXZ@=s2zP@CV^_`$?9G!+MNPQ(Rgng%r0E;Ac6BEUdE0Abh)iobtZo>-cjK0Xj z>}t7*2BArpHKl!n^gi*cH=Kazj(yN=skWVm%`%z$`YpS2*vR-A?Um80nAo7Q0po3% z;|omIrFVDTXqD7n@13t+{w^^GR#y+4T~ofoJNPETrSq~!=}pzw!2j);cCY5%?4e?c zU7uO2Ah%eCl!%qkog)fap^Nf-?RLI%OZ_gAZwb*fDWqR1hzd8mCu)IkY6>j7%9~O4 zTDILgAdqmJwElIesw3V;^blr+BrX=45~vqnU=Yri_`E}Nx%{cji@$#3a8=c7Rh7-W z7VQ@m4d`48SRy&~Pj->kH12*0@O9{!cNRP6u7Ks2`n^M3GI$=n5F49i)ynGLLcO8vT@FAxJrfAf`x)`C;wS?00LZ zsx$;2B`o0j8~Jd2S#Ok9sOJ65I}BGfpXJCph7(0D&f%0)7Igjhh)3T%RpnP>L7I9M z`OyG37T>XLXK}qYsvz8my*EnPn+v?4-BuW5lqh05Frx9|!x-}R8nE0oaWPHu&{GY$ z^_(DAKZrwVOLY-5PL%v7-oERyQje~lE!tbMb6hvQkf&5B%q;EwtjGa=9>N7cAN@rrZhNn|A70C z6wx%CG8cW#^lPvYXmx)z zIcAl!`-;x1IOpuTa>n9utv#cM(jc4SPovJOsC$SrIe?Box+*cy7t0`=wFj&xrc}E#~jOQm&P77!=_MtqbaGZ-zl?h9t4Zr-= zbL(#;&{e2&X+-IFKKM&|a?sZETsqOsoL5n|P71z#VRj2Eh!$4^|;(Mf=PHR;Gm96x_3zdeqlhj=qZ3ZAm@)|2FMpN&)((dVo+c}Xv zn`V$Xc~d*B&HF!_)5&3eHYr7Kd>&V8_NHOGHs}ZUu!%{I??gHOTuC_g5d&>RxudALm`GoF2DJ)fbVX`XK zN&BkHH&$Xy#=~Mn7JkQ0oImPZBzQ{KdbXV*@13pt&$}J_qXTowB-%8;$?6m1gnQOvZ0t$;3_%blhva` zx}&?*A=hORDk%XN>Msf^)wT*qMBcG2I){BnZorw<^BjmH;9+>velwtNMh zidNWA{V;J8U*mq?Bq`9%K(6gf0}}nfdxIp`_PQs-0t4#uR!Sjf#0KcQ?*?9LOa?g@ zrg(9134bN(Ll3T8Qmr>TxsfI7Vy?`_HU~t8Z0r z9#Cj)X{PAp&8fTS7~j83@O$g4Q-U;47}20s2Al=P2o%8E=Y28S6pScC-H3H3;DRX- ziJd@)Wmg^kQ9k%A%!J?>yeKt1W#K=|4oe20Zyw=^d5{)c5W5zd>Im|RuEnQfU-gZw zw)Tp{-h*EOGl$AW8_l~@pWnH}rh3z*_M{v-`=z*W>s{vH?pV1*6@SdlUL!Wa6i~RK z+ALDC!9QblXwz9SsRHHF$cAHsLGvCLgRA2SqFe!NURH{R$&I=!-(&wc8n^TIu5XOw zCq!1T+==dwXM6dES80f6lU_Pt?t(CfT$p)50azcG-x+0K_IRFTsa{I!fxMwb(zd_Q zH*-`h_D$D5T6E&jL?uM;#oT2wer{RkSg5|@pDKzs@4L7fq@5G0dP}Tvl@zF87o%o3 zt!L0}tdTW!Qv`vp>R^74R2f(KbFxbvYNGFUz-T7uYH~$ifO7sH5rGinyytY@^=A=p z%9x3RUwubM8|LoPi!Zlv>gwA^jqfi-nI4Yw=C?Ucyfn@~I;uyz>gfU#3QQRN3);q6 zIYAE`a`z94DYpOgubju0(*&kQ;s_o_<&697ZN{JP_T9f80Sri6s%>m-`e_=<96>?z1d>cygg`fy02ZAdv)x@M^EcI~A<>Y>WWj#scq?%5!6^x0Jj8{50^ z?%fUItDOY_;DF!BshK$MF-WM(Qe}C(<^fcB%zA8Js-h`+M_udk@xgMaxSB8pGm8 zXf}g8xvv2fNr=6u0nfux37`W(3`+cJyq0G6`+n|hOS7L{WIi40wKYV}I|e_>7|@Ga z&zc0zwovBlmTS=R`8VLPDPCshH`u|WKF_8E=9uwC z9JHCw@2|RSWOnFF2bR26^kkS7;a1U#bG+|AV60kqpk=GCS?+6EN5WVU!1k)>Ez>en`85k$sqr|d(}0BN z{BKX9qDTyh_!#P_vL!glx3qtvU55Uf^%&Eb;Rg7+gLp7Ry;bw6%ws@*^w!O{101K> z=P$P5zynO-WMc7|o1?Wt5r?zM%@1M%S|&G2&$cHPU079!eRw!7+=?Bx!@cqO+Ki9D zS!>KIea=&U^O3JW*M2GV%tr&Oeh&J;0KH{!S2QoyOcw(H`v*Oj@=O0+Nq__Usw_Tr zgV)gVuN{51dPyp4C<0?R$v>eI?JH>Ox%p|%5(xFz{poha&4s`B*>Zx0j8>fkAVQz8 zt^R3a+M5 zec2=tF0br5tqyb6>5WB|ljoXiKvnDa94X%&c7ou|gXQby6pgt1g)b&7;%K3rbURK# z#@h}$xD=ZOsaV05SjxM*@huYu`vZ(};3tW*e3J%$g{Z*GDAxcf z{qQm}=F$rWcL9O_@BU)M3$Q3{s9tobx`N|ggm$7Otzn_Zb>vCmAbD29xQz)Bqm*}i zE%-2z?V1^_hE_qnd@=wgtN*gCkvy(UM_h2+u;bUbub7Q|wkuKJsO5%78Mg?IZpsmj z{&9ltB8&R8M{X{*{cO`_qGOtLs&+J~6XSLa!rTpVB2c7&>o?CK{|-eHx{EpV3ff$t z%e#zMT=D(|D%}60*(nHEhOT=PTiTTWr!Bat;Xfz1?i%X$T$DzC;3Hvv1K}*|m6vYN zdXe|VQ(8428QmhkUaU(pKw)c;ItevHSk?G}>+GeY?e>}{LTNQ#?QSY5?4ZUVA0xBs z81}X2J|tp7$e6)*!Y6ETE{j>*=Mq)02;SPrze1U+6RMite*9&7D0pg4_WU5=PKACe zgmlCr_HuJSf+0d@>`bT2OpSgNB4Qw~oEoLmGq(OYw(^RhZYvRRPvXi}bO}JyKxoKZ zXvq0z7g+srrY~X8B;k#p2mz#DHcrsFr1W;9V0uB!oaJ^S{_a-)xf}mtuX$U)xKZ~# zABj&soVFk?IV+B10`=qK^urC~D9v}ah8wxYNx-*R~8Y=jiO1*={!dk$1bjK;y)B9`~E zUQC;XCvkO=9GYLoO8inkv@9?CB1r9rkGUt!Gu|^c=^BHnJJ{Rkryz4;s zqinjJ?^-+mTXtaw=Q*(7#j0Ivtq@? zr^{_5Q$95IJm3XpGFSB83iH1rJNnia8Q)unhkW$CTeni-&y#~mo5EsD+dc1*S%Z^y zLc5i#m#;e3jIY5t=%9zY<@G$Sn`{nXZAsTSzeF^&V~0n)^{+Q23eNg9+x~ zk`(j1CR#-g$5kJ6AQ)W)rfq#0ljA{h7iKny-I=acGU0BMes$lu&{^DnYi?3jpI+tb zHeF?Q2d1W;cK%B=D7yCgCxc!cmm)IvoEXSgmH||r{CLpq(W)LcVW>@kl*<~5A?hVE zk?p8zWwnW|3uPoAufoSs%$U`L*%|5ZN$sA=uZ!L2Fa>Abb5nD|@xIQkO*q6? zwFggG9rI$U$0z>dFVjZcRJR9?hllUNW68pYInt~?&JCjN;&?)ytw2|Kp&DX$VdY?* z-SdF2Nk4-do&MVEJ!f@ae{~E)($hPArcp-fwL}Y`gJ?y2xlL=OuE@I7q8xtd{cq1e zR84VMjR9ETM~-EO(GWfWI-p;|leQLAPEOS5;@prUJ)B5Ija*RxSb&FhoR z7#Y9VTJ45?I=xA!Ef(BMPIC1RC!63W62kIM&KURHFPF|6BH#0gW6nNje!PnYKlmDc zxK^cTes4Gys2?5b*boZELiIRYOQ5S{j!jin0TDcWCJKd$8IRx<3-n`;_uJ(zC|8!V zy|Ef?_R8_}rd0q< zufDU`dHQTF%b8kxlp27kU3 zwuw^U-Z}+hR(jDUHAvGnAKT2!XDv8U=BU8A8??4%pD3-4e7nGkE+4qtmp&@&pYM&} z9Se5XnMss?M@?u6-*19q&}#-gG|v##v*l)&_F#ho={V~{%9SL%_nI@w&4KJc>FONO ztCYG8|3aRhLEji^!&v@UB42sFPIq;ixh6}lgM~BtGZDr@WTYq!45b!XmxVclpOw4? z8rp`@TF_f5UWReui3@@9gk;9o6>-CO#uD_AYFg%St8?+z;Izj?`K?fG3g{N^Cxge| zrxuGdxSJ|BT{t192`6PM(3JX=FcO4g=ow?Qa`_Ev-*@{ZW|WmyOolL3^iGN1wude~ z+&#U0Vf?<8^kCmxq1667iRfH04Y%(?2UG*+Pa5jB^$+svbUV}3gpI6bgvP*t^o zwmML2CeXRd3*XFgYE0?WwFBFc7$a9=sOIn!;NJyvtNgXM_cB+KZxT}y9ug>u;I6kf zp5@)(jb}K|j@4#%#bm{%HPOMem6=<+>h|NjQ!3~rGnjIK%kkRh9{A9RUwwnIFp&0r zbj6QrN!SLTdMJCP>1 zv4cIN>c9Es5g5~Ii>ko7aGg@>i!j>7de*Z}5)if6^gSq*VS43b+5b^=?(t0a{~zy8 zXDWw8a_Df+`Lr!LQz_L9IgXqU+d?^wm}A}LFhVzGn`6jX

8_hP%0tLxdrd389%| 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 JtNwfO{{dCY@2da+ literal 380672 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

Xr+7DbS_gf~=q7nqv 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%0ARJ2l7^Gj`n+lo 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�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