summaryrefslogtreecommitdiffstats
path: root/xbmc/interfaces/python
diff options
context:
space:
mode:
Diffstat (limited to 'xbmc/interfaces/python')
-rw-r--r--xbmc/interfaces/python/AddonPythonInvoker.cpp131
-rw-r--r--xbmc/interfaces/python/AddonPythonInvoker.h23
-rw-r--r--xbmc/interfaces/python/CMakeLists.txt21
-rw-r--r--xbmc/interfaces/python/CallbackHandler.cpp65
-rw-r--r--xbmc/interfaces/python/CallbackHandler.h40
-rw-r--r--xbmc/interfaces/python/ContextItemAddonInvoker.cpp42
-rw-r--r--xbmc/interfaces/python/ContextItemAddonInvoker.h30
-rw-r--r--xbmc/interfaces/python/LanguageHook.cpp231
-rw-r--r--xbmc/interfaces/python/LanguageHook.h94
-rw-r--r--xbmc/interfaces/python/MethodType.groovy14
-rw-r--r--xbmc/interfaces/python/PyContext.cpp117
-rw-r--r--xbmc/interfaces/python/PyContext.h49
-rw-r--r--xbmc/interfaces/python/PythonInvoker.cpp724
-rw-r--r--xbmc/interfaces/python/PythonInvoker.h76
-rw-r--r--xbmc/interfaces/python/PythonSwig.cpp.template942
-rw-r--r--xbmc/interfaces/python/PythonTools.groovy140
-rw-r--r--xbmc/interfaces/python/XBPython.cpp627
-rw-r--r--xbmc/interfaces/python/XBPython.h123
-rw-r--r--xbmc/interfaces/python/preamble.h15
-rw-r--r--xbmc/interfaces/python/pythreadstate.h64
-rw-r--r--xbmc/interfaces/python/swig.cpp443
-rw-r--r--xbmc/interfaces/python/swig.h202
-rw-r--r--xbmc/interfaces/python/test/CMakeLists.txt5
-rw-r--r--xbmc/interfaces/python/test/TestSwig.cpp21
-rw-r--r--xbmc/interfaces/python/typemaps/python.Alternative.intm43
-rw-r--r--xbmc/interfaces/python/typemaps/python.Alternative.outtm34
-rw-r--r--xbmc/interfaces/python/typemaps/python.Tuple.intm35
-rw-r--r--xbmc/interfaces/python/typemaps/python.Tuple.outtm39
-rw-r--r--xbmc/interfaces/python/typemaps/python.buffer.intm36
-rw-r--r--xbmc/interfaces/python/typemaps/python.buffer.outtm11
-rw-r--r--xbmc/interfaces/python/typemaps/python.dict.intm23
-rw-r--r--xbmc/interfaces/python/typemaps/python.map.intm24
-rw-r--r--xbmc/interfaces/python/typemaps/python.smart_ptr.outtm14
-rw-r--r--xbmc/interfaces/python/typemaps/python.string.outtm11
-rw-r--r--xbmc/interfaces/python/typemaps/python.vector.intm36
-rw-r--r--xbmc/interfaces/python/typemaps/python.vector.outtm35
36 files changed, 4580 insertions, 0 deletions
diff --git a/xbmc/interfaces/python/AddonPythonInvoker.cpp b/xbmc/interfaces/python/AddonPythonInvoker.cpp
new file mode 100644
index 0000000..b6158a8
--- /dev/null
+++ b/xbmc/interfaces/python/AddonPythonInvoker.cpp
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// python.h should always be included first before any other includes
+#include "AddonPythonInvoker.h"
+
+#include <utility>
+
+#include <Python.h>
+#include <osdefs.h>
+
+#define MODULE "xbmc"
+
+#define RUNSCRIPT_PREAMBLE \
+ "" \
+ "import " MODULE "\n" \
+ "class xbmcout:\n" \
+ " def __init__(self, loglevel=" MODULE ".LOGDEBUG):\n" \
+ " self.ll=loglevel\n" \
+ " def write(self, data):\n" \
+ " " MODULE ".log(data,self.ll)\n" \
+ " def close(self):\n" \
+ " " MODULE ".log('.')\n" \
+ " def flush(self):\n" \
+ " " MODULE ".log('.')\n" \
+ "import sys\n" \
+ "sys.stdout = xbmcout()\n" \
+ "sys.stderr = xbmcout(" MODULE ".LOGERROR)\n" \
+ ""
+
+#define RUNSCRIPT_SETUPTOOLS_HACK \
+ "" \
+ "import types,sys\n" \
+ "pkg_resources_code = \\\n" \
+ "\"\"\"\n" \
+ "def resource_filename(__name__,__path__):\n" \
+ " return __path__\n" \
+ "\"\"\"\n" \
+ "pkg_resources = types.ModuleType('pkg_resources')\n" \
+ "exec(pkg_resources_code, pkg_resources.__dict__)\n" \
+ "sys.modules['pkg_resources'] = pkg_resources\n" \
+ ""
+
+#define RUNSCRIPT_SETUP_ENVIROMENT_VARIABLES \
+ "" \
+ "from os import environ\n" \
+ "environ['SSL_CERT_FILE'] = 'system/certs/cacert.pem'\n" \
+ ""
+
+#define RUNSCRIPT_POSTSCRIPT \
+ "print('-->Python Interpreter Initialized<--')\n" \
+ ""
+
+#if defined(TARGET_ANDROID)
+
+#define RUNSCRIPT_COMPLIANT \
+ RUNSCRIPT_PREAMBLE RUNSCRIPT_SETUPTOOLS_HACK RUNSCRIPT_POSTSCRIPT
+
+#elif defined(TARGET_WINDOWS_STORE)
+
+#define RUNSCRIPT_COMPLIANT \
+ RUNSCRIPT_PREAMBLE RUNSCRIPT_SETUP_ENVIROMENT_VARIABLES RUNSCRIPT_POSTSCRIPT
+
+#else
+
+#define RUNSCRIPT_COMPLIANT \
+ RUNSCRIPT_PREAMBLE RUNSCRIPT_POSTSCRIPT
+
+#endif
+
+namespace PythonBindings {
+PyObject* PyInit_Module_xbmcdrm(void);
+PyObject* PyInit_Module_xbmcgui(void);
+PyObject* PyInit_Module_xbmc(void);
+PyObject* PyInit_Module_xbmcplugin(void);
+PyObject* PyInit_Module_xbmcaddon(void);
+PyObject* PyInit_Module_xbmcvfs(void);
+}
+
+using namespace PythonBindings;
+
+typedef struct
+{
+ const char *name;
+ CPythonInvoker::PythonModuleInitialization initialization;
+} PythonModule;
+
+static PythonModule PythonModules[] =
+ {
+ { "xbmcdrm", PyInit_Module_xbmcdrm },
+ { "xbmcgui", PyInit_Module_xbmcgui },
+ { "xbmc", PyInit_Module_xbmc },
+ { "xbmcplugin", PyInit_Module_xbmcplugin },
+ { "xbmcaddon", PyInit_Module_xbmcaddon },
+ { "xbmcvfs", PyInit_Module_xbmcvfs }
+ };
+
+CAddonPythonInvoker::CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler)
+ : CPythonInvoker(invocationHandler)
+{
+ PyImport_AppendInittab("xbmcdrm", PyInit_Module_xbmcdrm);
+ PyImport_AppendInittab("xbmcgui", PyInit_Module_xbmcgui);
+ PyImport_AppendInittab("xbmc", PyInit_Module_xbmc);
+ PyImport_AppendInittab("xbmcplugin", PyInit_Module_xbmcplugin);
+ PyImport_AppendInittab("xbmcaddon", PyInit_Module_xbmcaddon);
+ PyImport_AppendInittab("xbmcvfs", PyInit_Module_xbmcvfs);
+}
+
+CAddonPythonInvoker::~CAddonPythonInvoker() = default;
+
+std::map<std::string, CPythonInvoker::PythonModuleInitialization> CAddonPythonInvoker::getModules() const
+{
+ static std::map<std::string, PythonModuleInitialization> modules;
+ if (modules.empty())
+ {
+ for (const PythonModule& pythonModule : PythonModules)
+ modules.insert(std::make_pair(pythonModule.name, pythonModule.initialization));
+ }
+
+ return modules;
+}
+
+const char* CAddonPythonInvoker::getInitializationScript() const
+{
+ return RUNSCRIPT_COMPLIANT;
+}
diff --git a/xbmc/interfaces/python/AddonPythonInvoker.h b/xbmc/interfaces/python/AddonPythonInvoker.h
new file mode 100644
index 0000000..a846071
--- /dev/null
+++ b/xbmc/interfaces/python/AddonPythonInvoker.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/python/PythonInvoker.h"
+
+class CAddonPythonInvoker : public CPythonInvoker
+{
+public:
+ explicit CAddonPythonInvoker(ILanguageInvocationHandler *invocationHandler);
+ ~CAddonPythonInvoker() override;
+
+protected:
+ // overrides of CPythonInvoker
+ std::map<std::string, PythonModuleInitialization> getModules() const override;
+ const char* getInitializationScript() const override;
+};
diff --git a/xbmc/interfaces/python/CMakeLists.txt b/xbmc/interfaces/python/CMakeLists.txt
new file mode 100644
index 0000000..061cc2b
--- /dev/null
+++ b/xbmc/interfaces/python/CMakeLists.txt
@@ -0,0 +1,21 @@
+set(SOURCES AddonPythonInvoker.cpp
+ CallbackHandler.cpp
+ ContextItemAddonInvoker.cpp
+ LanguageHook.cpp
+ PythonInvoker.cpp
+ XBPython.cpp
+ swig.cpp
+ PyContext.cpp)
+
+set(HEADERS AddonPythonInvoker.h
+ CallbackHandler.h
+ ContextItemAddonInvoker.h
+ LanguageHook.h
+ preamble.h
+ PyContext.h
+ PythonInvoker.h
+ pythreadstate.h
+ swig.h
+ XBPython.h)
+
+core_add_library(python_interface)
diff --git a/xbmc/interfaces/python/CallbackHandler.cpp b/xbmc/interfaces/python/CallbackHandler.cpp
new file mode 100644
index 0000000..8fd31a7
--- /dev/null
+++ b/xbmc/interfaces/python/CallbackHandler.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "CallbackHandler.h"
+
+#include "LanguageHook.h"
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ /**
+ * We are ASS-U-MEing that this construction is happening
+ * within the context of a Python call. This way we can
+ * store off the PyThreadState to later verify that we're
+ * handling callbacks in the appropriate thread.
+ */
+ PythonCallbackHandler::PythonCallbackHandler()
+ {
+ XBMC_TRACE;
+ objectThreadState = PyThreadState_Get();
+ }
+
+ /**
+ * Now we are answering the question as to whether or not we are in the
+ * PyThreadState that we were in when we started.
+ */
+ bool PythonCallbackHandler::isStateOk(AddonClass* obj)
+ {
+ XBMC_TRACE;
+ PyThreadState* state = PyThreadState_Get();
+ if (objectThreadState == state)
+ {
+ // make sure the interpreter is still active.
+ AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> lh(XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp));
+ if (lh.isNotNull() && lh->HasRegisteredAddonClassInstance(obj) && lh.get() == obj->GetLanguageHook())
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * For this method we expect the PyThreadState to be passed as the user
+ * data for the check.
+ *
+ * @todo This is a stupid way to get this information back to the handler.
+ * there should be a more language neutral means.
+ */
+ bool PythonCallbackHandler::shouldRemoveCallback(AddonClass* obj, void* threadState)
+ {
+ XBMC_TRACE;
+ if (threadState == objectThreadState)
+ return true;
+
+ // we also want to remove the callback if the language hook no longer exists.
+ // this is a belt-and-suspenders cleanup mechanism
+ return ! XBMCAddon::Python::PythonLanguageHook::IsAddonClassInstanceRegistered(obj);
+ }
+ }
+}
diff --git a/xbmc/interfaces/python/CallbackHandler.h b/xbmc/interfaces/python/CallbackHandler.h
new file mode 100644
index 0000000..b128b27
--- /dev/null
+++ b/xbmc/interfaces/python/CallbackHandler.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/legacy/CallbackHandler.h"
+
+#include <Python.h>
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ /**
+ * This class represents a specialization of the callback handler
+ * that specifically checks to see if we're in an OK thread state
+ * based on Python specifics.
+ */
+ class PythonCallbackHandler : public RetardedAsyncCallbackHandler
+ {
+ PyThreadState* objectThreadState;
+ public:
+
+ /**
+ * We are ASS-U-MEing that this construction is happening
+ * within the context of a Python call. This way we can
+ * store off the PyThreadState to later verify that we're
+ * handling callbacks in the appropriate thread.
+ */
+ PythonCallbackHandler();
+ bool isStateOk(AddonClass* obj) override;
+ bool shouldRemoveCallback(AddonClass* obj, void* threadState) override;
+ };
+ }
+}
diff --git a/xbmc/interfaces/python/ContextItemAddonInvoker.cpp b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp
new file mode 100644
index 0000000..734193b
--- /dev/null
+++ b/xbmc/interfaces/python/ContextItemAddonInvoker.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// python.h should always be included first before any other includes
+#include "ContextItemAddonInvoker.h"
+
+#include "interfaces/python/swig.h"
+#include "utils/log.h"
+
+#include <Python.h>
+#include <osdefs.h>
+
+
+CContextItemAddonInvoker::CContextItemAddonInvoker(
+ ILanguageInvocationHandler *invocationHandler,
+ const CFileItemPtr& item)
+ : CAddonPythonInvoker(invocationHandler), m_item(CFileItemPtr(new CFileItem(*item.get())))
+{
+}
+
+CContextItemAddonInvoker::~CContextItemAddonInvoker() = default;
+
+void CContextItemAddonInvoker::onPythonModuleInitialization(void* moduleDict)
+{
+ CAddonPythonInvoker::onPythonModuleInitialization(moduleDict);
+ if (m_item)
+ {
+ XBMCAddon::xbmcgui::ListItem* arg = new XBMCAddon::xbmcgui::ListItem(m_item);
+ PyObject* pyItem = PythonBindings::makePythonInstance(arg, true);
+ if (pyItem == Py_None || PySys_SetObject("listitem", pyItem) == -1)
+ {
+ CLog::Log(LOGERROR, "CPythonInvoker({}, {}): Failed to set sys parameter", GetId(),
+ m_sourceFile);
+ //FIXME: we should really abort execution
+ }
+ }
+}
diff --git a/xbmc/interfaces/python/ContextItemAddonInvoker.h b/xbmc/interfaces/python/ContextItemAddonInvoker.h
new file mode 100644
index 0000000..b2f4d03
--- /dev/null
+++ b/xbmc/interfaces/python/ContextItemAddonInvoker.h
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/python/AddonPythonInvoker.h"
+
+#include <memory>
+
+class CFileItem;
+typedef std::shared_ptr<CFileItem> CFileItemPtr;
+
+class CContextItemAddonInvoker : public CAddonPythonInvoker
+{
+public:
+ explicit CContextItemAddonInvoker(ILanguageInvocationHandler *invocationHandler,
+ const CFileItemPtr& item);
+ ~CContextItemAddonInvoker() override;
+
+protected:
+ void onPythonModuleInitialization(void* moduleDict) override;
+
+private:
+ const CFileItemPtr m_item;
+};
diff --git a/xbmc/interfaces/python/LanguageHook.cpp b/xbmc/interfaces/python/LanguageHook.cpp
new file mode 100644
index 0000000..0d4747f
--- /dev/null
+++ b/xbmc/interfaces/python/LanguageHook.cpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+
+#include "LanguageHook.h"
+
+#include "CallbackHandler.h"
+#include "PyContext.h"
+#include "ServiceBroker.h"
+#include "XBPython.h"
+#include "interfaces/legacy/AddonUtils.h"
+#include "utils/log.h"
+
+#include <mutex>
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ static AddonClass::Ref<PythonLanguageHook> instance;
+
+ static CCriticalSection hooksMutex;
+ static std::map<PyInterpreterState*,AddonClass::Ref<PythonLanguageHook> > hooks;
+
+ // vtab instantiation
+ PythonLanguageHook::~PythonLanguageHook()
+ {
+ XBMC_TRACE;
+ XBMCAddon::LanguageHook::deallocating();
+ }
+
+ void PythonLanguageHook::MakePendingCalls()
+ {
+ XBMC_TRACE;
+ PythonCallbackHandler::makePendingCalls();
+ }
+
+ void PythonLanguageHook::DelayedCallOpen()
+ {
+ XBMC_TRACE;
+ PyGILLock::releaseGil();
+ }
+
+ void PythonLanguageHook::DelayedCallClose()
+ {
+ XBMC_TRACE;
+ PyGILLock::acquireGil();
+ }
+
+ void PythonLanguageHook::RegisterMe()
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(hooksMutex);
+ hooks[m_interp] = AddonClass::Ref<PythonLanguageHook>(this);
+ }
+
+ void PythonLanguageHook::UnregisterMe()
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(hooksMutex);
+ hooks.erase(m_interp);
+ }
+
+ static AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> g_languageHook;
+
+ // Ok ... we're going to get it even if it doesn't exist. If it doesn't exist then
+ // we're going to assume we're not in control of the interpreter. This (apparently)
+ // can be the case. E.g. Libspotify manages to call into a script using a ctypes
+ // extension but under the control of an Interpreter we know nothing about. In
+ // cases like this we're going to use a global interpreter
+ AddonClass::Ref<PythonLanguageHook> PythonLanguageHook::GetIfExists(PyInterpreterState* interp)
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(hooksMutex);
+ std::map<PyInterpreterState*,AddonClass::Ref<PythonLanguageHook> >::iterator iter = hooks.find(interp);
+ if (iter != hooks.end())
+ return iter->second;
+
+ // if we got here then we need to use the global one.
+ if (g_languageHook.isNull())
+ g_languageHook = new XBMCAddon::Python::PythonLanguageHook();
+
+ return g_languageHook;
+ }
+
+ bool PythonLanguageHook::IsAddonClassInstanceRegistered(AddonClass* obj)
+ {
+ for (const auto& iter : hooks)
+ {
+ if (iter.second->HasRegisteredAddonClassInstance(obj))
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * PythonCallbackHandler expects to be instantiated PER AddonClass instance
+ * that is to be used as a callback. This is why this cannot be instantiated
+ * once.
+ *
+ * There is an expectation that this method is called from the Python thread
+ * that instantiated an AddonClass that has the potential for a callback.
+ *
+ * See RetardedAsyncCallbackHandler for more details.
+ * See PythonCallbackHandler for more details
+ * See PythonCallbackHandler::PythonCallbackHandler for more details
+ */
+ XBMCAddon::CallbackHandler* PythonLanguageHook::GetCallbackHandler()
+ {
+ XBMC_TRACE;
+ return new PythonCallbackHandler();
+ }
+
+ String PythonLanguageHook::GetAddonId()
+ {
+ XBMC_TRACE;
+
+ // Get a reference to the main module
+ // and global dictionary
+ PyObject* main_module = PyImport_AddModule("__main__");
+ if (!main_module)
+ {
+ CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__);
+ return "";
+ }
+ PyObject* global_dict = PyModule_GetDict(main_module);
+ // Extract a reference to the function "func_name"
+ // from the global dictionary
+ PyObject* pyid = PyDict_GetItemString(global_dict, "__xbmcaddonid__");
+ if (pyid)
+ return PyUnicode_AsUTF8(pyid);
+ return "";
+ }
+
+ String PythonLanguageHook::GetAddonVersion()
+ {
+ XBMC_TRACE;
+ // Get a reference to the main module
+ // and global dictionary
+ PyObject* main_module = PyImport_AddModule("__main__");
+ if (!main_module)
+ {
+ CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__);
+ return "";
+ }
+ PyObject* global_dict = PyModule_GetDict(main_module);
+ // Extract a reference to the function "func_name"
+ // from the global dictionary
+ PyObject* pyversion = PyDict_GetItemString(global_dict, "__xbmcapiversion__");
+ if (pyversion)
+ return PyUnicode_AsUTF8(pyversion);
+ return "";
+ }
+
+ long PythonLanguageHook::GetInvokerId()
+ {
+ XBMC_TRACE;
+
+ // Get a reference to the main module
+ // and global dictionary
+ PyObject* main_module = PyImport_AddModule("__main__");
+ if (!main_module)
+ {
+ CLog::Log(LOGDEBUG, "PythonLanguageHook::{}: __main__ returns null", __FUNCTION__);
+ return -1;
+ }
+ PyObject* global_dict = PyModule_GetDict(main_module);
+ // Extract a reference to the function "func_name"
+ // from the global dictionary
+ PyObject* pyid = PyDict_GetItemString(global_dict, "__xbmcinvokerid__");
+ if (pyid)
+ return PyLong_AsLong(pyid);
+ return -1;
+ }
+
+ void PythonLanguageHook::RegisterPlayerCallback(IPlayerCallback* player)
+ {
+ XBMC_TRACE;
+ CServiceBroker::GetXBPython().RegisterPythonPlayerCallBack(player);
+ }
+ void PythonLanguageHook::UnregisterPlayerCallback(IPlayerCallback* player)
+ {
+ XBMC_TRACE;
+ CServiceBroker::GetXBPython().UnregisterPythonPlayerCallBack(player);
+ }
+ void PythonLanguageHook::RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor)
+ {
+ XBMC_TRACE;
+ CServiceBroker::GetXBPython().RegisterPythonMonitorCallBack(monitor);
+ }
+ void PythonLanguageHook::UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor)
+ {
+ XBMC_TRACE;
+ CServiceBroker::GetXBPython().UnregisterPythonMonitorCallBack(monitor);
+ }
+
+ bool PythonLanguageHook::WaitForEvent(CEvent& hEvent, unsigned int milliseconds)
+ {
+ XBMC_TRACE;
+ return CServiceBroker::GetXBPython().WaitForEvent(hEvent, milliseconds);
+ }
+
+ void PythonLanguageHook::RegisterAddonClassInstance(AddonClass* obj)
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> l(*this);
+ obj->Acquire();
+ currentObjects.insert(obj);
+ }
+
+ void PythonLanguageHook::UnregisterAddonClassInstance(AddonClass* obj)
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> l(*this);
+ if (currentObjects.erase(obj) > 0)
+ obj->Release();
+ }
+
+ bool PythonLanguageHook::HasRegisteredAddonClassInstance(AddonClass* obj)
+ {
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> l(*this);
+ return currentObjects.find(obj) != currentObjects.end();
+ }
+ }
+}
diff --git a/xbmc/interfaces/python/LanguageHook.h b/xbmc/interfaces/python/LanguageHook.h
new file mode 100644
index 0000000..6a4e0d0
--- /dev/null
+++ b/xbmc/interfaces/python/LanguageHook.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/legacy/LanguageHook.h"
+#include "threads/Event.h"
+
+#include <map>
+#include <mutex>
+#include <set>
+
+#include <Python.h>
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ struct MutableInteger;
+
+ /**
+ * This class supplies the python specific functionality for
+ * plugging into the API. It's got a static only implementation
+ * and uses the singleton pattern for access.
+ */
+ class PythonLanguageHook : public XBMCAddon::LanguageHook
+ {
+ PyInterpreterState* m_interp;
+ CCriticalSection crit;
+ std::set<AddonClass*> currentObjects;
+
+ // This constructor is only used to instantiate the global LanguageHook
+ inline PythonLanguageHook() : m_interp(NULL) { }
+
+ public:
+
+ inline explicit PythonLanguageHook(PyInterpreterState* interp) : m_interp(interp) { }
+ ~PythonLanguageHook() override;
+
+ void DelayedCallOpen() override;
+ void DelayedCallClose() override;
+ void MakePendingCalls() override;
+
+ /**
+ * PythonCallbackHandler expects to be instantiated PER AddonClass instance
+ * that is to be used as a callback. This is why this cannot be instantiated
+ * once.
+ *
+ * There is an expectation that this method is called from the Python thread
+ * that instantiated an AddonClass that has the potential for a callback.
+ *
+ * See RetardedAsyncCallbackHandler for more details.
+ * See PythonCallbackHandler for more details
+ * See PythonCallbackHandler::PythonCallbackHandler for more details
+ */
+ XBMCAddon::CallbackHandler* GetCallbackHandler() override;
+
+ String GetAddonId() override;
+ String GetAddonVersion() override;
+ long GetInvokerId() override;
+
+ void RegisterPlayerCallback(IPlayerCallback* player) override;
+ void UnregisterPlayerCallback(IPlayerCallback* player) override;
+ void RegisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) override;
+ void UnregisterMonitorCallback(XBMCAddon::xbmc::Monitor* monitor) override;
+ bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds) override;
+
+ static AddonClass::Ref<PythonLanguageHook> GetIfExists(PyInterpreterState* interp);
+ static bool IsAddonClassInstanceRegistered(AddonClass* obj);
+
+ void RegisterAddonClassInstance(AddonClass* obj);
+ void UnregisterAddonClassInstance(AddonClass* obj);
+ bool HasRegisteredAddonClassInstance(AddonClass* obj);
+ inline bool HasRegisteredAddonClasses()
+ {
+ std::unique_lock<CCriticalSection> l(*this);
+ return !currentObjects.empty();
+ }
+
+ // You should hold the lock on the LanguageHook itself if you're
+ // going to do anything with the set that gets returned.
+ inline std::set<AddonClass*>& GetRegisteredAddonClasses() { return currentObjects; }
+
+ void UnregisterMe();
+ void RegisterMe();
+ };
+ }
+}
+
diff --git a/xbmc/interfaces/python/MethodType.groovy b/xbmc/interfaces/python/MethodType.groovy
new file mode 100644
index 0000000..18597fd
--- /dev/null
+++ b/xbmc/interfaces/python/MethodType.groovy
@@ -0,0 +1,14 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+public enum MethodType
+{
+ constructor, destructor, method
+}
+
+
diff --git a/xbmc/interfaces/python/PyContext.cpp b/xbmc/interfaces/python/PyContext.cpp
new file mode 100644
index 0000000..3b64ac6
--- /dev/null
+++ b/xbmc/interfaces/python/PyContext.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PyContext.h"
+
+#include "utils/log.h"
+
+#include <Python.h>
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ struct PyContextState
+ {
+ inline explicit PyContextState(bool pcreatedByGilRelease = false) :
+ state(NULL), createdByGilRelease(pcreatedByGilRelease) {}
+
+ int value = 0;
+ PyThreadState* state;
+ int gilReleasedDepth = 0;
+ bool createdByGilRelease;
+ };
+
+ static thread_local PyContextState* tlsPyContextState;
+
+ void* PyContext::enterContext()
+ {
+ PyContextState* cur = tlsPyContextState;
+ if (cur == NULL)
+ {
+ cur = new PyContextState();
+ tlsPyContextState = cur;
+ }
+
+ // increment the count
+ cur->value++;
+
+ return cur;
+ }
+
+ void PyContext::leaveContext()
+ {
+ // here we ASSUME that the constructor was called.
+ PyContextState* cur = tlsPyContextState;
+ cur->value--;
+ int curlevel = cur->value;
+
+ // this is a hack but ...
+ if (curlevel < 0)
+ {
+ CLog::Log(LOGERROR, "FATAL: PyContext closed more than opened");
+ curlevel = cur->value = 0;
+ }
+
+ if (curlevel == 0)
+ {
+ // clear the tlsPyContextState
+ tlsPyContextState = NULL;
+ delete cur;
+ }
+ }
+
+ void PyGILLock::releaseGil()
+ {
+ PyContextState* cur = tlsPyContextState;
+
+ // This means we're not within the python context, but
+ // because we may be in a thread spawned by python itself,
+ // we need to handle this.
+ if (!cur)
+ {
+ cur = static_cast<PyContextState*>(PyContext::enterContext());
+ cur->createdByGilRelease = true;
+ }
+
+ if (cur->gilReleasedDepth == 0) // true if we are at the outermost
+ {
+ PyThreadState* _save;
+ // this macro sets _save
+ {
+ Py_UNBLOCK_THREADS
+ }
+ cur->state = _save;
+ }
+ cur->gilReleasedDepth++; // the first time this goes to 1
+ }
+
+ void PyGILLock::acquireGil()
+ {
+ PyContextState* cur = tlsPyContextState;
+
+ // it's not possible for cur to be NULL (and if it is, we want to fail anyway).
+
+ // decrement the depth and make sure we're in the right place.
+ cur->gilReleasedDepth--;
+ if (cur->gilReleasedDepth == 0) // are we back to zero?
+ {
+ PyThreadState* _save = cur->state;
+ // This macros uses _save
+ {
+ Py_BLOCK_THREADS
+ }
+ cur->state = NULL; // clear the state to indicate we've reacquired the gil
+
+ // we clear it only if we created it on this level.
+ if (cur->createdByGilRelease)
+ PyContext::leaveContext();
+ }
+ }
+ }
+}
diff --git a/xbmc/interfaces/python/PyContext.h b/xbmc/interfaces/python/PyContext.h
new file mode 100644
index 0000000..216a45f
--- /dev/null
+++ b/xbmc/interfaces/python/PyContext.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+namespace XBMCAddon
+{
+ namespace Python
+ {
+ class PyGILLock;
+
+ /**
+ * These classes should NOT be used with 'new'. They are expected to reside
+ * as stack instances and they act as "Guard" classes that track the
+ * current context.
+ */
+ class PyContext
+ {
+ protected:
+ friend class PyGILLock;
+ static void* enterContext();
+ static void leaveContext();
+ public:
+
+ inline PyContext() { enterContext(); }
+ inline ~PyContext() { leaveContext(); }
+ };
+
+ /**
+ * This class supports recursive locking of the GIL. It assumes that
+ * all Python GIL manipulation is done through this class so that it
+ * can monitor the current owner.
+ */
+ class PyGILLock
+ {
+ public:
+ static void releaseGil();
+ static void acquireGil();
+
+ inline PyGILLock() { releaseGil(); }
+ inline ~PyGILLock() { acquireGil(); }
+ };
+ }
+}
diff --git a/xbmc/interfaces/python/PythonInvoker.cpp b/xbmc/interfaces/python/PythonInvoker.cpp
new file mode 100644
index 0000000..1e9d344
--- /dev/null
+++ b/xbmc/interfaces/python/PythonInvoker.cpp
@@ -0,0 +1,724 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// clang-format off
+// python.h should always be included first before any other includes
+#include <mutex>
+#include <Python.h>
+// clang-format on
+
+#include "PythonInvoker.h"
+
+#include "ServiceBroker.h"
+#include "addons/AddonManager.h"
+#include "addons/addoninfo/AddonInfo.h"
+#include "addons/addoninfo/AddonType.h"
+#include "dialogs/GUIDialogKaiToast.h"
+#include "filesystem/SpecialProtocol.h"
+#include "guilib/GUIComponent.h"
+#include "guilib/GUIWindowManager.h"
+#include "guilib/LocalizeStrings.h"
+#include "interfaces/python/PyContext.h"
+#include "interfaces/python/pythreadstate.h"
+#include "interfaces/python/swig.h"
+#include "messaging/ApplicationMessenger.h"
+#include "threads/SingleLock.h"
+#include "threads/SystemClock.h"
+#include "utils/CharsetConverter.h"
+#include "utils/FileUtils.h"
+#include "utils/StringUtils.h"
+#include "utils/URIUtils.h"
+#include "utils/XTimeUtils.h"
+#include "utils/log.h"
+#include "windowing/GraphicContext.h"
+
+// clang-format off
+// This breaks fmt because of SEP define, don't include
+// before anything that includes logging
+#include <osdefs.h>
+// clang-format on
+
+#include <cassert>
+#include <iterator>
+
+#ifdef TARGET_WINDOWS
+extern "C" FILE* fopen_utf8(const char* _Filename, const char* _Mode);
+#else
+#define fopen_utf8 fopen
+#endif
+
+#define GC_SCRIPT \
+ "import gc\n" \
+ "gc.collect(2)\n"
+
+#define PY_PATH_SEP DELIM
+
+// Time before ill-behaved scripts are terminated
+#define PYTHON_SCRIPT_TIMEOUT 5000ms // ms
+
+using namespace XFILE;
+using namespace std::chrono_literals;
+
+#define PythonModulesSize sizeof(PythonModules) / sizeof(PythonModule)
+
+CCriticalSection CPythonInvoker::s_critical;
+
+static const std::string getListOfAddonClassesAsString(
+ XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook>& languageHook)
+{
+ std::string message;
+ std::unique_lock<CCriticalSection> l(*(languageHook.get()));
+ const std::set<XBMCAddon::AddonClass*>& acs = languageHook->GetRegisteredAddonClasses();
+ bool firstTime = true;
+ for (const auto& iter : acs)
+ {
+ if (!firstTime)
+ message += ",";
+ else
+ firstTime = false;
+ message += iter->GetClassname();
+ }
+
+ return message;
+}
+
+CPythonInvoker::CPythonInvoker(ILanguageInvocationHandler* invocationHandler)
+ : ILanguageInvoker(invocationHandler), m_threadState(NULL), m_stop(false)
+{
+}
+
+CPythonInvoker::~CPythonInvoker()
+{
+ // nothing to do for the default invoker used for registration with the
+ // CScriptInvocationManager
+ if (GetId() < 0)
+ return;
+
+ if (GetState() < InvokerStateExecutionDone)
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): waiting for python thread \"{}\" to stop", GetId(),
+ (!m_sourceFile.empty() ? m_sourceFile : "unknown script"));
+ Stop(true);
+ pulseGlobalEvent();
+
+ onExecutionFinalized();
+}
+
+bool CPythonInvoker::Execute(
+ const std::string& script,
+ const std::vector<std::string>& arguments /* = std::vector<std::string>() */)
+{
+ if (script.empty())
+ return false;
+
+ if (!CFileUtils::Exists(script))
+ {
+ CLog::Log(LOGERROR, "CPythonInvoker({}): python script \"{}\" does not exist", GetId(),
+ CSpecialProtocol::TranslatePath(script));
+ return false;
+ }
+
+ if (!onExecutionInitialized())
+ return false;
+
+ return ILanguageInvoker::Execute(script, arguments);
+}
+
+bool CPythonInvoker::execute(const std::string& script, const std::vector<std::string>& arguments)
+{
+ std::vector<std::wstring> w_arguments;
+ for (const auto& argument : arguments)
+ {
+ std::wstring w_argument;
+ g_charsetConverter.utf8ToW(argument, w_argument);
+ w_arguments.push_back(w_argument);
+ }
+ return execute(script, w_arguments);
+}
+
+bool CPythonInvoker::execute(const std::string& script, std::vector<std::wstring>& arguments)
+{
+ // copy the code/script into a local string buffer
+ m_sourceFile = script;
+ std::set<std::string> pythonPath;
+
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): start processing", GetId(), m_sourceFile);
+
+ std::string realFilename(CSpecialProtocol::TranslatePath(m_sourceFile));
+ std::string scriptDir = URIUtils::GetDirectory(realFilename);
+ URIUtils::RemoveSlashAtEnd(scriptDir);
+
+ // set m_threadState if it's not set.
+ PyThreadState* l_threadState = nullptr;
+ bool newInterp = false;
+ {
+ if (!m_threadState)
+ {
+#if PY_VERSION_HEX < 0x03070000
+ // this is a TOTAL hack. We need the GIL but we need to borrow a PyThreadState in order to get it
+ // as of Python 3.2 since PyEval_AcquireLock is deprecated
+ extern PyThreadState* savestate;
+ PyEval_RestoreThread(savestate);
+#else
+ PyThreadState* ts = PyInterpreterState_ThreadHead(PyInterpreterState_Main());
+ PyEval_RestoreThread(ts);
+#endif
+ l_threadState = Py_NewInterpreter();
+ PyEval_ReleaseThread(l_threadState);
+ if (l_threadState == NULL)
+ {
+ CLog::Log(LOGERROR, "CPythonInvoker({}, {}): FAILED to get thread m_threadState!", GetId(),
+ m_sourceFile);
+ return false;
+ }
+ newInterp = true;
+ }
+ else
+ l_threadState = m_threadState;
+ }
+
+ // get the GIL
+ PyEval_RestoreThread(l_threadState);
+ if (newInterp)
+ {
+ m_languageHook = new XBMCAddon::Python::PythonLanguageHook(l_threadState->interp);
+ m_languageHook->RegisterMe();
+
+ onInitialization();
+ setState(InvokerStateInitialized);
+
+ if (realFilename == m_sourceFile)
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): the source file to load is \"{}\"", GetId(),
+ m_sourceFile, m_sourceFile);
+ else
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): the source file to load is \"{}\" (\"{}\")",
+ GetId(), m_sourceFile, m_sourceFile, realFilename);
+
+ // get path from script file name and add python path's
+ // this is used for python so it will search modules from script path first
+ pythonPath.emplace(scriptDir);
+
+ // add all addon module dependencies to path
+ if (m_addon)
+ {
+ std::set<std::string> paths;
+ getAddonModuleDeps(m_addon, paths);
+ for (const auto& it : paths)
+ pythonPath.emplace(it);
+ }
+ else
+ { // for backwards compatibility.
+ // we don't have any addon so just add all addon modules installed
+ CLog::Log(
+ LOGWARNING,
+ "CPythonInvoker({}): Script invoked without an addon. Adding all addon "
+ "modules installed to python path as fallback. This behaviour will be removed in future "
+ "version.",
+ GetId());
+ ADDON::VECADDONS addons;
+ CServiceBroker::GetAddonMgr().GetAddons(addons, ADDON::AddonType::SCRIPT_MODULE);
+ for (unsigned int i = 0; i < addons.size(); ++i)
+ pythonPath.emplace(CSpecialProtocol::TranslatePath(addons[i]->LibPath()));
+ }
+
+ PyObject* sysPath = PySys_GetObject("path");
+
+ std::for_each(pythonPath.crbegin(), pythonPath.crend(),
+ [&sysPath](const auto& path)
+ {
+ PyObject* pyPath = PyUnicode_FromString(path.c_str());
+ PyList_Insert(sysPath, 0, pyPath);
+
+ Py_DECREF(pyPath);
+ });
+
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): full python path:", GetId());
+
+ Py_ssize_t pathListSize = PyList_Size(sysPath);
+
+ for (Py_ssize_t index = 0; index < pathListSize; index++)
+ {
+ if (index == 0 && !pythonPath.empty())
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): custom python path:", GetId());
+
+ if (index == static_cast<ssize_t>(pythonPath.size()))
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): default python path:", GetId());
+
+ PyObject* pyPath = PyList_GetItem(sysPath, index);
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): {}", GetId(), PyUnicode_AsUTF8(pyPath));
+ }
+
+ { // set the m_threadState to this new interp
+ std::unique_lock<CCriticalSection> lockMe(m_critical);
+ m_threadState = l_threadState;
+ }
+ }
+ else
+ // swap in my thread m_threadState
+ PyThreadState_Swap(m_threadState);
+
+ PyObject* sysArgv = PyList_New(0);
+
+ if (arguments.empty())
+ arguments.emplace_back(L"");
+
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): adding args:", GetId());
+
+ for (const auto& arg : arguments)
+ {
+ PyObject* pyArg = PyUnicode_FromWideChar(arg.c_str(), arg.length());
+ PyList_Append(sysArgv, pyArg);
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}): {}", GetId(), PyUnicode_AsUTF8(pyArg));
+
+ Py_DECREF(pyArg);
+ }
+
+ PySys_SetObject("argv", sysArgv);
+
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): entering source directory {}", GetId(), m_sourceFile,
+ scriptDir);
+ PyObject* module = PyImport_AddModule("__main__");
+ PyObject* moduleDict = PyModule_GetDict(module);
+
+ // we need to check if we was asked to abort before we had inited
+ bool stopping = false;
+ {
+ GilSafeSingleLock lock(m_critical);
+ stopping = m_stop;
+ }
+
+ bool failed = false;
+ std::string exceptionType, exceptionValue, exceptionTraceback;
+ if (!stopping)
+ {
+ try
+ {
+ // run script from file
+ // We need to have python open the file because on Windows the DLL that python
+ // is linked against may not be the DLL that xbmc is linked against so
+ // passing a FILE* to python from an fopen has the potential to crash.
+
+ PyObject* pyRealFilename = Py_BuildValue("s", realFilename.c_str());
+ FILE* fp = _Py_fopen_obj(pyRealFilename, "rb");
+ Py_DECREF(pyRealFilename);
+
+ if (fp != NULL)
+ {
+ PyObject* f = PyUnicode_FromString(realFilename.c_str());
+ PyDict_SetItemString(moduleDict, "__file__", f);
+
+ onPythonModuleInitialization(moduleDict);
+
+ Py_DECREF(f);
+ setState(InvokerStateRunning);
+ XBMCAddon::Python::PyContext
+ pycontext; // this is a guard class that marks this callstack as being in a python context
+ executeScript(fp, realFilename, moduleDict);
+ }
+ else
+ CLog::Log(LOGERROR, "CPythonInvoker({}, {}): {} not found!", GetId(), m_sourceFile,
+ m_sourceFile);
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ setState(InvokerStateFailed);
+ e.LogThrowMessage();
+ failed = true;
+ }
+ catch (...)
+ {
+ setState(InvokerStateFailed);
+ CLog::Log(LOGERROR, "CPythonInvoker({}, {}): failure in script", GetId(), m_sourceFile);
+ failed = true;
+ }
+ }
+
+ m_systemExitThrown = false;
+ InvokerState stateToSet;
+ if (!failed && !PyErr_Occurred())
+ {
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script successfully run", GetId(), m_sourceFile);
+ stateToSet = InvokerStateScriptDone;
+ onSuccess();
+ }
+ else if (PyErr_ExceptionMatches(PyExc_SystemExit))
+ {
+ m_systemExitThrown = true;
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script aborted", GetId(), m_sourceFile);
+ stateToSet = InvokerStateFailed;
+ onAbort();
+ }
+ else
+ {
+ stateToSet = InvokerStateFailed;
+
+ // if it failed with an exception we already logged the details
+ if (!failed)
+ {
+ PythonBindings::PythonToCppException* e = NULL;
+ if (PythonBindings::PythonToCppException::ParsePythonException(exceptionType, exceptionValue,
+ exceptionTraceback))
+ e = new PythonBindings::PythonToCppException(exceptionType, exceptionValue,
+ exceptionTraceback);
+ else
+ e = new PythonBindings::PythonToCppException();
+
+ e->LogThrowMessage();
+ delete e;
+ }
+
+ onError(exceptionType, exceptionValue, exceptionTraceback);
+ }
+
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ // no need to do anything else because the script has already stopped
+ if (failed)
+ {
+ setState(stateToSet);
+ return true;
+ }
+
+ if (m_threadState)
+ {
+ // make sure all sub threads have finished
+ for (PyThreadState* old = nullptr; m_threadState != nullptr;)
+ {
+ PyThreadState* s = PyInterpreterState_ThreadHead(m_threadState->interp);
+ for (; s && s == m_threadState;)
+ s = PyThreadState_Next(s);
+
+ if (!s)
+ break;
+
+ if (old != s)
+ {
+ CLog::Log(LOGINFO, "CPythonInvoker({}, {}): waiting on thread {}", GetId(), m_sourceFile,
+ (uint64_t)s->thread_id);
+ old = s;
+ }
+
+ lock.unlock();
+ CPyThreadState pyState;
+ KODI::TIME::Sleep(100ms);
+ pyState.Restore();
+ lock.lock();
+ }
+ }
+
+ // pending calls must be cleared out
+ XBMCAddon::RetardedAsyncCallbackHandler::clearPendingCalls(m_threadState);
+
+ assert(m_threadState != nullptr);
+ PyEval_ReleaseThread(m_threadState);
+
+ setState(stateToSet);
+
+ return true;
+}
+
+void CPythonInvoker::executeScript(FILE* fp, const std::string& script, PyObject* moduleDict)
+{
+ if (fp == NULL || script.empty() || moduleDict == NULL)
+ return;
+
+ int m_Py_file_input = Py_file_input;
+ PyRun_FileExFlags(fp, script.c_str(), m_Py_file_input, moduleDict, moduleDict, 1, NULL);
+}
+
+FILE* CPythonInvoker::PyFile_AsFileWithMode(PyObject* py_file, const char* mode)
+{
+ PyObject* ret = PyObject_CallMethod(py_file, "flush", "");
+ if (ret == NULL)
+ return NULL;
+ Py_DECREF(ret);
+
+ int fd = PyObject_AsFileDescriptor(py_file);
+ if (fd == -1)
+ return NULL;
+
+ FILE* f = fdopen(fd, mode);
+ if (f == NULL)
+ {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ return f;
+}
+
+bool CPythonInvoker::stop(bool abort)
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ m_stop = true;
+
+ if (!IsRunning() && !m_threadState)
+ return false;
+
+ if (m_threadState != NULL)
+ {
+ if (IsRunning())
+ {
+ setState(InvokerStateStopping);
+ lock.unlock();
+
+ PyEval_RestoreThread((PyThreadState*)m_threadState);
+
+ //tell xbmc.Monitor to call onAbortRequested()
+ if (m_addon)
+ {
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): trigger Monitor abort request", GetId(),
+ m_sourceFile);
+ AbortNotification();
+ }
+
+ PyEval_ReleaseThread(m_threadState);
+ }
+ else
+ //Release the lock while waiting for threads to finish
+ lock.unlock();
+
+ XbmcThreads::EndTime<> timeout(PYTHON_SCRIPT_TIMEOUT);
+ while (!m_stoppedEvent.Wait(15ms))
+ {
+ if (timeout.IsTimePast())
+ {
+ CLog::Log(LOGERROR,
+ "CPythonInvoker({}, {}): script didn't stop in {} seconds - let's kill it",
+ GetId(), m_sourceFile,
+ std::chrono::duration_cast<std::chrono::seconds>(PYTHON_SCRIPT_TIMEOUT).count());
+ break;
+ }
+
+ // We can't empty-spin in the main thread and expect scripts to be able to
+ // dismantle themselves. Python dialogs aren't normal XBMC dialogs, they rely
+ // on TMSG_GUI_PYTHON_DIALOG messages, so pump the message loop.
+ if (CServiceBroker::GetAppMessenger()->IsProcessThread())
+ {
+ CServiceBroker::GetAppMessenger()->ProcessMessages();
+ }
+ }
+
+ lock.lock();
+
+ setState(InvokerStateExecutionDone);
+
+ // Useful for add-on performance metrics
+ if (!timeout.IsTimePast())
+ CLog::Log(LOGDEBUG, "CPythonInvoker({}, {}): script termination took {}ms", GetId(),
+ m_sourceFile, (PYTHON_SCRIPT_TIMEOUT - timeout.GetTimeLeft()).count());
+
+ // Since we released the m_critical it's possible that the state is cleaned up
+ // so we need to recheck for m_threadState == NULL
+ if (m_threadState != NULL)
+ {
+ {
+ // grabbing the PyLock while holding the m_critical is asking for a deadlock
+ CSingleExit ex2(m_critical);
+ PyEval_RestoreThread((PyThreadState*)m_threadState);
+ }
+
+
+ PyThreadState* state = PyInterpreterState_ThreadHead(m_threadState->interp);
+ while (state)
+ {
+ // Raise a SystemExit exception in python threads
+ Py_XDECREF(state->async_exc);
+ state->async_exc = PyExc_SystemExit;
+ Py_XINCREF(state->async_exc);
+ state = PyThreadState_Next(state);
+ }
+
+ // If a dialog entered its doModal(), we need to wake it to see the exception
+ pulseGlobalEvent();
+
+ PyEval_ReleaseThread(m_threadState);
+ }
+ lock.unlock();
+
+ setState(InvokerStateFailed);
+ }
+
+ return true;
+}
+
+// Always called from Invoker thread
+void CPythonInvoker::onExecutionDone()
+{
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ if (m_threadState != NULL)
+ {
+ CLog::Log(LOGDEBUG, "{}({}, {})", __FUNCTION__, GetId(), m_sourceFile);
+
+ PyEval_RestoreThread(m_threadState);
+
+ onDeinitialization();
+
+ // run the gc before finishing
+ //
+ // if the script exited by throwing a SystemExit exception then going back
+ // into the interpreter causes this python bug to get hit:
+ // http://bugs.python.org/issue10582
+ // and that causes major failures. So we are not going to go back in
+ // to run the GC if that's the case.
+ if (!m_stop && m_languageHook->HasRegisteredAddonClasses() && !m_systemExitThrown &&
+ PyRun_SimpleString(GC_SCRIPT) == -1)
+ CLog::Log(LOGERROR,
+ "CPythonInvoker({}, {}): failed to run the gc to clean up after running prior to "
+ "shutting down the Interpreter",
+ GetId(), m_sourceFile);
+
+ Py_EndInterpreter(m_threadState);
+
+ // If we still have objects left around, produce an error message detailing what's been left behind
+ if (m_languageHook->HasRegisteredAddonClasses())
+ CLog::Log(LOGWARNING,
+ "CPythonInvoker({}, {}): the python script \"{}\" has left several "
+ "classes in memory that we couldn't clean up. The classes include: {}",
+ GetId(), m_sourceFile, m_sourceFile, getListOfAddonClassesAsString(m_languageHook));
+
+ // unregister the language hook
+ m_languageHook->UnregisterMe();
+
+#if PY_VERSION_HEX < 0x03070000
+ PyEval_ReleaseLock();
+#else
+ PyThreadState_Swap(PyInterpreterState_ThreadHead(PyInterpreterState_Main()));
+ PyEval_SaveThread();
+#endif
+
+ // set stopped event - this allows ::stop to run and kill remaining threads
+ // this event has to be fired without holding m_critical
+ // also the GIL (PyEval_AcquireLock) must not be held
+ // if not obeyed there is still no deadlock because ::stop waits with timeout (smart one!)
+ m_stoppedEvent.Set();
+
+ m_threadState = nullptr;
+
+ setState(InvokerStateExecutionDone);
+ }
+ ILanguageInvoker::onExecutionDone();
+}
+
+void CPythonInvoker::onExecutionFailed()
+{
+ PyEval_SaveThread();
+
+ setState(InvokerStateFailed);
+ CLog::Log(LOGERROR, "CPythonInvoker({}, {}): abnormally terminating python thread", GetId(),
+ m_sourceFile);
+
+ std::unique_lock<CCriticalSection> lock(m_critical);
+ m_threadState = NULL;
+
+ ILanguageInvoker::onExecutionFailed();
+}
+
+void CPythonInvoker::onInitialization()
+{
+ XBMC_TRACE;
+ {
+ GilSafeSingleLock lock(s_critical);
+ initializeModules(getModules());
+ }
+
+ // get a possible initialization script
+ const char* runscript = getInitializationScript();
+ if (runscript != NULL && strlen(runscript) > 0)
+ {
+ // redirecting default output to debug console
+ if (PyRun_SimpleString(runscript) == -1)
+ CLog::Log(LOGFATAL, "CPythonInvoker({}, {}): initialize error", GetId(), m_sourceFile);
+ }
+}
+
+void CPythonInvoker::onPythonModuleInitialization(void* moduleDict)
+{
+ if (m_addon.get() == NULL || moduleDict == NULL)
+ return;
+
+ PyObject* moduleDictionary = (PyObject*)moduleDict;
+
+ PyObject* pyaddonid = PyUnicode_FromString(m_addon->ID().c_str());
+ PyDict_SetItemString(moduleDictionary, "__xbmcaddonid__", pyaddonid);
+
+ ADDON::CAddonVersion version = m_addon->GetDependencyVersion("xbmc.python");
+ PyObject* pyxbmcapiversion = PyUnicode_FromString(version.asString().c_str());
+ PyDict_SetItemString(moduleDictionary, "__xbmcapiversion__", pyxbmcapiversion);
+
+ PyObject* pyinvokerid = PyLong_FromLong(GetId());
+ PyDict_SetItemString(moduleDictionary, "__xbmcinvokerid__", pyinvokerid);
+
+ CLog::Log(LOGDEBUG,
+ "CPythonInvoker({}, {}): instantiating addon using automatically obtained id of \"{}\" "
+ "dependent on version {} of the xbmc.python api",
+ GetId(), m_sourceFile, m_addon->ID(), version.asString());
+}
+
+void CPythonInvoker::onDeinitialization()
+{
+ XBMC_TRACE;
+}
+
+void CPythonInvoker::onError(const std::string& exceptionType /* = "" */,
+ const std::string& exceptionValue /* = "" */,
+ const std::string& exceptionTraceback /* = "" */)
+{
+ CPyThreadState releaseGil;
+ std::unique_lock<CCriticalSection> gc(CServiceBroker::GetWinSystem()->GetGfxContext());
+
+ CGUIDialogKaiToast* pDlgToast =
+ CServiceBroker::GetGUI()->GetWindowManager().GetWindow<CGUIDialogKaiToast>(
+ WINDOW_DIALOG_KAI_TOAST);
+ if (pDlgToast != NULL)
+ {
+ std::string message;
+ if (m_addon && !m_addon->Name().empty())
+ message = StringUtils::Format(g_localizeStrings.Get(2102), m_addon->Name());
+ else
+ message = g_localizeStrings.Get(2103);
+ pDlgToast->QueueNotification(CGUIDialogKaiToast::Error, message, g_localizeStrings.Get(2104));
+ }
+}
+
+void CPythonInvoker::initializeModules(
+ const std::map<std::string, PythonModuleInitialization>& modules)
+{
+ for (const auto& module : modules)
+ {
+ if (!initializeModule(module.second))
+ CLog::Log(LOGWARNING, "CPythonInvoker({}, {}): unable to initialize python module \"{}\"",
+ GetId(), m_sourceFile, module.first);
+ }
+}
+
+bool CPythonInvoker::initializeModule(PythonModuleInitialization module)
+{
+ if (module == NULL)
+ return false;
+
+ return module() != nullptr;
+}
+
+void CPythonInvoker::getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths)
+{
+ for (const auto& it : addon->GetDependencies())
+ {
+ //Check if dependency is a module addon
+ ADDON::AddonPtr dependency;
+ if (CServiceBroker::GetAddonMgr().GetAddon(it.id, dependency, ADDON::AddonType::SCRIPT_MODULE,
+ ADDON::OnlyEnabled::CHOICE_YES))
+ {
+ std::string path = CSpecialProtocol::TranslatePath(dependency->LibPath());
+ if (paths.find(path) == paths.end())
+ {
+ // add it and its dependencies
+ paths.insert(path);
+ getAddonModuleDeps(dependency, paths);
+ }
+ }
+ }
+}
diff --git a/xbmc/interfaces/python/PythonInvoker.h b/xbmc/interfaces/python/PythonInvoker.h
new file mode 100644
index 0000000..dd093ed
--- /dev/null
+++ b/xbmc/interfaces/python/PythonInvoker.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2013-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/generic/ILanguageInvoker.h"
+#include "interfaces/legacy/Addon.h"
+#include "interfaces/python/LanguageHook.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+
+#include <map>
+#include <string>
+#include <vector>
+
+typedef struct _object PyObject;
+
+class CPythonInvoker : public ILanguageInvoker
+{
+public:
+ explicit CPythonInvoker(ILanguageInvocationHandler* invocationHandler);
+ ~CPythonInvoker() override;
+
+ bool Execute(const std::string& script,
+ const std::vector<std::string>& arguments = std::vector<std::string>()) override;
+
+ bool IsStopping() const override { return m_stop || ILanguageInvoker::IsStopping(); }
+
+ typedef PyObject* (*PythonModuleInitialization)();
+
+protected:
+ // implementation of ILanguageInvoker
+ bool execute(const std::string& script, const std::vector<std::string>& arguments) override;
+ virtual void executeScript(FILE* fp, const std::string& script, PyObject* moduleDict);
+ bool stop(bool abort) override;
+ void onExecutionDone() override;
+ void onExecutionFailed() override;
+
+ // custom virtual methods
+ virtual std::map<std::string, PythonModuleInitialization> getModules() const = 0;
+ virtual const char* getInitializationScript() const = 0;
+ virtual void onInitialization();
+ // actually a PyObject* but don't wanna draw Python.h include into the header
+ virtual void onPythonModuleInitialization(void* moduleDict);
+ virtual void onDeinitialization();
+
+ virtual void onSuccess() {}
+ virtual void onAbort() {}
+ virtual void onError(const std::string& exceptionType = "",
+ const std::string& exceptionValue = "",
+ const std::string& exceptionTraceback = "");
+
+ std::string m_sourceFile;
+ CCriticalSection m_critical;
+
+private:
+ void initializeModules(const std::map<std::string, PythonModuleInitialization>& modules);
+ bool initializeModule(PythonModuleInitialization module);
+ void getAddonModuleDeps(const ADDON::AddonPtr& addon, std::set<std::string>& paths);
+ bool execute(const std::string& script, std::vector<std::wstring>& arguments);
+ FILE* PyFile_AsFileWithMode(PyObject* py_file, const char* mode);
+
+ PyThreadState* m_threadState;
+ bool m_stop;
+ CEvent m_stoppedEvent;
+
+ XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> m_languageHook;
+ bool m_systemExitThrown = false;
+
+ static CCriticalSection s_critical;
+};
diff --git a/xbmc/interfaces/python/PythonSwig.cpp.template b/xbmc/interfaces/python/PythonSwig.cpp.template
new file mode 100644
index 0000000..24756ea
--- /dev/null
+++ b/xbmc/interfaces/python/PythonSwig.cpp.template
@@ -0,0 +1,942 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+%>
+<%
+import Helper
+import SwigTypeParser
+import PythonTools
+
+import groovy.xml.XmlUtil
+import groovy.text.SimpleTemplateEngine
+import java.util.regex.Pattern
+
+/**
+ * All of the method nodes and all of the class nodes are used several
+ * times over, so they are pulled out once here.
+ */
+
+// ---------------------------------------------------------
+// initialize the SwigTypeParser with the module's typetables
+module.findAll( { it.name() == 'typetab' } ).each { SwigTypeParser.appendTypeTable(it) }
+// ---------------------------------------------------------
+
+// ---------------------------------------------------------
+// Flatten out all of the method/function nodes, whether inside
+// classes or not, into 'methods'
+List methods = module.depthFirst().findAll { it.name() == 'function' || it.name() == 'constructor' || it.name() == 'destructor' }
+// ---------------------------------------------------------
+
+// ---------------------------------------------------------
+// Flatten out all of the class nodes into 'classes'
+List classes = module.depthFirst().findAll { it.name() == 'class' }
+// ---------------------------------------------------------
+
+// ---------------------------------------------------------
+// Initialize the Helper with the type conversions
+Helper.setup(this,classes,
+ /**
+ * This is meant to contain mini-templates for converting the return type
+ * of the native call to be returned to the python caller.
+ */
+ [ 'void' : 'Py_INCREF(Py_None);\n ${result} = Py_None;',
+ 'long': '${result} = PyLong_FromLong(${api});',
+ 'unsigned long': '${result} = PyLong_FromLong(${api});',
+ 'bool': '${result} = ${api} ? Py_True : Py_False; Py_INCREF(${result});',
+ 'long long': '${result} = Py_BuildValue("L", ${api});',
+ 'int': '${result} = Py_BuildValue("i", ${api});',
+ 'unsigned int': '${result} = Py_BuildValue("I", ${api});',
+ 'double': '${result} = PyFloat_FromDouble(${api});',
+ 'float': '${result} = Py_BuildValue("f", static_cast<double>(${api}));',
+ 'std::string' : new File('typemaps/python.string.outtm'),
+ 'p.q(const).char' : '${result} = PyUnicode_FromString(${api});',
+ (Pattern.compile('''(p.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.outtm'),
+ (Pattern.compile('''std::shared_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'),
+ (Pattern.compile('''std::unique_ptr<\\(.*\\)>''')) : new File('typemaps/python.smart_ptr.outtm'),
+ (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.outtm'),
+ (Pattern.compile('''(p.){0,1}Tuple<\\(.*\\)>''')) : new File('typemaps/python.Tuple.outtm'),
+ (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.outtm')
+ ], '${result} = makePythonInstance(${api},true);',
+ /**
+ * This is meant to contain mini-templates for converting the parameter types
+ * of the native call to be converted from the python types provided by the caller.
+ *
+ * Note: if the type can be handled by PythonTools.ltypeToFormatChar then it wont
+ * appear here as it gets converted directly within the PyArg_ParseTupleAndKeywords
+ * call.
+ */
+ [
+ 'std::string' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},false,"${api}","${method.@name}");',
+ (Pattern.compile('''(p.){0,1}std::vector<\\(.*\\)>''')) : new File('typemaps/python.vector.intm'),
+ (Pattern.compile('''(p.){0,1}Tuple(3){0,1}<\\(.*\\)>''')) : new File('typemaps/python.Tuple.intm'),
+ (Pattern.compile('''(p.){0,1}Alternative<\\(.*\\)>''')) : new File('typemaps/python.Alternative.intm'),
+ (Pattern.compile('''(r.){0,1}XbmcCommons::Buffer''')) : new File('typemaps/python.buffer.intm'),
+ (Pattern.compile('''(p.){0,1}std::map<\\(.*\\)>''')) : new File('typemaps/python.map.intm'),
+ (Pattern.compile('''(r.){0,1}XBMCAddon::Dictionary<\\(.*\\)>''')) : new File('typemaps/python.dict.intm'),
+ (Pattern.compile('''p.void''')) : '${api} = (void*)${slarg};',
+ 'bool' : '${api} = (PyLong_AsLong(${slarg}) == 0L ? false : true);',
+ 'long' : '${api} = PyLong_AsLong(${slarg});',
+ 'unsigned long' : '${api} = PyLong_AsUnsignedLong(${slarg});',
+ 'long long' : '${api} = PyLong_AsLongLong(${slarg});',
+ 'unsigned long long' : '${api} = PyLong_AsUnsignedLongLong(${slarg});',
+ 'int' : '${api} = (int)PyLong_AsLong(${slarg});',
+ 'double' : '${api} = PyFloat_AsDouble(${slarg});',
+ 'float' : '${api} = (float)PyFloat_AsDouble(${slarg});',
+ 'XBMCAddon::StringOrInt' : 'if (${slarg}) PyXBMCGetUnicodeString(${api},${slarg},PyLong_Check(${slarg}) || PyFloat_Check(${slarg}),"${api}","${method.@name}");'
+ ], '${api} = (${swigTypeParser.SwigType_str(ltype)})retrieveApiInstance(${slarg},"${ltype}","${helper.findNamespace(method)}","${helper.callingName(method)}");')
+// ---------------------------------------------------------
+
+/*******************************************************************************/
+/**
+ * The doMethod will actually write out the CPython method call for
+ * the method/function represented by the provided Node ('method').
+ */
+void doMethod(Node method, MethodType methodType)
+{
+ boolean isOperator = method.@name.startsWith("operator ")
+ boolean doAsMappingIndex = false
+ boolean doAsCallable = false
+
+ if (isOperator)
+ {
+ if("[]" == method.@name.substring(9))
+ doAsMappingIndex = true
+ else if("()" == method.@name.substring(9))
+ doAsCallable = true
+ else
+ return;
+ }
+
+ boolean constructor = methodType == MethodType.constructor
+
+ // if we're a constructor, but we're private, then we're outta here
+ if (constructor && method.@access != null && method.@access != "public")
+ return
+
+ boolean destructor = methodType == MethodType.destructor
+ List params = method?.parm
+ int numParams = params?.size()
+ String clazz = Helper.findFullClassName(method)
+ String returns = constructor ? 'p.' + clazz : (destructor ? 'void' : Helper.getReturnSwigType(method))
+ Node classnode = Helper.findClassNode(method)
+ String classNameAsVariable = clazz == null ? null : PythonTools.getClassNameAsVariable(classnode)
+ boolean useKeywordParsing = !('true' == classnode?.@feature_python_nokwds || 'true' == method?.@feature_python_nokwds)
+
+ // do the docs
+ if (!constructor && !destructor)
+ {
+ if (Helper.hasDoc(method))
+ {
+%>
+ PyDoc_STRVAR(${PythonTools.getPyMethodName(method,methodType)}__doc__,
+ ${PythonTools.makeDocString(method.doc[0])});
+<% }
+ }
+%>
+ static <% if(methodType == MethodType.destructor) { %>void<% } else { %>PyObject*<% } %> ${module.@name}_${PythonTools.getPyMethodName(method,methodType)} (<%= ((clazz == null) ? "PyObject" :
+ (constructor ? "PyTypeObject" : 'PyHolder')) %>* ${constructor ? 'pytype' : 'self'} <%
+ if (doAsMappingIndex) { %>, PyObject* py${params[0].@name}<% }
+ else if (methodType != MethodType.destructor) { %> , PyObject *args, PyObject *kwds <%} %> )
+ {
+ XBMC_TRACE;
+<% if (numParams > 0)
+ {
+ if (useKeywordParsing && !doAsMappingIndex)
+ { %>
+ static const char *keywords[] = {<%
+ params.each { %>
+ "${it.@name}",<% } %>
+ NULL};
+<% }
+ params.each {
+%>
+ ${SwigTypeParser.SwigType_str(SwigTypeParser.convertTypeToLTypeForParam(it.@type))} ${it.@name} ${it.@value != null ? ' = ' + it.@value : SwigTypeParser.SwigType_ispointer(it.@type) ? ' = nullptr' : ''};<%
+ if (!PythonTools.parameterCanBeUsedDirectly(it) && !doAsMappingIndex)
+ { %>
+ PyObject* py${it.@name} = NULL;<%
+ }
+ }
+ if (!doAsMappingIndex)
+ { %>
+ if (!${useKeywordParsing ? 'PyArg_ParseTupleAndKeywords' : 'PyArg_ParseTuple'}(
+ args,
+ <% if (useKeywordParsing) { %>kwds,<% } %>
+ "<%= PythonTools.makeFormatStringFromParameters(method) %>",
+ <% if (useKeywordParsing) { %>const_cast<char**>(keywords),<% } %><% params.eachWithIndex { param,i -> %>
+ &${PythonTools.parameterCanBeUsedDirectly(param) ? '' : 'py'}${param.@name}${i < params.size() - 1 ? "," : ""}<% } %>
+ ))
+ {
+ return NULL;
+ }
+
+<% }
+ }
+ // now actually invoke the method
+ if (returns != "void") { %> ${SwigTypeParser.SwigType_str(returns)} apiResult;<% }
+%>
+ try
+ {
+<%
+ // now do the input conversion if any are necessary
+ params.findAll({ !PythonTools.parameterCanBeUsedDirectly(it) || doAsMappingIndex }).each { %> ${Helper.getInConversion(it.@type, it.@name, 'py' + it.@name, method)} <% println() }
+%>
+<%
+ // check to see if this method is a call to a virtual function on a director class.
+ boolean isDirectorCall = Helper.isDirector(method)
+ if (isDirectorCall)
+ {
+%> // This is a director call coming from python so it explicitly calls the base class method.
+<%
+ }
+ // now do the method call itself
+ if (!destructor) {
+ if (constructor || !clazz) { %> XBMCAddon::SetLanguageHookGuard slhg(XBMCAddon::Python::PythonLanguageHook::GetIfExists(PyThreadState_Get()->interp).get());<% println() }
+%> <%
+ if (returns != "void") { %>apiResult = <% }
+ if (clazz && !constructor) {
+ %>((${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(method)}","${clazz}"))-> <%
+ }
+ if (constructor && classnode.@feature_director) {
+ %>(&(Ty${classNameAsVariable}_Type.pythonType) != pytype) ? new ${classNameAsVariable}_Director(<% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) : <% }
+
+ // Here is the actual call ... if this is a Director we need to do an upCall (from Python)
+ if (isDirectorCall){ %>${clazz}::<% }
+ %>${Helper.callingName(method)}( <% params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %> );
+<%
+ if (constructor) { %> prepareForReturn(apiResult);<% }
+ } // close the 'if method is not a destructor'
+ else { // it is a destructor
+%>
+ ${clazz}* theObj = (${clazz}*)retrieveApiInstance((PyObject*)self,&Ty${classNameAsVariable}_Type,"~${Helper.callingName(method)}","${clazz}");
+ cleanForDealloc(theObj);
+<%
+ }
+%>
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_TypeError, e.GetExMessage()); <%
+ if (!destructor) { %>
+ return NULL; <%
+ } %>
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage()); <%
+ if (!destructor) { %>
+ return NULL; <%
+ } %>
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${Helper.callingName(method)}\"");
+ PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${Helper.callingName(method)}\""); <%
+ if (!destructor) { %>
+ return NULL; <%
+ } %>
+ }
+<%
+ if (!destructor) { %>
+ PyObject* result = Py_None;
+
+ // transform the result
+<%
+ if (constructor) {
+ %> result = makePythonInstance(apiResult,pytype,false);<%
+ }
+ else {
+%> ${Helper.getOutConversion(returns,'result',method)}<%
+ }
+ if (constructor && method.@feature_director) { %>
+ if (&(Ty${classNameAsVariable}_Type.pythonType) != pytype)
+ ((${classNameAsVariable}_Director*)apiResult)->setPyObjectForDirector(result);<%
+ }
+ %>
+
+ return result; <% }
+ else { %>
+ (((PyObject*)(self))->ob_type)->tp_free((PyObject*)self);
+ <%
+ }
+ %>
+ } <%
+}
+/*******************************************************************************/
+
+/**
+ * This method writes out the instance of a TypeInfo (which includes
+ * The PyTypeObject as a member) for the class node provided.
+ *
+ * If classNameAsVariable is not null then the class name as a
+ * variable will be appended to it.
+ */
+void doClassTypeInfo(Node clazz, List classNameAsVariables = null)
+{
+ String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz)
+ String fullClassName = Helper.findFullClassName(clazz)
+ classNameAsVariables?.add(classNameAsVariable)
+%>
+ //=========================================================================
+ // These variables will hold the Python Type information for ${fullClassName}
+ TypeInfo Ty${classNameAsVariable}_Type(typeid(${fullClassName}));<%
+%>
+ //=========================================================================
+<%
+}
+
+/**
+ * This method will take the name of an API class from another module and
+ * create an external reference to its TypeInfo instance.
+ */
+void doExternClassTypeInfo(String knownType)
+{
+ String classNameAsVariable = knownType.replaceAll('::','_')
+%>
+ //=========================================================================
+ // These variables define the type ${knownType} from another module
+ extern TypeInfo Ty${classNameAsVariable}_Type;
+ //=========================================================================
+<%
+}
+
+/*******************************************************************************/
+/**
+ * This method takes the class node and outputs all of the python meta-data
+ * and class oddities (like comparators, as_mapping, etc.). These include:
+ *
+ * 1) comparator *_cmp python method as long as there's an operator==, an
+ * operator>, AND an operator<.
+ * 2) it will create a python "as_mapping" method as long as there's both
+ * an operator[], AND a .size() method on the class.
+ * 3) it will handle the explicitly defined rich compare (_rcmp) if the
+ * feature is included in the .i file using %feature("python:rcmp")
+ * 4) The array of PyMethodDefs for the class definition
+ * 5) It will handle public fields as if the were python properties by:
+ * a) Creating a get/set_member if there are read/write properties.
+ * b) Creating only a get if there are only read-only properties.
+ * 6) It will write the init[Classname] method for the class which will
+ * initialize the TypeInfo and PyTypeObject structs.
+ *
+ * If initTypeCalls is not null then the method name for the generated init
+ * method (see #6 above) will be appended to it.
+ */
+void doClassMethodInfo(Node clazz, List initTypeCalls)
+{
+ String classNameAsVariable = PythonTools.getClassNameAsVariable(clazz)
+ String fullClassName = Helper.findFullClassName(clazz)
+ String initTypeCall = "initPy${classNameAsVariable}_Type"
+ initTypeCalls?.add(initTypeCall)
+
+ // see if we have any valid (or invalid) operators
+ boolean doComparator = false
+ boolean doAsMapping = false
+ boolean hasEquivalenceOp = false
+ boolean hasLtOp = false
+ boolean hasGtOp = false
+ Node indexOp = null
+ Node callableOp = null
+ Node sizeNode = null
+
+ List normalMethods = clazz.function.findAll { !it.@name.startsWith("operator ") }
+ List operators = clazz.function.findAll { it.@name.startsWith("operator ") }
+ List properties = clazz.variable.findAll { it.@access != null && it.@access == "public" }
+ List properties_set = properties.findAll { it.@feature_immutable == null || it.@feature_immutable == 0 }
+
+ operators.each {
+ // we have an operator. The only one we can handle is ==
+ if (it.@name.substring(9).startsWith("=="))
+ hasEquivalenceOp = true
+ else if (it.@name.substring(9) == "<")
+ hasLtOp = true
+ else if (it.@name.substring(9) == ">")
+ hasGtOp = true
+ else if (it.@name.substring(9) == "[]")
+ indexOp = it
+ else if (it.@name.substring(9) == "()")
+ callableOp = it
+ else
+ System.err.println ("Warning: class ${fullClassName} has an operator \"${it.@name}\" that is being ignored.");
+ }
+
+ if (hasGtOp || hasLtOp || hasEquivalenceOp)
+ {
+ if (!(hasLtOp && hasGtOp && hasEquivalenceOp))
+ System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a comparator you must implement all 3 operators >,<,==.")
+ else
+ doComparator = true
+ }
+
+ if (indexOp)
+ {
+ sizeNode = clazz.function.find { it.@name == "size" }
+ if (sizeNode)
+ doAsMapping = true
+ else
+ System.err.println ("Warning: class ${fullClassName} has an inconsistent operator set. To get a as_mapping you must implement 'size' as well as operator[]")
+ }
+
+ if (doAsMapping)
+ {
+%>
+ static Py_ssize_t ${module.@name}_${classNameAsVariable}_size_(PyObject* self)
+ {
+ return (Py_ssize_t)((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${Helper.callingName(indexOp)}","${fullClassName}"))-> size();
+ }
+
+ //=========================================================================
+ // tp_as_mapping struct for ${fullClassName}
+ //=========================================================================
+ PyMappingMethods ${module.@name}_${classNameAsVariable}_as_mapping = {
+ ${module.@name}_${classNameAsVariable}_size_, /* inquiry mp_length; __len__ */
+ (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(indexOp,MethodType.method)}, /* binaryfunc mp_subscript __getitem__ */
+ 0, /* objargproc mp_ass_subscript; __setitem__ */
+ };
+<%
+ }
+
+ if (clazz.@feature_python_rcmp)
+ { %>
+ static PyObject* ${module.@name}_${classNameAsVariable}_rcmp(PyObject* obj1, PyObject *obj2, int method)
+ ${Helper.unescape(clazz.@feature_python_rcmp)}
+<%
+ }
+%>
+ //=========================================================================
+ // This section contains the initialization for the
+ // Python extension for the Api class ${fullClassName}
+ //=========================================================================
+ // All of the methods on this class
+ static PyMethodDef ${classNameAsVariable}_methods[] = { <%
+ normalMethods.each { %>
+ {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% }
+
+ // now do all of the explicit feature:python:method's that may be in this class
+ List tmpl = []
+ tmpl.addAll(clazz.attributes().keySet())
+ List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') }
+ newMethodKeys.each { key ->
+ String featureEntry = clazz.attribute(key)
+ String methodName = key.substring('feature_python_method_'.length()) %>
+ {"${methodName}", (PyCFunction)${module.@name}_${PythonTools.getClassNameAsVariable(clazz)}_${methodName}, METH_VARARGS|METH_KEYWORDS, NULL},
+<%
+ }
+%>
+ {NULL, NULL, 0, NULL}
+ };
+
+<%
+ if (properties.size() > 0) {
+%> static PyObject* ${classNameAsVariable}_getMember(PyHolder *self, void *name)
+ {
+ if (self == NULL)
+ return NULL;
+<%
+ String clazzName = Helper.findFullClassName(properties[0])
+%>
+ try
+ {
+ ${clazzName}* theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}");
+
+ PyObject* result = NULL;
+ <%
+ properties.each {
+ String returns = Helper.getPropertyReturnSwigType(it);
+%> if (strcmp((char*)name, "${it.@sym_name}") == 0)
+ {
+ ${SwigTypeParser.SwigType_lstr(returns)} apiResult = theObj->${it.@sym_name};
+ ${Helper.getOutConversion(returns, 'result', it)}
+ }
+ else<%
+ } %>
+ {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ return result;
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_TypeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\"");
+ PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\"");
+ return NULL;
+ }
+
+ return NULL;
+ }
+
+<%
+ if (properties_set.size() > 0) {
+%> int ${classNameAsVariable}_setMember(PyHolder *self, PyObject *value, void *name)
+ {
+ if (self == NULL)
+ return -1;
+
+ ${clazzName}* theObj = NULL;
+ try
+ {
+ theObj = (${clazzName}*)retrieveApiInstance((PyObject*)self, &Ty${classNameAsVariable}_Type, "${classNameAsVariable}_getMember()", "${clazzName}");
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_TypeError, e.GetExMessage());
+ return -1;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage());
+ return -1;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\"");
+ PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${classNameAsVariable}_getMember()\"");
+ return -1;
+ }
+
+<%
+ properties_set.each {
+ String returns = Helper.getPropertyReturnSwigType(it);
+%> if (strcmp((char*)name, "${it.@sym_name}") == 0)
+ {
+ ${SwigTypeParser.SwigType_lstr(returns)} tmp;
+ ${Helper.getInConversion(returns, 'tmp', 'value', it)}
+ if (PyErr_Occurred())
+ throw PythonBindings::PythonToCppException();
+
+ theObj->${it.@sym_name} = tmp;
+ }
+ else<%
+ } %>
+ return -1;
+
+ return 0;
+ } <%
+ }
+%>
+
+ // All of the methods on this class
+ static PyGetSetDef ${classNameAsVariable}_getsets[] = { <%
+ properties.each { %>
+ {(char*)"${it.@sym_name}", (getter)${classNameAsVariable}_getMember, ${(it.@feature_immutable == null || it.@feature_immutable == 0) ? '(setter)' + classNameAsVariable + '_setMember' : 'NULL'}, (char*)${Helper.hasDoc(it) ? PythonTools.makeDocString(it.doc[0]) : 'NULL'}, (char*)"${it.@sym_name}" }, <% }
+%>
+ {NULL}
+ };
+<%
+ }
+
+ if ((clazz.@feature_iterator && clazz.@feature_iterator != '') ||
+ (clazz.@feature_iterable && clazz.@feature_iterable != '')) { %>
+ static PyObject* ${module.@name}_${classNameAsVariable}_iter(PyObject* self)
+ { <%
+ if (clazz.@feature_iterator) { %>
+ return self; <%
+ }
+ else { %>
+ PyObject* result = NULL;
+ try
+ {
+ ${clazz.@feature_iterable}* apiResult = ((${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}"))->begin();
+
+ ${Helper.getOutConversion('p.' + clazz.@feature_iterable,'result',clazz)}
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_TypeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\"");
+ PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\"");
+ return NULL;
+ }
+
+ return result; <%
+ } %>
+ }
+<%
+
+ if (clazz.@feature_iterator) { %>
+ static PyObject* ${module.@name}_${classNameAsVariable}_iternext(PyObject* self)
+ {
+ PyObject* result = NULL;
+ try
+ {
+ ${fullClassName}* iter = (${fullClassName}*)retrieveApiInstance(self,&Ty${classNameAsVariable}_Type,"${module.@name}_${classNameAsVariable}_iternext","${fullClassName}");
+
+ // check if we have reached the end
+ if (!iter->end())
+ {
+ ++(*iter);
+
+ ${clazz.@feature_iterator} apiResult = **iter;
+ ${Helper.getOutConversion(clazz.@feature_iterator,'result',clazz)}
+ }
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_TypeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (const XbmcCommons::Exception& e)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: {}",e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage());
+ return NULL;
+ }
+ catch (...)
+ {
+ CLog::Log(LOGERROR,"EXCEPTION: Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\"");
+ PyErr_SetString(PyExc_RuntimeError, "Unknown exception thrown from the call \"${module.@name}_${classNameAsVariable}_iternext\"");
+ return NULL;
+ }
+
+ return result;
+ }
+<%
+ }
+ }
+%>
+
+ // This method initializes the above mentioned Python Type structure
+ static void ${initTypeCall}()
+ {
+<%
+ if (Helper.hasDoc(clazz))
+ {
+%>
+ PyDoc_STRVAR(${classNameAsVariable}__doc__,
+ ${PythonTools.makeDocString(clazz.doc[0])}
+ );
+<% } %>
+
+ PyTypeObject& pythonType = Ty${classNameAsVariable}_Type.pythonType;
+ pythonType.tp_name = "${module.@name}.${clazz.@sym_name}";
+ pythonType.tp_basicsize = sizeof(PyHolder);
+ pythonType.tp_dealloc = (destructor)${module.@name}_${classNameAsVariable}_Dealloc; <%
+
+ if (clazz.@feature_python_rcmp) { %>
+ pythonType.tp_richcompare=(richcmpfunc)${module.@name}_${classNameAsVariable}_rcmp;<%
+ } %>
+
+ pythonType.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
+
+ pythonType.tp_doc = ${Helper.hasDoc(clazz) ? (classNameAsVariable + '__doc__') : 'NULL' };
+ pythonType.tp_methods = ${classNameAsVariable}_methods; <%
+ if (properties.size() > 0) { %>
+ pythonType.tp_getset = ${classNameAsVariable}_getsets;
+<%
+ }
+ if (callableOp) { %>
+ pythonType.tp_call = (ternaryfunc)${module.@name}_${PythonTools.getPyMethodName(callableOp,MethodType.method)};
+<%
+ }
+ if (doAsMapping) { %>
+ pythonType.tp_as_mapping = &${module.@name}_${classNameAsVariable}_as_mapping;
+<%
+ }
+
+ if (clazz.@feature_iterator) { %>
+ pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter;
+ pythonType.tp_iternext = (iternextfunc)${module.@name}_${classNameAsVariable}_iternext;
+<%
+ }
+ else if (clazz.@feature_iterable && clazz.@feature_iterable != '') { %>
+ pythonType.tp_iter = (getiterfunc)${module.@name}_${classNameAsVariable}_iter;
+<%
+ }
+
+ Node baseclass = PythonTools.findValidBaseClass(clazz, module)
+%>
+
+ pythonType.tp_base = ${baseclass ? ('&(Ty' + PythonTools.getClassNameAsVariable(baseclass) + '_Type.pythonType)') : "NULL"};
+ pythonType.tp_new = <% !Helper.hasDefinedConstructor(clazz) || Helper.hasHiddenConstructor(clazz) ? print('NULL') : print("${module.@name}_${classNameAsVariable}_New") %>;
+ pythonType.tp_init = dummy_tp_init;
+
+ Ty${classNameAsVariable}_Type.swigType="p.${fullClassName}";<%
+ if (baseclass) { %>
+ Ty${classNameAsVariable}_Type.parentType=&Ty${PythonTools.getClassNameAsVariable(baseclass)}_Type;
+<%}
+
+ if (!Helper.hasHiddenConstructor(clazz)) { %>
+ registerAddonClassTypeInformation(&Ty${classNameAsVariable}_Type);
+<%} %>
+ }
+ //=========================================================================
+<%
+}
+/*******************************************************************************/
+
+
+List getAllVirtualMethods(Node clazz)
+{
+ List ret = []
+ ret.addAll(clazz.findAll({ it.name() == 'function' && it.@storage && it.@storage == 'virtual' }))
+ if (clazz.baselist) {
+ if (clazz.baselist[0].base) clazz.baselist[0].base.each {
+ Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz)
+ if (baseclassnode && baseclassnode.@feature_director) ret.addAll(getAllVirtualMethods(baseclassnode))
+ }
+ }
+ return ret;
+}
+
+%>
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// ************************************************************************
+// This file was generated by xbmc compile process. DO NOT EDIT!!
+// It was created by running the code generator on the spec file for
+// the module "${module.@name}" on the template file PythonSwig.template.cpp
+// ************************************************************************
+
+<%
+Helper.getInsertNodes(module, 'begin').each { %>${Helper.unescape(it)}<% }
+%>
+
+#include <Python.h>
+#include <string>
+#include "CompileInfo.h"
+#include "interfaces/python/LanguageHook.h"
+#include "interfaces/python/swig.h"
+#include "interfaces/python/PyContext.h"
+
+<%
+Helper.getInsertNodes(module, 'header').each { %>${Helper.unescape(it)}<% }
+%>
+
+namespace PythonBindings
+{
+<%
+ // initTypeCalls is the
+ List initTypeCalls = []
+ List classNameAsVariables = []
+
+ classes.each { clazz -> doClassTypeInfo(clazz, classNameAsVariables) }
+
+ // make sure known api types are declared as externs
+
+ // first, find all of the declared known api types
+ Set<String> knownApiTypes = new HashSet<String>()
+ module.depthFirst().each
+ {
+ String attr = it.attribute('feature_knownapitypes')
+ if (attr != null)
+ {
+ attr.trim().split(',').each { knownApiTypes.add(it) }
+ }
+ }
+
+ // now declare an extern for each one
+ knownApiTypes.each { doExternClassTypeInfo(it) }
+
+%>
+
+<%
+//=========================================================================
+// Do the directors. For every class that can be extended in python, we
+// need to create a Director instance with bridging calls. This chunk of
+// code will generate those classes.
+ classes.findAll({ it.@feature_director != null }).each { clazz ->
+ // find the constructor for this class
+ constructor = clazz.constructor[0]
+%>
+ //=========================================================================
+ // This class is the Director for ${Helper.findFullClassName(clazz)}.
+ // It provides the "reverse bridge" from C++ to Python to support
+ // cross-language polymorphism.
+ //=========================================================================
+ class ${PythonTools.getClassNameAsVariable(clazz)}_Director : public Director, public ${clazz.@name}
+ {
+ public:
+<%
+ if (constructor)
+ {%>
+ inline ${PythonTools.getClassNameAsVariable(clazz)}_Director(<%
+ List params = constructor?.parm
+ params.eachWithIndex { param, i -> %>${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% }
+ %>) : ${Helper.findFullClassName(constructor)}(<%
+ params.eachWithIndex { param, i -> %> ${param.@name}${i < params.size() - 1 ? "," : ""} <% } %>) { } <%
+ }
+%>
+<%
+ getAllVirtualMethods(clazz).each
+ { %>
+ virtual ${SwigTypeParser.SwigType_str(Helper.getReturnSwigType(it))} ${Helper.callingName(it)}( <%
+ List params = it?.parm
+ String paramFormatStr = ''
+ params.each { paramFormatStr += 'O' }
+ params.eachWithIndex { param, i -> %> ${SwigTypeParser.SwigType_str(param.@type)} ${param.@name}${i < params.size() - 1 ? "," : ""} <% }
+ %> )
+ { <%
+ params.each
+ { param ->
+ %>
+ PyObject* py${param.@name} = NULL;
+ ${Helper.getOutConversion(param.@type,'result',it,['result' : 'py' + param.@name, 'api' : param.@name])}<%
+ }
+%>
+ XBMCAddon::Python::PyContext pyContext;
+ PyObject_CallMethod(self,"${Helper.callingName(it)}","(${paramFormatStr})"<%
+ params.each {
+ %>, py${it.@name} <%
+ }
+ %>);
+ if (PyErr_Occurred())
+ throw PythonBindings::PythonToCppException();
+ }
+<% }
+
+%>
+ };
+<%
+ }
+//=========================================================================
+
+ // types used as method parameter or return values need to be declared
+ // as extern if they are unknown types.
+ methods.each { if (it.name() != 'destructor') { doMethod(it, (it.name() == 'constructor' ? MethodType.constructor : MethodType.method)); println(); } }
+ classes.each { clazz -> doMethod(clazz, MethodType.destructor) }
+
+ // now find any methods that have been added explicitly
+ classes.each { node ->
+ List tmpl = []
+ tmpl.addAll(node.attributes().keySet())
+ List newMethodKeys = tmpl.findAll { it.startsWith('feature_python_method_') }
+ newMethodKeys.each { key ->
+ String featureEntry = node.attribute(key)
+ String methodName = key.substring('feature_python_method_'.length()) %>
+ static PyObject* ${module.@name}_${PythonTools.getClassNameAsVariable(node)}_${methodName}(PyObject* self, PyObject *args, PyObject *kwds)
+ ${Helper.unescape(featureEntry)}
+<%
+ }
+ }
+
+ classes.each { clazz -> doClassMethodInfo(clazz, initTypeCalls) }
+
+%>
+
+ static PyMethodDef ${module.@name}_methods[] = { <%
+ module.depthFirst().findAll({ it.name() == 'function' && Helper.parents(it, { Node lnode -> lnode.name() == 'class'}).size() == 0 }).each { %>
+ {"${it.@sym_name}", (PyCFunction)${module.@name}_${PythonTools.getPyMethodName(it,MethodType.method)}, METH_VARARGS|METH_KEYWORDS, ${Helper.hasDoc(it) ? PythonTools.getPyMethodName(it,MethodType.method) + '__doc__' : 'NULL'} }, <% }
+%>
+ {NULL, NULL, 0, NULL}
+ };
+
+ // This is the call that will call all of the other initializes
+ // for all of the classes in this module
+ static void initTypes()
+ {
+ static bool typesAlreadyInitialized = false;
+ if (!typesAlreadyInitialized)
+ {
+ typesAlreadyInitialized = true;
+<%
+ initTypeCalls.each { %>
+ ${it}();<%
+ }
+
+ classNameAsVariables.each { %>
+ if (PyType_Ready(&(Ty${it}_Type.pythonType)) < 0)
+ return;<%
+ }%>
+ }
+ }
+
+ static struct PyModuleDef createModule
+ {
+ PyModuleDef_HEAD_INIT,
+ "${module.@name}",
+ "",
+ -1,
+ ${module.@name}_methods,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ };
+
+ PyObject *PyInit_Module_${module.@name}()
+ {
+ initTypes();
+
+ // init general ${module.@name} modules
+ PyObject* module;
+
+<% classNameAsVariables.each { %>
+ Py_INCREF(&(Ty${it}_Type.pythonType));<%
+ }%>
+
+ module = PyModule_Create(&createModule);
+ if (module == NULL) return NULL;
+
+<% classes.each { clazz -> %>
+ PyModule_AddObject(module, "${clazz.@sym_name}", (PyObject*)(&(Ty${PythonTools.getClassNameAsVariable(clazz)}_Type.pythonType)));<%
+ }%>
+
+ // constants
+ PyModule_AddStringConstant(module, "__author__", "Team Kodi <http://kodi.tv>");
+ PyModule_AddStringConstant(module, "__date__", CCompileInfo::GetBuildDate().c_str());
+ PyModule_AddStringConstant(module, "__version__", "3.0.1");
+ PyModule_AddStringConstant(module, "__credits__", "Team Kodi");
+ PyModule_AddStringConstant(module, "__platform__", "ALL");
+
+ // need to handle constants
+ // #define constants
+<% module.depthFirst().findAll( { it.name() == 'constant'} ).each {
+ String pyCall =
+ (it.@type == 'int' || it.@type == 'long' || it.@type == 'unsigned int' || it.@type == 'unsigned long' || it.@type == 'bool') ?
+ 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %>
+ ${pyCall}(module,"${it.@sym_name}",${it.@value}); <%
+ } %>
+ // constexpr constants
+<% module.depthFirst().findAll( { it.name() == 'variable' && it.@storage && it.@storage == "constexpr" && !it.@error } ).each {
+ String pyCall =
+ ( it.@type == 'q(const).int' || it.@type == 'q(const).long' || it.@type == 'q(const).unsigned int' || it.@type == 'q(const).unsigned long' || it.@type == 'q(const).bool' ) ?
+ 'PyModule_AddIntConstant' : 'PyModule_AddStringConstant' %>
+ ${pyCall}(module,"${it.@sym_name}",${it.@value}); <%
+ } %>
+ return module;
+ }
+
+} // end PythonBindings namespace for python type definitions
+
+<%
+Helper.getInsertNodes(module, 'footer').each { %>${Helper.unescape(it)}<% }
+%>
diff --git a/xbmc/interfaces/python/PythonTools.groovy b/xbmc/interfaces/python/PythonTools.groovy
new file mode 100644
index 0000000..e41db6c
--- /dev/null
+++ b/xbmc/interfaces/python/PythonTools.groovy
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+import Helper
+import SwigTypeParser
+
+public class PythonTools
+{
+ /**
+ * This array defines a mapping of the api spec type to the python parse format character.
+ * By default, if a lookup here results in 'null' then the format char is 'O'
+ */
+ private static Map ltypeToFormatChar = [
+ 'p.char':"s", bool:"b",
+ int:"i", 'unsigned int' : 'I',
+ long:"l", 'unsigned long' : 'k',
+ 'double':"d", 'float':"f",
+ 'long long' : "L"
+ ]
+
+ /**
+ * if the parameter can be directly read from python then its type should be in the ltypeToFormatChar
+ * otherwise we need an intermediate pyobject
+ */
+ public static boolean parameterCanBeUsedDirectly(Node param) { return ltypeToFormatChar[SwigTypeParser.convertTypeToLTypeForParam(param.@type)] != null }
+
+ /**
+ * This method will take the parameter list from the method node passed
+ * and will convert it to a Python argument string for PyArg_ParseTupleAndKeywords
+ */
+ public static String makeFormatStringFromParameters(Node method)
+ {
+ if (!method)
+ return ''
+ List params = method.parm
+ String format = ""
+ boolean previousDefaulted = false
+ params.eachWithIndex { param, i ->
+ String defaultValue = param.@value
+ String paramtype = SwigTypeParser.convertTypeToLTypeForParam(param.@type)
+ String curFormat = ltypeToFormatChar[paramtype];
+ if (curFormat == null) // then we will assume it's an object
+ curFormat = "O";
+
+ if (defaultValue != null && !previousDefaulted)
+ {
+ format +="|"
+ previousDefaulted = true
+ }
+ format += curFormat
+ }
+ return format;
+ }
+
+ /**
+ * This method gets the FULL class name as a variable including the
+ * namespace. If converts all of the '::' references to '_' so
+ * that the result can be used in part, or in whole, as a variable name
+ */
+ public static String getClassNameAsVariable(Node clazz) { return Helper.findFullClassName(clazz).replaceAll('::','_') }
+
+ public static String getPyMethodName(Node method, MethodType methodType)
+ {
+ String clazz = Helper.findFullClassName(method)?.replaceAll('::','_')
+
+ // if we're not in a class then this must be a method node
+ assert (clazz != null || methodType == MethodType.method), 'Cannot use a non-class function as a constructor or destructor ' + method
+
+ // it's ok to pass a 'class' node if the methodType is either constructor or destructor
+ assert (method.name() != 'class' || (methodType == MethodType.constructor || methodType == MethodType.destructor))
+
+ // if this is a constructor node then the methodtype best reflect that
+ assert (method.name() != 'constructor' || methodType == MethodType.constructor), 'Cannot use a constructor node and not identify the type as a constructor' + method
+
+ // if this is a destructor node then the methodtype best reflect that
+ assert (method.name() != 'destructor' || methodType == MethodType.destructor), 'Cannot use a destructor node and not identify the type as a destructor' + method
+
+ if (clazz == null)
+ return method.@sym_name
+
+ if (methodType == MethodType.constructor)
+ return clazz + "_New"
+
+ if (methodType == MethodType.destructor)
+ return clazz + "_Dealloc"
+
+ if (method.@name.startsWith("operator "))
+ {
+ if ("[]" == method.@name.substring(9))
+ return clazz + "_operatorIndex_"
+
+ if ("()" == method.@name.substring(9))
+ return clazz + "_callable_"
+ }
+
+ return clazz + "_" + method.@sym_name;
+ }
+
+ public static String makeDocString(Node docnode)
+ {
+ if (docnode?.name() != 'doc')
+ throw new RuntimeException("Invalid doc Node passed to PythonTools.makeDocString (" + docnode + ")")
+
+ String[] lines = (docnode.@value).split(Helper.newline)
+ def ret = ''
+ lines.eachWithIndex { val, index ->
+ val = ((val =~ /\\n/).replaceAll('')) // remove extraneous \n's
+ val = val.replaceAll("\\\\","\\\\\\\\") // escape backslash
+ val = ((val =~ /\"/).replaceAll("\\\\\"")) // escape quotes
+ ret += ('"' + val + '\\n"' + (index != lines.length - 1 ? Helper.newline : ''))
+ }
+
+ return ret
+ }
+
+ public static Node findValidBaseClass(Node clazz, Node module, boolean warn = false)
+ {
+ // I need to find the base type if there is a known class with it
+ assert clazz.baselist.size() < 2, "${clazz} has multiple baselists - need to write code to separate out the public one."
+ String baseclass = 'NULL'
+ List knownbases = []
+ if (clazz.baselist)
+ {
+ if (clazz.baselist[0].base) clazz.baselist[0].base.each {
+ Node baseclassnode = Helper.findClassNodeByName(module,it.@name,clazz)
+ if (baseclassnode) knownbases.add(baseclassnode)
+ else if (warn && !Helper.isKnownBaseType(it.@name,clazz))
+ System.out.println("WARNING: the base class ${it.@name} for ${Helper.findFullClassName(clazz)} is unrecognized within ${module.@name}.")
+ }
+ }
+ assert knownbases.size() < 2,
+ "The class ${Helper.findFullClassName(clazz)} has too many known base classes. Multiple inheritance isn't supported in the code generator. Please \"#ifdef SWIG\" out all but one."
+ return knownbases.size() > 0 ? knownbases[0] : null
+ }
+}
diff --git a/xbmc/interfaces/python/XBPython.cpp b/xbmc/interfaces/python/XBPython.cpp
new file mode 100644
index 0000000..ee8ed93
--- /dev/null
+++ b/xbmc/interfaces/python/XBPython.cpp
@@ -0,0 +1,627 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+// clang-format off
+// python.h should always be included first before any other includes
+#include <mutex>
+#include <Python.h>
+// clang-format on
+
+#include "XBPython.h"
+
+#include "ServiceBroker.h"
+#include "Util.h"
+#include "cores/DllLoader/DllLoaderContainer.h"
+#include "filesystem/SpecialProtocol.h"
+#include "interfaces/AnnouncementManager.h"
+#include "interfaces/legacy/AddonUtils.h"
+#include "interfaces/legacy/Monitor.h"
+#include "interfaces/python/AddonPythonInvoker.h"
+#include "interfaces/python/PythonInvoker.h"
+#include "settings/AdvancedSettings.h"
+#include "settings/SettingsComponent.h"
+#include "utils/JSONVariantWriter.h"
+#include "utils/Variant.h"
+#include "utils/log.h"
+#include "utils/CharsetConverter.h"
+
+#ifdef TARGET_WINDOWS
+#include "platform/Environment.h"
+#endif
+
+#include <algorithm>
+
+// Only required for Py3 < 3.7
+PyThreadState* savestate;
+
+bool XBPython::m_bInitialized = false;
+
+XBPython::XBPython()
+{
+ CServiceBroker::GetAnnouncementManager()->AddAnnouncer(this);
+}
+
+XBPython::~XBPython()
+{
+ XBMC_TRACE;
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+}
+
+#define LOCK_AND_COPY(type, dest, src) \
+ if (!m_bInitialized) \
+ return; \
+ std::unique_lock<CCriticalSection> lock(src); \
+ src.hadSomethingRemoved = false; \
+ type dest; \
+ dest = src
+
+#define CHECK_FOR_ENTRY(l, v) \
+ (l.hadSomethingRemoved ? (std::find(l.begin(), l.end(), v) != l.end()) : true)
+
+void XBPython::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data)
+{
+ if (flag & ANNOUNCEMENT::VideoLibrary)
+ {
+ if (message == "OnScanFinished")
+ OnScanFinished("video");
+ else if (message == "OnScanStarted")
+ OnScanStarted("video");
+ else if (message == "OnCleanStarted")
+ OnCleanStarted("video");
+ else if (message == "OnCleanFinished")
+ OnCleanFinished("video");
+ }
+ else if (flag & ANNOUNCEMENT::AudioLibrary)
+ {
+ if (message == "OnScanFinished")
+ OnScanFinished("music");
+ else if (message == "OnScanStarted")
+ OnScanStarted("music");
+ else if (message == "OnCleanStarted")
+ OnCleanStarted("music");
+ else if (message == "OnCleanFinished")
+ OnCleanFinished("music");
+ }
+ else if (flag & ANNOUNCEMENT::GUI)
+ {
+ if (message == "OnScreensaverDeactivated")
+ OnScreensaverDeactivated();
+ else if (message == "OnScreensaverActivated")
+ OnScreensaverActivated();
+ else if (message == "OnDPMSDeactivated")
+ OnDPMSDeactivated();
+ else if (message == "OnDPMSActivated")
+ OnDPMSActivated();
+ }
+
+ std::string jsonData;
+ if (CJSONVariantWriter::Write(
+ data, jsonData,
+ CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_jsonOutputCompact))
+ OnNotification(sender,
+ std::string(ANNOUNCEMENT::AnnouncementFlagToString(flag)) + "." +
+ std::string(message),
+ jsonData);
+}
+
+// message all registered callbacks that we started playing
+void XBPython::OnPlayBackStarted(const CFileItem& file)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackStarted(file);
+ }
+}
+
+// message all registered callbacks that we changed stream
+void XBPython::OnAVStarted(const CFileItem& file)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnAVStarted(file);
+ }
+}
+
+// message all registered callbacks that we changed stream
+void XBPython::OnAVChange()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnAVChange();
+ }
+}
+
+// message all registered callbacks that we paused playing
+void XBPython::OnPlayBackPaused()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackPaused();
+ }
+}
+
+// message all registered callbacks that we resumed playing
+void XBPython::OnPlayBackResumed()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackResumed();
+ }
+}
+
+// message all registered callbacks that xbmc stopped playing
+void XBPython::OnPlayBackEnded()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackEnded();
+ }
+}
+
+// message all registered callbacks that user stopped playing
+void XBPython::OnPlayBackStopped()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackStopped();
+ }
+}
+
+// message all registered callbacks that playback stopped due to error
+void XBPython::OnPlayBackError()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackError();
+ }
+}
+
+// message all registered callbacks that playback speed changed (FF/RW)
+void XBPython::OnPlayBackSpeedChanged(int iSpeed)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSpeedChanged(iSpeed);
+ }
+}
+
+// message all registered callbacks that player is seeking
+void XBPython::OnPlayBackSeek(int64_t iTime, int64_t seekOffset)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSeek(iTime, seekOffset);
+ }
+}
+
+// message all registered callbacks that player chapter seeked
+void XBPython::OnPlayBackSeekChapter(int iChapter)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnPlayBackSeekChapter(iChapter);
+ }
+}
+
+// message all registered callbacks that next item has been queued
+void XBPython::OnQueueNextItem()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<void*>, tmp, m_vecPlayerCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecPlayerCallbackList, it))
+ ((IPlayerCallback*)it)->OnQueueNextItem();
+ }
+}
+
+void XBPython::RegisterPythonPlayerCallBack(IPlayerCallback* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList);
+ m_vecPlayerCallbackList.push_back(pCallback);
+}
+
+void XBPython::UnregisterPythonPlayerCallBack(IPlayerCallback* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecPlayerCallbackList);
+ PlayerCallbackList::iterator it = m_vecPlayerCallbackList.begin();
+ while (it != m_vecPlayerCallbackList.end())
+ {
+ if (*it == pCallback)
+ {
+ it = m_vecPlayerCallbackList.erase(it);
+ m_vecPlayerCallbackList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+}
+
+void XBPython::RegisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList);
+ m_vecMonitorCallbackList.push_back(pCallback);
+}
+
+void XBPython::UnregisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_vecMonitorCallbackList);
+ MonitorCallbackList::iterator it = m_vecMonitorCallbackList.begin();
+ while (it != m_vecMonitorCallbackList.end())
+ {
+ if (*it == pCallback)
+ {
+ it = m_vecMonitorCallbackList.erase(it);
+ m_vecMonitorCallbackList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+}
+
+void XBPython::OnSettingsChanged(const std::string& ID)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it) && (it->GetId() == ID))
+ it->OnSettingsChanged();
+ }
+}
+
+void XBPython::OnScreensaverActivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScreensaverActivated();
+ }
+}
+
+void XBPython::OnScreensaverDeactivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScreensaverDeactivated();
+ }
+}
+
+void XBPython::OnDPMSActivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnDPMSActivated();
+ }
+}
+
+void XBPython::OnDPMSDeactivated()
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnDPMSDeactivated();
+ }
+}
+
+void XBPython::OnScanStarted(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScanStarted(library);
+ }
+}
+
+void XBPython::OnScanFinished(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnScanFinished(library);
+ }
+}
+
+void XBPython::OnCleanStarted(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnCleanStarted(library);
+ }
+}
+
+void XBPython::OnCleanFinished(const std::string& library)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnCleanFinished(library);
+ }
+}
+
+void XBPython::OnNotification(const std::string& sender,
+ const std::string& method,
+ const std::string& data)
+{
+ XBMC_TRACE;
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ it->OnNotification(sender, method, data);
+ }
+}
+
+void XBPython::Uninitialize()
+{
+ // don't handle any more announcements as most scripts are probably already
+ // stopped and executing a callback on one of their already destroyed classes
+ // would lead to a crash
+ CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
+
+ LOCK_AND_COPY(std::vector<PyElem>, tmpvec, m_vecPyList);
+ m_vecPyList.clear();
+ m_vecPyList.hadSomethingRemoved = true;
+
+ lock.unlock(); //unlock here because the python thread might lock when it exits
+
+ // cleanup threads that are still running
+ tmpvec.clear();
+}
+
+void XBPython::Process()
+{
+ if (m_bInitialized)
+ {
+ PyList tmpvec;
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ for (PyList::iterator it = m_vecPyList.begin(); it != m_vecPyList.end();)
+ {
+ if (it->bDone)
+ {
+ tmpvec.push_back(*it);
+ it = m_vecPyList.erase(it);
+ m_vecPyList.hadSomethingRemoved = true;
+ }
+ else
+ ++it;
+ }
+ lock.unlock();
+
+ //delete scripts which are done
+ tmpvec.clear();
+ }
+}
+
+bool XBPython::OnScriptInitialized(ILanguageInvoker* invoker)
+{
+ if (invoker == NULL)
+ return false;
+
+ XBMC_TRACE;
+ CLog::Log(LOGDEBUG, "initializing python engine.");
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ m_iDllScriptCounter++;
+ if (!m_bInitialized)
+ {
+ // Darwin packs .pyo files, we need PYTHONOPTIMIZE on in order to load them.
+ // linux built with unified builds only packages the pyo files so need it
+#if defined(TARGET_DARWIN) || defined(TARGET_LINUX)
+ setenv("PYTHONOPTIMIZE", "1", 1);
+#endif
+ // Info about interesting python envvars available
+ // at http://docs.python.org/using/cmdline.html#environment-variables
+
+#if !defined(TARGET_WINDOWS) && !defined(TARGET_ANDROID)
+ // check if we are running as real xbmc.app or just binary
+ if (!CUtil::GetFrameworksPath(true).empty())
+ {
+ // using external python, it's build looking for xxx/lib/python3.8
+ // so point it to frameworks which is where python3.8 is located
+ setenv("PYTHONHOME", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1);
+ setenv("PYTHONPATH", CSpecialProtocol::TranslatePath("special://frameworks").c_str(), 1);
+ CLog::Log(LOGDEBUG, "PYTHONHOME -> {}",
+ CSpecialProtocol::TranslatePath("special://frameworks"));
+ CLog::Log(LOGDEBUG, "PYTHONPATH -> {}",
+ CSpecialProtocol::TranslatePath("special://frameworks"));
+ }
+#elif defined(TARGET_WINDOWS)
+
+#ifdef TARGET_WINDOWS_STORE
+#ifdef _DEBUG
+ CEnvironment::putenv("PYTHONCASEOK=1");
+#endif
+ CEnvironment::putenv("OS=win10");
+#else // TARGET_WINDOWS_DESKTOP
+ CEnvironment::putenv("OS=win32");
+#endif
+
+ std::wstring pythonHomeW;
+ CCharsetConverter::utf8ToW(CSpecialProtocol::TranslatePath("special://xbmc/system/python"),
+ pythonHomeW);
+ Py_SetPythonHome(pythonHomeW.c_str());
+
+ std::string pythonPath = CSpecialProtocol::TranslatePath("special://xbmc/system/python/DLLs");
+ pythonPath += ";";
+ pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib");
+ pythonPath += ";";
+ pythonPath += CSpecialProtocol::TranslatePath("special://xbmc/system/python/Lib/site-packages");
+ std::wstring pythonPathW;
+ CCharsetConverter::utf8ToW(pythonPath, pythonPathW);
+
+ Py_SetPath(pythonPathW.c_str());
+
+ Py_OptimizeFlag = 1;
+#endif
+
+ Py_Initialize();
+
+#if PY_VERSION_HEX < 0x03070000
+ // Python >= 3.7 Py_Initialize implicitly calls PyEval_InitThreads
+ // Python < 3.7 we have to manually call initthreads.
+ // PyEval_InitThreads is a no-op on subsequent calls, No need to wrap in
+ // PyEval_ThreadsInitialized() check
+ PyEval_InitThreads();
+#endif
+
+ // Acquire GIL if thread doesn't currently hold.
+ if (!PyGILState_Check())
+ PyEval_RestoreThread((PyThreadState*)m_mainThreadState);
+
+ if (!(m_mainThreadState = PyThreadState_Get()))
+ CLog::Log(LOGERROR, "Python threadstate is NULL.");
+ savestate = PyEval_SaveThread();
+
+ m_bInitialized = true;
+ }
+
+ return m_bInitialized;
+}
+
+void XBPython::OnScriptStarted(ILanguageInvoker* invoker)
+{
+ if (invoker == NULL)
+ return;
+
+ if (!m_bInitialized)
+ return;
+
+ PyElem inf;
+ inf.id = invoker->GetId();
+ inf.bDone = false;
+ inf.pyThread = static_cast<CPythonInvoker*>(invoker);
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ m_vecPyList.push_back(inf);
+}
+
+void XBPython::NotifyScriptAborting(ILanguageInvoker* invoker)
+{
+ XBMC_TRACE;
+
+ long invokerId(-1);
+ if (invoker != NULL)
+ invokerId = invoker->GetId();
+
+ LOCK_AND_COPY(std::vector<XBMCAddon::xbmc::Monitor*>, tmp, m_vecMonitorCallbackList);
+ for (auto& it : tmp)
+ {
+ if (CHECK_FOR_ENTRY(m_vecMonitorCallbackList, it))
+ {
+ if (invokerId < 0 || it->GetInvokerId() == invokerId)
+ it->AbortNotify();
+ }
+ }
+}
+
+void XBPython::OnExecutionEnded(ILanguageInvoker* invoker)
+{
+ std::unique_lock<CCriticalSection> lock(m_vecPyList);
+ PyList::iterator it = m_vecPyList.begin();
+ while (it != m_vecPyList.end())
+ {
+ if (it->id == invoker->GetId())
+ {
+ if (it->pyThread->IsStopping())
+ CLog::Log(LOGDEBUG, "Python interpreter interrupted by user");
+ else
+ CLog::Log(LOGDEBUG, "Python interpreter stopped");
+ it->bDone = true;
+ }
+ ++it;
+ }
+}
+
+void XBPython::OnScriptFinalized(ILanguageInvoker* invoker)
+{
+ XBMC_TRACE;
+ std::unique_lock<CCriticalSection> lock(m_critSection);
+ // for linux - we never release the library. its loaded and stays in memory.
+ if (m_iDllScriptCounter)
+ m_iDllScriptCounter--;
+ else
+ CLog::Log(LOGERROR, "Python script counter attempted to become negative");
+}
+
+ILanguageInvoker* XBPython::CreateInvoker()
+{
+ return new CAddonPythonInvoker(this);
+}
+
+void XBPython::PulseGlobalEvent()
+{
+ m_globalEvent.Set();
+}
+
+bool XBPython::WaitForEvent(CEvent& hEvent, unsigned int milliseconds)
+{
+ // wait for either this event our our global event
+ XbmcThreads::CEventGroup eventGroup{&hEvent, &m_globalEvent};
+ CEvent* ret = eventGroup.wait(std::chrono::milliseconds(milliseconds));
+ if (ret)
+ m_globalEvent.Reset();
+ return ret != NULL;
+}
diff --git a/xbmc/interfaces/python/XBPython.h b/xbmc/interfaces/python/XBPython.h
new file mode 100644
index 0000000..e54b4a2
--- /dev/null
+++ b/xbmc/interfaces/python/XBPython.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "cores/IPlayerCallback.h"
+#include "interfaces/IAnnouncer.h"
+#include "interfaces/generic/ILanguageInvocationHandler.h"
+#include "threads/CriticalSection.h"
+#include "threads/Event.h"
+#include "threads/Thread.h"
+
+#include <memory>
+#include <vector>
+
+class CPythonInvoker;
+class CVariant;
+
+typedef struct
+{
+ int id;
+ bool bDone;
+ CPythonInvoker* pyThread;
+} PyElem;
+
+class LibraryLoader;
+
+namespace XBMCAddon
+{
+namespace xbmc
+{
+class Monitor;
+}
+} // namespace XBMCAddon
+
+template<class T>
+struct LockableType : public T, public CCriticalSection
+{
+ bool hadSomethingRemoved;
+};
+
+typedef LockableType<std::vector<void*>> PlayerCallbackList;
+typedef LockableType<std::vector<XBMCAddon::xbmc::Monitor*>> MonitorCallbackList;
+typedef LockableType<std::vector<PyElem>> PyList;
+typedef std::vector<LibraryLoader*> PythonExtensionLibraries;
+
+class XBPython : public IPlayerCallback,
+ public ANNOUNCEMENT::IAnnouncer,
+ public ILanguageInvocationHandler
+{
+public:
+ XBPython();
+ ~XBPython() override;
+ void OnPlayBackEnded() override;
+ void OnPlayBackStarted(const CFileItem& file) override;
+ void OnAVStarted(const CFileItem& file) override;
+ void OnAVChange() override;
+ void OnPlayBackPaused() override;
+ void OnPlayBackResumed() override;
+ void OnPlayBackStopped() override;
+ void OnPlayBackError() override;
+ void OnPlayBackSpeedChanged(int iSpeed) override;
+ void OnPlayBackSeek(int64_t iTime, int64_t seekOffset) override;
+ void OnPlayBackSeekChapter(int iChapter) override;
+ void OnQueueNextItem() override;
+
+ void Announce(ANNOUNCEMENT::AnnouncementFlag flag,
+ const std::string& sender,
+ const std::string& message,
+ const CVariant& data) override;
+ void RegisterPythonPlayerCallBack(IPlayerCallback* pCallback);
+ void UnregisterPythonPlayerCallBack(IPlayerCallback* pCallback);
+ void RegisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback);
+ void UnregisterPythonMonitorCallBack(XBMCAddon::xbmc::Monitor* pCallback);
+ void OnSettingsChanged(const std::string& strings);
+ void OnScreensaverActivated();
+ void OnScreensaverDeactivated();
+ void OnDPMSActivated();
+ void OnDPMSDeactivated();
+ void OnScanStarted(const std::string& library);
+ void OnScanFinished(const std::string& library);
+ void OnCleanStarted(const std::string& library);
+ void OnCleanFinished(const std::string& library);
+ void OnNotification(const std::string& sender,
+ const std::string& method,
+ const std::string& data);
+
+ void Process() override;
+ void PulseGlobalEvent() override;
+ void Uninitialize() override;
+ bool OnScriptInitialized(ILanguageInvoker* invoker) override;
+ void OnScriptStarted(ILanguageInvoker* invoker) override;
+ void NotifyScriptAborting(ILanguageInvoker* invoker) override;
+ void OnExecutionEnded(ILanguageInvoker* invoker) override;
+ void OnScriptFinalized(ILanguageInvoker* invoker) override;
+ ILanguageInvoker* CreateInvoker() override;
+
+ bool WaitForEvent(CEvent& hEvent, unsigned int milliseconds);
+
+private:
+ static bool m_bInitialized; // whether global python runtime was already initialized
+
+ CCriticalSection m_critSection;
+ void* m_mainThreadState{nullptr};
+ int m_iDllScriptCounter{0}; // to keep track of the total scripts running that need the dll
+
+ //Vector with list of threads used for running scripts
+ PyList m_vecPyList;
+ PlayerCallbackList m_vecPlayerCallbackList;
+ MonitorCallbackList m_vecMonitorCallbackList;
+
+ // any global events that scripts should be using
+ CEvent m_globalEvent;
+
+ // in order to finalize and unload the python library, need to save all the extension libraries that are
+ // loaded by it and unload them first (not done by finalize)
+ PythonExtensionLibraries m_extensions;
+};
diff --git a/xbmc/interfaces/python/preamble.h b/xbmc/interfaces/python/preamble.h
new file mode 100644
index 0000000..080a4fb
--- /dev/null
+++ b/xbmc/interfaces/python/preamble.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#ifdef SWIGPYTHON
+
+#include <Python.h>
+
+#endif
diff --git a/xbmc/interfaces/python/pythreadstate.h b/xbmc/interfaces/python/pythreadstate.h
new file mode 100644
index 0000000..2cae5a7
--- /dev/null
+++ b/xbmc/interfaces/python/pythreadstate.h
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+
+//WARNING: since this will unlock/lock the python global interpreter lock,
+// it will not work recursively
+
+//this is basically a scoped version of a Py_BEGIN_ALLOW_THREADS .. Py_END_ALLOW_THREADS block
+class CPyThreadState
+{
+ public:
+ explicit CPyThreadState(bool save = true)
+ {
+ m_threadState = NULL;
+
+ if (save)
+ Save();
+ }
+
+ ~CPyThreadState()
+ {
+ Restore();
+ }
+
+ void Save()
+ {
+ if (!m_threadState)
+ m_threadState = PyEval_SaveThread(); //same as Py_BEGIN_ALLOW_THREADS
+ }
+
+ void Restore()
+ {
+ if (m_threadState)
+ {
+ PyEval_RestoreThread(m_threadState); //same as Py_END_ALLOW_THREADS
+ m_threadState = NULL;
+ }
+ }
+
+ private:
+ PyThreadState* m_threadState;
+};
+
+/**
+ * A std::unique_lock<CCriticalSection> that will relinquish the GIL during the time
+ * it takes to obtain the CriticalSection
+ */
+class GilSafeSingleLock : public CPyThreadState, public std::unique_lock<CCriticalSection>
+{
+public:
+ explicit GilSafeSingleLock(CCriticalSection& critSec)
+ : CPyThreadState(true), std::unique_lock<CCriticalSection>(critSec)
+ {
+ CPyThreadState::Restore();
+ }
+};
+
diff --git a/xbmc/interfaces/python/swig.cpp b/xbmc/interfaces/python/swig.cpp
new file mode 100644
index 0000000..0c49f87
--- /dev/null
+++ b/xbmc/interfaces/python/swig.cpp
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "swig.h"
+
+#include "LanguageHook.h"
+#include "interfaces/legacy/AddonString.h"
+#include "utils/StringUtils.h"
+
+#include <string>
+
+namespace PythonBindings
+{
+ TypeInfo::TypeInfo(const std::type_info& ti) : swigType(NULL), parentType(NULL), typeIndex(ti)
+ {
+ static PyTypeObject py_type_object_header = {
+ PyVarObject_HEAD_INIT(nullptr, 0) 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+#if PY_VERSION_HEX > 0x03080000
+ 0,
+ 0,
+#endif
+#if PY_VERSION_HEX < 0x03090000
+ 0,
+#endif
+#if PY_VERSION_HEX >= 0x030C00A1
+ 0,
+#endif
+ };
+
+ static int size = (long*)&(py_type_object_header.tp_name) - (long*)&py_type_object_header;
+ memcpy(&(this->pythonType), &py_type_object_header, size);
+ }
+
+ class PyObjectDecrementor
+ {
+ PyObject* obj;
+ public:
+ inline explicit PyObjectDecrementor(PyObject* pyobj) : obj(pyobj) {}
+ inline ~PyObjectDecrementor() { Py_XDECREF(obj); }
+
+ inline PyObject* get() { return obj; }
+ };
+
+ void PyXBMCGetUnicodeString(std::string& buf, PyObject* pObject, bool coerceToString,
+ const char* argumentName, const char* methodname)
+ {
+ // It's okay for a string to be "None". In this case the buf returned
+ // will be the emptyString.
+ if (pObject == Py_None)
+ {
+ buf = XBMCAddon::emptyString;
+ return;
+ }
+
+ //! @todo UTF-8: Does python use UTF-16?
+ //! Do we need to convert from the string charset to UTF-8
+ //! for non-unicode data?
+ if (PyUnicode_Check(pObject))
+ {
+ // Python unicode objects are UCS2 or UCS4 depending on compilation
+ // options, wchar_t is 16-bit or 32-bit depending on platform.
+ // Avoid the complexity by just letting python convert the string.
+
+ buf = PyUnicode_AsUTF8(pObject);
+ return;
+ }
+
+ if (PyBytes_Check(pObject)) // If pobject is of type Bytes
+ {
+ buf = PyBytes_AsString(pObject);
+ return;
+ }
+
+ // if we got here then we need to coerce the value to a string
+ if (coerceToString)
+ {
+ PyObjectDecrementor dec(PyObject_Str(pObject));
+ PyObject* pyStrCast = dec.get();
+ if (pyStrCast)
+ {
+ PyXBMCGetUnicodeString(buf,pyStrCast,false,argumentName,methodname);
+ return;
+ }
+ }
+
+ // Object is not a unicode or a normal string.
+ buf = "";
+ throw XBMCAddon::WrongTypeException("argument \"%s\" for method \"%s\" must be unicode or str", argumentName, methodname);
+ }
+
+ // need to compare the typestring
+ bool isParameterRightType(const char* passedType, const char* expectedType, const char* methodNamespacePrefix, bool tryReverse)
+ {
+ if (strcmp(expectedType,passedType) == 0)
+ return true;
+
+ // well now things are a bit more complicated. We need to see if the passed type
+ // is a subset of the overall type
+ std::string et(expectedType);
+ bool isPointer = (et[0] == 'p' && et[1] == '.');
+ std::string baseType(et,(isPointer ? 2 : 0)); // this may contain a namespace
+
+ std::string ns(methodNamespacePrefix);
+ // cut off trailing '::'
+ if (ns.size() > 2 && ns[ns.size() - 1] == ':' && ns[ns.size() - 2] == ':')
+ ns = ns.substr(0,ns.size()-2);
+
+ bool done = false;
+ while(! done)
+ {
+ done = true;
+
+ // now we need to see if the expected type can be munged
+ // into the passed type by tacking on the namespace of
+ // of the method.
+ std::string check(isPointer ? "p." : "");
+ check += ns;
+ check += "::";
+ check += baseType;
+
+ if (strcmp(check.c_str(),passedType) == 0)
+ return true;
+
+ // see if the namespace is nested.
+ int posOfScopeOp = ns.find("::");
+ if (posOfScopeOp >= 0)
+ {
+ done = false;
+ // cur off the outermost namespace
+ ns = ns.substr(posOfScopeOp + 2);
+ }
+ }
+
+ // so far we applied the namespace to the expected type. Now lets try
+ // the reverse if we haven't already.
+ if (tryReverse)
+ return isParameterRightType(expectedType, passedType, methodNamespacePrefix, false);
+
+ return false;
+ }
+
+ PythonToCppException::PythonToCppException() : XbmcCommons::UncheckedException(" ")
+ {
+ setClassname("PythonToCppException");
+
+ std::string msg;
+ std::string type, value, traceback;
+ if (!ParsePythonException(type, value, traceback))
+ UncheckedException::SetMessage("Strange: No Python exception occurred");
+ else
+ SetMessage(type, value, traceback);
+ }
+
+ PythonToCppException::PythonToCppException(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback) : XbmcCommons::UncheckedException(" ")
+ {
+ setClassname("PythonToCppException");
+
+ SetMessage(exceptionType, exceptionValue, exceptionTraceback);
+ }
+
+ bool PythonToCppException::ParsePythonException(std::string &exceptionType, std::string &exceptionValue, std::string &exceptionTraceback)
+ {
+ PyObject* exc_type;
+ PyObject* exc_value;
+ PyObject* exc_traceback;
+ PyObject* pystring = NULL;
+
+ PyErr_Fetch(&exc_type, &exc_value, &exc_traceback);
+ if (exc_type == NULL && exc_value == NULL && exc_traceback == NULL)
+ return false;
+
+ // See https://docs.python.org/3/c-api/exceptions.html#c.PyErr_NormalizeException
+ PyErr_NormalizeException(&exc_type, &exc_value, &exc_traceback);
+ if (exc_traceback != NULL) {
+ PyException_SetTraceback(exc_value, exc_traceback);
+ }
+
+ exceptionType.clear();
+ exceptionValue.clear();
+ exceptionTraceback.clear();
+
+ if (exc_type != NULL && (pystring = PyObject_Str(exc_type)) != NULL && PyUnicode_Check(pystring))
+ {
+ const char* str = PyUnicode_AsUTF8(pystring);
+ if (str != NULL)
+ exceptionType = str;
+
+ pystring = PyObject_Str(exc_value);
+ if (pystring != NULL)
+ {
+ str = PyUnicode_AsUTF8(pystring);
+ exceptionValue = str;
+ }
+
+ PyObject *tracebackModule = PyImport_ImportModule("traceback");
+ if (tracebackModule != NULL)
+ {
+ char method[] = "format_exception";
+ char format[] = "OOO";
+ PyObject *tbList = PyObject_CallMethod(tracebackModule, method, format, exc_type, exc_value == NULL ? Py_None : exc_value, exc_traceback == NULL ? Py_None : exc_traceback);
+
+ if (tbList)
+ {
+ PyObject* emptyString = PyUnicode_FromString("");
+ char method[] = "join";
+ char format[] = "O";
+ PyObject *strRetval = PyObject_CallMethod(emptyString, method, format, tbList);
+ Py_DECREF(emptyString);
+
+ if (strRetval)
+ {
+ str = PyUnicode_AsUTF8(strRetval);
+ if (str != NULL)
+ exceptionTraceback = str;
+ Py_DECREF(strRetval);
+ }
+ Py_DECREF(tbList);
+ }
+ Py_DECREF(tracebackModule);
+
+ }
+ }
+
+ Py_XDECREF(exc_type);
+ Py_XDECREF(exc_value);
+ Py_XDECREF(exc_traceback);
+ Py_XDECREF(pystring);
+
+ return true;
+ }
+
+ void PythonToCppException::SetMessage(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback)
+ {
+ std::string msg = "-->Python callback/script returned the following error<--\n";
+ msg += " - NOTE: IGNORING THIS CAN LEAD TO MEMORY LEAKS!\n";
+
+ if (!exceptionType.empty())
+ {
+ msg += StringUtils::Format("Error Type: {}\n", exceptionType);
+
+ if (!exceptionValue.empty())
+ msg += StringUtils::Format("Error Contents: {}\n", exceptionValue);
+
+ if (!exceptionTraceback.empty())
+ msg += exceptionTraceback;
+
+ msg += "-->End of Python script error report<--\n";
+ }
+ else
+ msg += "<unknown exception type>";
+
+ UncheckedException::SetMessage("%s", msg.c_str());
+ }
+
+ XBMCAddon::AddonClass* doretrieveApiInstance(const PyHolder* pythonObj, const TypeInfo* typeInfo, const char* expectedType,
+ const char* methodNamespacePrefix, const char* methodNameForErrorString)
+ {
+ if (pythonObj->magicNumber != XBMC_PYTHON_TYPE_MAGIC_NUMBER)
+ throw XBMCAddon::WrongTypeException("Non api type passed to \"%s\" in place of the expected type \"%s.\"",
+ methodNameForErrorString, expectedType);
+ if (!isParameterRightType(typeInfo->swigType,expectedType,methodNamespacePrefix))
+ {
+ // maybe it's a child class
+ if (typeInfo->parentType)
+ return doretrieveApiInstance(pythonObj, typeInfo->parentType,expectedType,
+ methodNamespacePrefix, methodNameForErrorString);
+ else
+ throw XBMCAddon::WrongTypeException("Incorrect type passed to \"%s\", was expecting a \"%s\" but received a \"%s\"",
+ methodNameForErrorString,expectedType,typeInfo->swigType);
+ }
+ return const_cast<XBMCAddon::AddonClass*>(pythonObj->pSelf);
+ }
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class constructor being returned from the generated code to Python
+ */
+ void prepareForReturn(XBMCAddon::AddonClass* c)
+ {
+ XBMC_TRACE;
+ if(c) {
+ c->Acquire();
+ PyThreadState* state = PyThreadState_Get();
+ XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp)->RegisterAddonClassInstance(c);
+ }
+ }
+
+ static bool handleInterpRegistrationForClean(XBMCAddon::AddonClass* c)
+ {
+ XBMC_TRACE;
+ if(c){
+ XBMCAddon::AddonClass::Ref<XBMCAddon::Python::PythonLanguageHook> lh =
+ XBMCAddon::AddonClass::Ref<XBMCAddon::AddonClass>(c->GetLanguageHook());
+
+ if (lh.isNotNull())
+ {
+ lh->UnregisterAddonClassInstance(c);
+ return true;
+ }
+ else
+ {
+ PyThreadState* state = PyThreadState_Get();
+ lh = XBMCAddon::Python::PythonLanguageHook::GetIfExists(state->interp);
+ if (lh.isNotNull()) lh->UnregisterAddonClassInstance(c);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class destructor being dealloc-ed from the generated code from Python
+ */
+ void cleanForDealloc(XBMCAddon::AddonClass* c)
+ {
+ XBMC_TRACE;
+ if (handleInterpRegistrationForClean(c))
+ c->Release();
+ }
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class destructor being dealloc-ed from the generated code from Python
+ *
+ * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be
+ * called on destruction but cannot be called from the destructor.
+ * This overrides the default cleanForDealloc to resolve that.
+ */
+ void cleanForDealloc(XBMCAddon::xbmcgui::Window* c)
+ {
+ XBMC_TRACE;
+ if (handleInterpRegistrationForClean(c))
+ {
+ c->dispose();
+ c->Release();
+ }
+ }
+
+ /**
+ * This method allows for conversion of the native api Type to the Python type.
+ *
+ * When this form of the call is used (and pytype isn't NULL) then the
+ * passed type is used in the instance. This is for classes that extend API
+ * classes in python. The type passed may not be the same type that's stored
+ * in the class metadata of the AddonClass of which 'api' is an instance,
+ * it can be a subclass in python.
+ *
+ * if pytype is NULL then the type is inferred using the class metadata
+ * stored in the AddonClass instance 'api'.
+ */
+ PyObject* makePythonInstance(XBMCAddon::AddonClass* api, PyTypeObject* pytype, bool incrementRefCount)
+ {
+ // null api types result in Py_None
+ if (!api)
+ {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+
+ // retrieve the TypeInfo from the api class
+ const TypeInfo* typeInfo = getTypeInfoForInstance(api);
+ PyTypeObject* typeObj = pytype == NULL ? const_cast<PyTypeObject*>(&(typeInfo->pythonType)) : pytype;
+
+ PyHolder* self = reinterpret_cast<PyHolder*>(typeObj->tp_alloc(typeObj,0));
+ if (!self) return NULL;
+ self->magicNumber = XBMC_PYTHON_TYPE_MAGIC_NUMBER;
+ self->typeInfo = typeInfo;
+ self->pSelf = api;
+ if (incrementRefCount)
+ Py_INCREF((PyObject*)self);
+ return (PyObject*)self;
+ }
+
+ std::map<std::type_index, const TypeInfo*> typeInfoLookup;
+
+ void registerAddonClassTypeInformation(const TypeInfo* classInfo)
+ {
+ typeInfoLookup[classInfo->typeIndex] = classInfo;
+ }
+
+ const TypeInfo* getTypeInfoForInstance(XBMCAddon::AddonClass* obj)
+ {
+ std::type_index ti(typeid(*obj));
+ return typeInfoLookup[ti];
+ }
+
+ int dummy_tp_init(PyObject* self, PyObject* args, PyObject* kwds)
+ {
+ return 0;
+ }
+}
+
diff --git a/xbmc/interfaces/python/swig.h b/xbmc/interfaces/python/swig.h
new file mode 100644
index 0000000..353d968
--- /dev/null
+++ b/xbmc/interfaces/python/swig.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include "interfaces/legacy/AddonClass.h"
+#include "interfaces/legacy/Exception.h"
+#include "interfaces/legacy/Window.h"
+
+#include <stdint.h>
+#include <string>
+#include <typeindex>
+
+#include <Python.h>
+
+namespace PythonBindings
+{
+ /**
+ * This call will convert the python object passed to a string. The object
+ * passed must be a python str or unicode object unless coerceToString is
+ * true. If coerceToString is true then the type must be castable to a string
+ * using the python call str(pObject).
+ *
+ * This method will handle a 'None' that's passed in. If 'None' is passed then
+ * the resulting buf will contain the value of XBMCAddon::emptyString (which
+ * is simply a std::string instantiated with the default constructor.
+ */
+ void PyXBMCGetUnicodeString(std::string& buf, PyObject* pObject, bool coerceToString = false,
+ const char* pos = "unknown",
+ const char* methodname = "unknown");
+
+ struct TypeInfo
+ {
+ const char* swigType;
+ TypeInfo* parentType;
+ PyTypeObject pythonType;
+ const std::type_index typeIndex;
+
+ explicit TypeInfo(const std::type_info& ti);
+ };
+
+ // This will hold the pointer to the api type, whether known or unknown
+ struct PyHolder
+ {
+ PyObject_HEAD
+ int32_t magicNumber;
+ const TypeInfo* typeInfo;
+ XBMCAddon::AddonClass* pSelf;
+ };
+
+#define XBMC_PYTHON_TYPE_MAGIC_NUMBER 0x58626D63
+
+ /**
+ * This method retrieves the pointer from the PyHolder. The return value should
+ * be cast to the appropriate type.
+ *
+ * Since the calls to this are generated there's no NULL pointer checks
+ */
+ inline XBMCAddon::AddonClass* retrieveApiInstance(PyObject* pythonObj, const TypeInfo* typeToCheck,
+ const char* methodNameForErrorString,
+ const char* typenameForErrorString)
+ {
+ if (pythonObj == NULL || pythonObj == Py_None)
+ return NULL;
+ if (reinterpret_cast<PyHolder*>(pythonObj)->magicNumber != XBMC_PYTHON_TYPE_MAGIC_NUMBER || !PyObject_TypeCheck(pythonObj, const_cast<PyTypeObject*>((&(typeToCheck->pythonType)))))
+ throw XBMCAddon::WrongTypeException("Incorrect type passed to \"%s\", was expecting a \"%s\".",methodNameForErrorString,typenameForErrorString);
+ return reinterpret_cast<PyHolder*>(pythonObj)->pSelf;
+ }
+
+ bool isParameterRightType(const char* passedType, const char* expectedType, const char* methodNamespacePrefix, bool tryReverse = true);
+
+ XBMCAddon::AddonClass* doretrieveApiInstance(const PyHolder* pythonObj, const TypeInfo* typeInfo, const char* expectedType,
+ const char* methodNamespacePrefix, const char* methodNameForErrorString);
+
+ /**
+ * This method retrieves the pointer from the PyHolder. The return value should
+ * be cast to the appropriate type.
+ *
+ * Since the calls to this are generated there's no NULL pointer checks
+ *
+ * This method will return NULL if either the pythonObj is NULL or the
+ * pythonObj is Py_None.
+ */
+ inline XBMCAddon::AddonClass* retrieveApiInstance(const PyObject* pythonObj, const char* expectedType, const char* methodNamespacePrefix,
+ const char* methodNameForErrorString)
+ {
+ return (pythonObj == NULL || pythonObj == Py_None) ? NULL :
+ doretrieveApiInstance(reinterpret_cast<const PyHolder*>(pythonObj),reinterpret_cast<const PyHolder*>(pythonObj)->typeInfo, expectedType, methodNamespacePrefix, methodNameForErrorString);
+ }
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class constructor being returned from the generated code to Python
+ */
+ void prepareForReturn(XBMCAddon::AddonClass* c);
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class destructor being dealloc-ed from the generated code from Python
+ */
+ void cleanForDealloc(XBMCAddon::AddonClass* c);
+
+ /**
+ * This method is a helper for the generated API. It's called prior to any API
+ * class destructor being dealloc-ed from the generated code from Python
+ *
+ * There is a Catch-22 in the destruction of a Window. 'dispose' needs to be
+ * called on destruction but cannot be called from the destructor.
+ * This overrides the default cleanForDealloc to resolve that.
+ */
+ void cleanForDealloc(XBMCAddon::xbmcgui::Window* c);
+
+ /**
+ * This method allows for conversion of the native api Type to the Python type.
+ *
+ * When this form of the call is used (and pythonType isn't NULL) then the
+ * passed type is used in the instance. This is for classes that extend API
+ * classes in python. The type passed may not be the same type that's stored
+ * in the class metadata of the AddonClass of which 'api' is an instance,
+ * it can be a subclass in python.
+ *
+ * if pythonType is NULL then the type is inferred using the class metadata
+ * stored in the AddonClass instance 'api'.
+ */
+ PyObject* makePythonInstance(XBMCAddon::AddonClass* api, PyTypeObject* pythonType, bool incrementRefCount);
+
+ /**
+ * This method allows for conversion of the native api Type to the Python type.
+ *
+ * When this form of the call is used then the python type constructed will be the
+ * type given by the class metadata in the AddonClass instance 'api'.
+ *
+ * This is just a helper inline to call the other makePythonInstance with NULL as
+ * the pythonType.
+ */
+ inline PyObject* makePythonInstance(XBMCAddon::AddonClass* api, bool incrementRefCount)
+ {
+ return makePythonInstance(api,NULL,incrementRefCount);
+ }
+
+ void registerAddonClassTypeInformation(const TypeInfo* classInfo);
+ const TypeInfo* getTypeInfoForInstance(XBMCAddon::AddonClass* obj);
+
+ int dummy_tp_init(PyObject* self, PyObject* args, PyObject* kwds);
+
+ class Director
+ {
+ protected:
+ PyObject* self;
+ public:
+ inline Director() : self(NULL) {}
+ inline void setPyObjectForDirector(PyObject* pyargself) { self = pyargself; }
+ };
+
+ /**
+ * This exception is thrown from Director calls that call into python when the
+ * Python error is
+ */
+ class PythonToCppException : public XbmcCommons::UncheckedException
+ {
+ public:
+ /**
+ * Assuming a PyErr_Occurred, this will fill the exception message with all
+ * of the appropriate information including the traceback if it can be
+ * obtained. It will also clear the python message.
+ */
+ PythonToCppException();
+ PythonToCppException(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback);
+
+ static bool ParsePythonException(std::string &exceptionType, std::string &exceptionValue, std::string &exceptionTraceback);
+
+ protected:
+ void SetMessage(const std::string &exceptionType, const std::string &exceptionValue, const std::string &exceptionTraceback);
+ };
+
+ template<class T> struct PythonCompare
+ {
+ static inline int compare(PyObject* obj1, PyObject* obj2, const char* swigType, const char* methodNamespacePrefix, const char* methodNameForErrorString)
+ {
+ XBMC_TRACE;
+ try
+ {
+ T* o1 = (T*)retrieveApiInstance(obj1, swigType, methodNamespacePrefix, methodNameForErrorString);
+ T* o2 = (T*)retrieveApiInstance(obj2, swigType, methodNamespacePrefix, methodNameForErrorString);
+
+ return ((*o1) < (*o2) ? -1 :
+ ((*o1) > (*o2) ? 1 : 0));
+ }
+ catch (const XBMCAddon::WrongTypeException& e)
+ {
+ CLog::Log(LOGERROR, "EXCEPTION: {}", e.GetExMessage());
+ PyErr_SetString(PyExc_RuntimeError, e.GetExMessage());
+ }
+ return -1;
+ }
+ };
+}
diff --git a/xbmc/interfaces/python/test/CMakeLists.txt b/xbmc/interfaces/python/test/CMakeLists.txt
new file mode 100644
index 0000000..ec38a51
--- /dev/null
+++ b/xbmc/interfaces/python/test/CMakeLists.txt
@@ -0,0 +1,5 @@
+if(PYTHON_FOUND)
+ set(SOURCES TestSwig.cpp)
+
+ core_add_test_library(python_test)
+endif()
diff --git a/xbmc/interfaces/python/test/TestSwig.cpp b/xbmc/interfaces/python/test/TestSwig.cpp
new file mode 100644
index 0000000..463bb98
--- /dev/null
+++ b/xbmc/interfaces/python/test/TestSwig.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "../swig.h"
+
+#include <gtest/gtest.h>
+
+using namespace PythonBindings;
+
+TEST(TestSwig, TypeConversion)
+{
+ EXPECT_TRUE(isParameterRightType("p.XBMCAddon::xbmcgui::ListItem","p.XBMCAddon::xbmcgui::ListItem","XBMCAddon::xbmc::"));
+ EXPECT_TRUE(isParameterRightType("p.XBMCAddon::xbmc::PlayList","p.PlayList","XBMCAddon::xbmc::"));
+ EXPECT_TRUE(isParameterRightType("p.PlayList","p.XBMCAddon::xbmc::PlayList","XBMCAddon::xbmc::"));
+}
+
diff --git a/xbmc/interfaces/python/typemaps/python.Alternative.intm b/xbmc/interfaces/python/typemaps/python.Alternative.intm
new file mode 100644
index 0000000..12a5e65
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.Alternative.intm
@@ -0,0 +1,43 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ boolean ispointer = swigTypeParser.SwigType_ispointer(ltype)
+ String accessor = ispointer ? '->' : '.'
+ int seq = sequence.increment()
+ altAccess = [ 'former', 'later' ]
+ altSwitch = [ 'first', 'second' ]
+
+ List types = swigTypeParser.SwigType_templateparmlist(ltype)
+%>
+ {
+ // we need to check the parameter type and see if it matches
+ PyObject *pyentry_${seq} = ${slarg};
+ try
+ {
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(types[0]))} entry0_${seq};
+ ${helper.getInConversion(types[0], 'entry0' + '_' + seq, 'pyentry' + '_' + seq, method,
+ [ 'sequence' : sequence ])}
+ ${api}${accessor}${altAccess[0]}() = entry0_${seq};
+ }
+ catch (const XBMCAddon::WrongTypeException&)
+ {
+ try
+ {
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(types[1]))} entry1_${seq};
+ ${helper.getInConversion(types[1], 'entry1' + '_' + seq, 'pyentry' + '_' + seq, method,
+ [ 'sequence' : sequence ])}
+ ${api}${accessor}${altAccess[1]}() = entry1_${seq};
+ }
+ catch (const XBMCAddon::WrongTypeException&)
+ {
+ throw XBMCAddon::WrongTypeException("Failed to convert to input type to either a "
+ "${swigTypeParser.SwigType_ltype(types[0])} or a "
+ "${swigTypeParser.SwigType_ltype(types[1])}" );
+ }
+ }
+ } \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.Alternative.outtm b/xbmc/interfaces/python/typemaps/python.Alternative.outtm
new file mode 100644
index 0000000..31f4205
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.Alternative.outtm
@@ -0,0 +1,34 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List types = swigTypeParser.SwigType_templateparmlist(type)
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ int seq = sequence.increment()
+ String accessor = ispointer ? '->' : '.'
+ altAccess = [ 'former', 'later' ]
+ altSwitch = [ 'first', 'second' ]
+%>
+ WhichAlternative pos = ${api}${accessor}which();
+
+ if (<%if (ispointer) { %>${api} != NULL && <%}%>pos != XBMCAddon::none)
+ { <%
+ types.eachWithIndex { curType, entryIndex ->
+%>
+ if (pos == XBMCAddon::${altSwitch[entryIndex]})
+ {
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_lrtype(curType))} entry${seq} = ${api}${accessor}${altAccess[entryIndex]}();
+ {
+ ${helper.getOutConversion(curType,result,method,[ 'api' : 'entry' + seq, 'sequence' : sequence ])}
+ }
+ }
+<%
+ }
+%>
+ }
+ else
+ ${result} = Py_None; \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.Tuple.intm b/xbmc/interfaces/python/typemaps/python.Tuple.intm
new file mode 100644
index 0000000..c426856
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.Tuple.intm
@@ -0,0 +1,35 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List types = swigTypeParser.SwigType_templateparmlist(ltype)
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ String accessor = ispointer ? '->' : '.'
+ int seq = sequence.increment()
+ tupleAccess = [ 'first', 'second', 'third', 'fourth' ]
+%>
+ if(${slarg})
+ {
+ bool isTuple = PyObject_TypeCheck(${slarg},&PyTuple_Type);
+ if (!isTuple && !PyObject_TypeCheck(${slarg},&PyList_Type))
+ throw WrongTypeException("The parameter \"${api}\" must be either a Tuple or a List.");
+ Py_ssize_t vecSize = (isTuple ? PyTuple_Size(${slarg}) : PyList_Size(${slarg}));
+<%
+ types.eachWithIndex { curType, entryIndex ->
+%>
+ if (vecSize > ${entryIndex})
+ {
+ PyObject *pyentry${entryIndex}_${seq} = NULL;
+ pyentry${entryIndex}_${seq} = (isTuple ? PyTuple_GetItem(${slarg}, ${entryIndex}) : PyList_GetItem(${slarg}, ${entryIndex}));
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(curType))} entry${entryIndex}_${seq};
+ ${helper.getInConversion(curType, 'entry' + entryIndex + '_' + seq, 'pyentry' + entryIndex + '_' + seq, method,[ 'sequence' : sequence ])}
+ ${api}${accessor}${tupleAccess[entryIndex]}() = entry${entryIndex}_${seq};
+ }
+<%
+ }
+%>
+ }
diff --git a/xbmc/interfaces/python/typemaps/python.Tuple.outtm b/xbmc/interfaces/python/typemaps/python.Tuple.outtm
new file mode 100644
index 0000000..18655fe
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.Tuple.outtm
@@ -0,0 +1,39 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List types = swigTypeParser.SwigType_templateparmlist(type)
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ int seq = sequence.increment()
+ String accessor = ispointer ? '->' : '.'
+ tupleAccess = [ 'first', 'second', 'third', 'fourth' ]
+%>
+ int vecSize = ${api}${accessor}GetNumValuesSet();
+ ${result} = PyTuple_New(vecSize);
+<%
+ if (ispointer)
+ {
+%>
+ if (${api} != NULL)
+<% } // this ends the if (ispointer)
+%> {
+ PyObject* pyentry${seq}; <%
+ types.eachWithIndex { curType, entryIndex ->
+%>
+
+ if (vecSize > ${entryIndex})
+ {
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_lrtype(curType))} entry${seq} = ${api}${accessor}${tupleAccess[entryIndex]}();
+ {
+ ${helper.getOutConversion(curType,'result',method,[ 'result' : 'pyentry' + seq, 'api' : 'entry' + seq, 'sequence' : sequence ])}
+ }
+ PyTuple_SetItem(${result}, ${entryIndex}, pyentry${seq});
+ }
+<%
+ }
+%>
+ } \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.buffer.intm b/xbmc/interfaces/python/typemaps/python.buffer.intm
new file mode 100644
index 0000000..f074b2b
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.buffer.intm
@@ -0,0 +1,36 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+%>
+ if (PyUnicode_Check(${slarg}))
+ {
+ Py_ssize_t pysize;
+ const char* str = PyUnicode_AsUTF8AndSize(${slarg}, &pysize);
+ size_t size = static_cast<size_t>(pysize);
+ ${api}.allocate(size);
+ ${api}.put(str, size);
+ ${api}.flip(); // prepare the buffer for reading from
+ }
+ else if (PyBytes_Check(${slarg}))
+ {
+ Py_ssize_t pysize = PyBytes_GET_SIZE(${slarg});
+ const char* str = PyBytes_AS_STRING(${slarg});
+ size_t size = static_cast<size_t>(pysize);
+ ${api}.allocate(size);
+ ${api}.put(str, size);
+ ${api}.flip(); // prepare the buffer for reading from
+ }
+ else if (PyByteArray_Check(${slarg}))
+ {
+ size_t size = PyByteArray_Size(${slarg});
+ ${api}.allocate(size);
+ ${api}.put(PyByteArray_AsString(${slarg}),size);
+ ${api}.flip(); // prepare the buffer for reading from
+ }
+ else
+ throw XBMCAddon::WrongTypeException("argument \"%s\" for \"%s\" must be a string, bytes or a bytearray", "${api}", "${method.@name}"); \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.buffer.outtm b/xbmc/interfaces/python/typemaps/python.buffer.outtm
new file mode 100644
index 0000000..8e38d81
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.buffer.outtm
@@ -0,0 +1,11 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ String accessor = ispointer ? '->' : '.'
+%>${result} = PyByteArray_FromStringAndSize((char*)${api}${accessor}curPosition(),${api}${accessor}remaining()); \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.dict.intm b/xbmc/interfaces/python/typemaps/python.dict.intm
new file mode 100644
index 0000000..ea3c78c
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.dict.intm
@@ -0,0 +1,23 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype)
+ valtype = templateArgs[0]
+%>
+ {
+ PyObject *pykey, *pyvalue;
+ Py_ssize_t pos = 0;
+ while(PyDict_Next(${slarg}, &pos, &pykey, &pyvalue))
+ {
+ std::string key;
+ PyXBMCGetUnicodeString(key,pykey,false,"${api}","${method.@name}");
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(valtype))} value;
+ ${helper.getInConversion(valtype, 'value', 'pyvalue' ,method)}
+ ${api}.emplace(std::move(key), std::move(value));
+ }
+ } \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.map.intm b/xbmc/interfaces/python/typemaps/python.map.intm
new file mode 100644
index 0000000..095d23b
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.map.intm
@@ -0,0 +1,24 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype)
+ keytype = templateArgs[0]
+ valtype = templateArgs[1]
+%>
+ {
+ PyObject *pykey, *pyvalue;
+ Py_ssize_t pos = 0;
+ while(PyDict_Next(${slarg}, &pos, &pykey, &pyvalue))
+ {
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(keytype))} key;
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(valtype))} value;
+ ${helper.getInConversion(keytype, 'key', 'pykey', method)}
+ ${helper.getInConversion(valtype, 'value', 'pyvalue' ,method)}
+ ${api}.emplace(std::move(key), std::move(value));
+ }
+ } \ No newline at end of file
diff --git a/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm b/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm
new file mode 100644
index 0000000..0d7fa31
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.smart_ptr.outtm
@@ -0,0 +1,14 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ itype = swigTypeParser.SwigType_templateparmlist(type)[0]
+ pointertype = swigTypeParser.SwigType_makepointer(itype)
+ int seq = sequence.increment()
+%>
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(pointertype))} entry${seq} = ${api}.get();
+ ${helper.getOutConversion(pointertype,'result',method,[ 'api' : 'entry' + seq, 'sequence' : sequence ])}
diff --git a/xbmc/interfaces/python/typemaps/python.string.outtm b/xbmc/interfaces/python/typemaps/python.string.outtm
new file mode 100644
index 0000000..f9eb068
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.string.outtm
@@ -0,0 +1,11 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+%>${result} = <%
+ if(method.@feature_python_strictUnicode) { %>PyUnicode_DecodeUTF8(${api}.c_str(),${api}.size(),"strict");<% }
+ else { %>PyUnicode_DecodeUTF8(${api}.c_str(),${api}.size(),"surrogateescape");<% } %>
diff --git a/xbmc/interfaces/python/typemaps/python.vector.intm b/xbmc/interfaces/python/typemaps/python.vector.intm
new file mode 100644
index 0000000..6bc71ec
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.vector.intm
@@ -0,0 +1,36 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List templateArgs = swigTypeParser.SwigType_templateparmlist(ltype)
+ vectype = templateArgs[0]
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ String accessor = ispointer ? '->' : '.'
+ int seq = sequence.increment()
+%>
+ if (${slarg})
+ {
+ bool isTuple = PyObject_TypeCheck(${slarg},&PyTuple_Type);
+ if (!isTuple && !PyObject_TypeCheck(${slarg},&PyList_Type))
+ throw WrongTypeException("The parameter \"${api}\" must be either a Tuple or a List.");
+
+ <% if (ispointer) print("${api} = new std::vector<${swigTypeParser.SwigType_str(vectype)}>();") %>
+ PyObject *pyentry${seq} = NULL;
+ Py_ssize_t vecSize = (isTuple ? PyTuple_Size(${slarg}) : PyList_Size(${slarg}));
+ ${api}${accessor}reserve(vecSize);
+ for(Py_ssize_t i = 0; i < vecSize; i++)
+ {
+ pyentry${seq} = (isTuple ? PyTuple_GetItem(${slarg}, i) : PyList_GetItem(${slarg}, i));
+ ${swigTypeParser.SwigType_str(swigTypeParser.SwigType_ltype(vectype))} entry${seq};
+ ${helper.getInConversion(vectype, 'entry' + seq, 'pyentry' + seq, method,
+ [ 'type' : vectype,
+ 'ltype' : swigTypeParser.SwigType_ltype(vectype),
+ 'sequence' : sequence
+ ])}
+ ${api}${accessor}push_back(<% if (swigTypeParser.SwigType_ispointer(vectype) || vectype in ["bool", "double", "int"]) { %>entry${seq}<% } else { %>std::move(entry${seq})<% } %>);
+ }
+ }
diff --git a/xbmc/interfaces/python/typemaps/python.vector.outtm b/xbmc/interfaces/python/typemaps/python.vector.outtm
new file mode 100644
index 0000000..c1c4c79
--- /dev/null
+++ b/xbmc/interfaces/python/typemaps/python.vector.outtm
@@ -0,0 +1,35 @@
+<%
+/*
+ * Copyright (C) 2005-2018 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+ List templateArgs = swigTypeParser.SwigType_templateparmlist(type)
+ vectype = templateArgs[0]
+ boolean ispointer = swigTypeParser.SwigType_ispointer(type)
+ String accessor = ispointer ? '->' : '.'
+ seq = sequence.increment()
+
+ if (ispointer)
+ {
+%>
+ if (${api} != NULL)
+ {
+<% } %>
+ ${result} = PyList_New(0);
+
+ for (std::vector<${swigTypeParser.SwigType_str(vectype)}>::iterator iter = ${api}${accessor}begin(); iter != ${api}${accessor}end(); ++iter)
+ {
+ PyObject* pyentry${seq};
+ ${helper.getOutConversion(vectype,'result',method,[ 'result' : 'pyentry' + seq, 'api' : '(*iter)', 'sequence' : sequence ])}
+ PyList_Append(${result}, pyentry${seq});
+ Py_DECREF(pyentry${seq});
+ }
+<%
+ if (ispointer)
+ {
+%>
+ }
+<% } %>