summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/python/XBPython.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/python/XBPython.cpp')
-rw-r--r--xbmc/interfaces/python/XBPython.cpp627
1 files changed, 627 insertions, 0 deletions
diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp
new file mode 100644
index 0000000..ee8ed93
--- /dev/null
+++ b/xbmc/interfaces/python/XBPython.cpp
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// clang-format off
+// python.h should always be included first before any other includes
+#include <mutex>
+#include <Python.h>
+// clang-format on
+
+#include "XBPython.h"
+
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "cores/DllLoader/DllLoaderContainer.h"
+#include "filesystem/SpecialProtocol.h"
+#include "interfaces/AnnouncementManager.h"
+#include "interfaces/legacy/AddonUtils.h"
+#include "interfaces/legacy/Monitor.h"
+#include "interfaces/python/AddonPythonInvoker.h"
+#include "interfaces/python/PythonInvoker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/JSONVariantWriter.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "utils/CharsetConverter.h"
+
+#ifdef TARGET_WINDOWS
+#include "platform/Environment.h"
+#endif
+
+#include <algorithm>
+
+// Only required for Py3 < 3.7
+PyThreadState* savestate;
+
+bool XBPython::m_bInitialized = false;
+
+XBPython::XBPython()
+{
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+XBPython::~XBPython()
+{
+ XBMC_TRACE;
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+}
+
+#define LOCK_AND_COPY(type, dest, src) \
+ if (!m_bInitialized) \
+ return; \
+ std::unique_lock<CCriticalSection> lock(src); \
+ src.hadSomethingRemoved = false; \
+ type dest; \
+ dest = src
+
+#define CHECK_FOR_ENTRY(l, v) \
+ (l.hadSomethingRemoved ? (std::find(l.begin(), l.end(), v) != l.end()) : true)
+
+void XBPython::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag & ANNOUNCEMENT::VideoLibrary)
+ {
+ if (message == "OnScanFinished")
+ OnScanFinished("video");
+ else if (message == "OnScanStarted")
+ OnScanStarted("video");
+ else if (message == "OnCleanStarted")
+ OnCleanStarted("video");
+ else if (message == "OnCleanFinished")
+ OnCleanFinished("video");
+ }
+ else if (flag & ANNOUNCEMENT::AudioLibrary)
+ {
+ if (message == "OnScanFinished")
+ OnScanFinished("music");
+ else if (message == "OnScanStarted")
+ OnScanStarted("music");
+ else if (message == "OnCleanStarted")
+ OnCleanStarted("music");
+ else if (message == "OnCleanFinished")
+ OnCleanFinished("music");
+ }
+ else if (flag & ANNOUNCEMENT::GUI)
+ {
+ if (message == "OnScreensaverDeactivated")
+ OnScreensaverDeactivated();
+ else if (message == "OnScreensaverActivated")
+ OnScreensaverActivated();
+ else if (message == "OnDPMSDeactivated")
+ OnDPMSDeactivated();
+ else if (message == "OnDPMSActivated")
+ OnDPMSActivated();
+ }
+
+ std::string jsonData;
+ if (CJSONVariantWriter::Write(
+ data, jsonData,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact))
+ OnNotification(sender,
+ std::string(ANNOUNCEMENT::AnnouncementFlagToString(flag)) + "." +
+ std::string(message),
+ jsonData);
+}
+
+// message all registered callbacks that we started playing
+void XBPython::OnPlayBackStarted(const CFileItem& file)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackStarted(file);
+ }
+}
+
+// message all registered callbacks that we changed stream
+void XBPython::OnAVStarted(const CFileItem& file)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnAVStarted(file);
+ }
+}
+
+// message all registered callbacks that we changed stream
+void XBPython::OnAVChange()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnAVChange();
+ }
+}
+
+// message all registered callbacks that we paused playing
+void XBPython::OnPlayBackPaused()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackPaused();
+ }
+}
+
+// message all registered callbacks that we resumed playing
+void XBPython::OnPlayBackResumed()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackResumed();
+ }
+}
+
+// message all registered callbacks that xbmc stopped playing
+void XBPython::OnPlayBackEnded()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackEnded();
+ }
+}
+
+// message all registered callbacks that user stopped playing
+void XBPython::OnPlayBackStopped()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackStopped();
+ }
+}
+
+// message all registered callbacks that playback stopped due to error
+void XBPython::OnPlayBackError()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackError();
+ }
+}
+
+// message all registered callbacks that playback speed changed (FF/RW)
+void XBPython::OnPlayBackSpeedChanged(int iSpeed)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSpeedChanged(iSpeed);
+ }
+}
+
+// message all registered callbacks that player is seeking
+void XBPython::OnPlayBackSeek(int64_t iTime, int64_t seekOffset)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSeek(iTime, seekOffset);
+ }
+}
+
+// message all registered callbacks that player chapter seeked
+void XBPython::OnPlayBackSeekChapter(int iChapter)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSeekChapter(iChapter);
+ }
+}
+
+// message all registered callbacks that next item has been queued
+void XBPython::OnQueueNextItem()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnQueueNextItem();
+ }
+}
+
+void XBPython::RegisterPythonPlayerCallBack(IPlayerCallback* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList);
+ m_vecPlayerCallbackList.push_back(pCallback);
+}
+
+void XBPython::UnregisterPythonPlayerCallBack(IPlayerCallback* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList);
+ PlayerCallbackList::iterator it = m_vecPlayerCallbackList.begin();
+ while (it != m_vecPlayerCallbackList.end())
+ {
+ if (*it == pCallback)
+ {
+ it = m_vecPlayerCallbackList.erase(it);
+ m_vecPlayerCallbackList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+}
+
+void XBPython::RegisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList);
+ m_vecMonitorCallbackList.push_back(pCallback);
+}
+
+void XBPython::UnregisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList);
+ MonitorCallbackList::iterator it = m_vecMonitorCallbackList.begin();
+ while (it != m_vecMonitorCallbackList.end())
+ {
+ if (*it == pCallback)
+ {
+ it = m_vecMonitorCallbackList.erase(it);
+ m_vecMonitorCallbackList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+}
+
+void XBPython::OnSettingsChanged(const std::string& ID)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it) && (it->GetId() == ID))
+ it->OnSettingsChanged();
+ }
+}
+
+void XBPython::OnScreensaverActivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScreensaverActivated();
+ }
+}
+
+void XBPython::OnScreensaverDeactivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScreensaverDeactivated();
+ }
+}
+
+void XBPython::OnDPMSActivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnDPMSActivated();
+ }
+}
+
+void XBPython::OnDPMSDeactivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnDPMSDeactivated();
+ }
+}
+
+void XBPython::OnScanStarted(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScanStarted(library);
+ }
+}
+
+void XBPython::OnScanFinished(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScanFinished(library);
+ }
+}
+
+void XBPython::OnCleanStarted(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnCleanStarted(library);
+ }
+}
+
+void XBPython::OnCleanFinished(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnCleanFinished(library);
+ }
+}
+
+void XBPython::OnNotification(const std::string& sender,
+ const std::string& method,
+ const std::string& data)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnNotification(sender, method, data);
+ }
+}
+
+void XBPython::Uninitialize()
+{
+ // don't handle any more announcements as most scripts are probably already
+ // stopped and executing a callback on one of their already destroyed classes
+ // would lead to a crash
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+
+ LOCK_AND_COPY(std::vector<PyElem>, tmpvec, m_vecPyList);
+ m_vecPyList.clear();
+ m_vecPyList.hadSomethingRemoved = true;
+
+ lock.unlock(); //unlock here because the python thread might lock when it exits
+
+ // cleanup threads that are still running
+ tmpvec.clear();
+}
+
+void XBPython::Process()
+{
+ if (m_bInitialized)
+ {
+ PyList tmpvec;
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ for (PyList::iterator it = m_vecPyList.begin(); it != m_vecPyList.end();)
+ {
+ if (it->bDone)
+ {
+ tmpvec.push_back(*it);
+ it = m_vecPyList.erase(it);
+ m_vecPyList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+ lock.unlock();
+
+ //delete scripts which are done
+ tmpvec.clear();
+ }
+}
+
+bool XBPython::OnScriptInitialized(ILanguageInvoker* invoker)
+{
+ if (invoker == NULL)
+ return false;
+
+ XBMC_TRACE;
+ CLog::Log(LOGDEBUG, "initializing python engine.");
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iDllScriptCounter++;
+ if (!m_bInitialized)
+ {
+ // Darwin packs .pyo files, we need PYTHONOPTIMIZE on in order to load them.
+ // linux built with unified builds only packages the pyo files so need it
+#if defined(TARGET_DARWIN) || defined(TARGET_LINUX)
+ setenv("PYTHONOPTIMIZE", "1", 1);
+#endif
+ // Info about interesting python envvars available
+ // at http://docs.python.org/using/cmdline.html#environment-variables
+
+#if !defined(TARGET_WINDOWS) && !defined(TARGET_ANDROID)
+ // check if we are running as real xbmc.app or just binary
+ if (!CUtil::GetFrameworksPath(true).empty())
+ {
+ // using external python, it's build looking for xxx/lib/python3.8
+ // so point it to frameworks which is where python3.8 is located
+ setenv("PYTHONHOME", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1);
+ setenv("PYTHONPATH", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1);
+ CLog::Log(LOGDEBUG, "PYTHONHOME -> {}",
+ CSpecialProtocol::TranslatePath("special://frameworks"));
+ CLog::Log(LOGDEBUG, "PYTHONPATH -> {}",
+ CSpecialProtocol::TranslatePath("special://frameworks"));
+ }
+#elif defined(TARGET_WINDOWS)
+
+#ifdef TARGET_WINDOWS_STORE
+#ifdef _DEBUG
+ CEnvironment::putenv("PYTHONCASEOK=1");
+#endif
+ CEnvironment::putenv("OS=win10");
+#else // TARGET_WINDOWS_DESKTOP
+ CEnvironment::putenv("OS=win32");
+#endif
+
+ std::wstring pythonHomeW;
+ CCharsetConverter::utf8ToW(CSpecialProtocol::TranslatePath("special://xbmc/system/python"),
+ pythonHomeW);
+ Py_SetPythonHome(pythonHomeW.c_str());
+
+ std::string pythonPath = CSpecialProtocol::TranslatePath("special://xbmc/system/python/DLLs");
+ pythonPath += ";";
+ pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib");
+ pythonPath += ";";
+ pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib/site-packages");
+ std::wstring pythonPathW;
+ CCharsetConverter::utf8ToW(pythonPath, pythonPathW);
+
+ Py_SetPath(pythonPathW.c_str());
+
+ Py_OptimizeFlag = 1;
+#endif
+
+ Py_Initialize();
+
+#if PY_VERSION_HEX < 0x03070000
+ // Python >= 3.7 Py_Initialize implicitly calls PyEval_InitThreads
+ // Python < 3.7 we have to manually call initthreads.
+ // PyEval_InitThreads is a no-op on subsequent calls, No need to wrap in
+ // PyEval_ThreadsInitialized() check
+ PyEval_InitThreads();
+#endif
+
+ // Acquire GIL if thread doesn't currently hold.
+ if (!PyGILState_Check())
+ PyEval_RestoreThread((PyThreadState*)m_mainThreadState);
+
+ if (!(m_mainThreadState = PyThreadState_Get()))
+ CLog::Log(LOGERROR, "Python threadstate is NULL.");
+ savestate = PyEval_SaveThread();
+
+ m_bInitialized = true;
+ }
+
+ return m_bInitialized;
+}
+
+void XBPython::OnScriptStarted(ILanguageInvoker* invoker)
+{
+ if (invoker == NULL)
+ return;
+
+ if (!m_bInitialized)
+ return;
+
+ PyElem inf;
+ inf.id = invoker->GetId();
+ inf.bDone = false;
+ inf.pyThread = static_cast<CPythonInvoker*>(invoker);
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ m_vecPyList.push_back(inf);
+}
+
+void XBPython::NotifyScriptAborting(ILanguageInvoker* invoker)
+{
+ XBMC_TRACE;
+
+ long invokerId(-1);
+ if (invoker != NULL)
+ invokerId = invoker->GetId();
+
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ {
+ if (invokerId < 0 || it->GetInvokerId() == invokerId)
+ it->AbortNotify();
+ }
+ }
+}
+
+void XBPython::OnExecutionEnded(ILanguageInvoker* invoker)
+{
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ PyList::iterator it = m_vecPyList.begin();
+ while (it != m_vecPyList.end())
+ {
+ if (it->id == invoker->GetId())
+ {
+ if (it->pyThread->IsStopping())
+ CLog::Log(LOGDEBUG, "Python interpreter interrupted by user");
+ else
+ CLog::Log(LOGDEBUG, "Python interpreter stopped");
+ it->bDone = true;
+ }
+ ++it;
+ }
+}
+
+void XBPython::OnScriptFinalized(ILanguageInvoker* invoker)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // for linux - we never release the library. its loaded and stays in memory.
+ if (m_iDllScriptCounter)
+ m_iDllScriptCounter--;
+ else
+ CLog::Log(LOGERROR, "Python script counter attempted to become negative");
+}
+
+ILanguageInvoker* XBPython::CreateInvoker()
+{
+ return new CAddonPythonInvoker(this);
+}
+
+void XBPython::PulseGlobalEvent()
+{
+ m_globalEvent.Set();
+}
+
+bool XBPython::WaitForEvent(CEvent& hEvent, unsigned int milliseconds)
+{
+ // wait for either this event our our global event
+ XbmcThreads::CEventGroup eventGroup{&hEvent, &m_globalEvent};
+ CEvent* ret = eventGroup.wait(std::chrono::milliseconds(milliseconds));
+ if (ret)
+ m_globalEvent.Reset();
+ return ret != NULL;
+}