mirror of
https://github.com/ModOrganizer2/modorganizer-plugin_python.git
synced 2026-07-27 14:03:33 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c922b97578 |
+4
-12
@@ -7,22 +7,14 @@ environment:
|
||||
build:
|
||||
parallel: true
|
||||
build_script:
|
||||
- pwsh: >-
|
||||
$ErrorActionPreference = 'Stop'
|
||||
- cmd: >-
|
||||
git clone --depth=1 --branch=%APPVEYOR_REPO_BRANCH% https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella 2> $null
|
||||
|
||||
git clone --depth=1 --no-single-branch https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella
|
||||
|
||||
New-Item -ItemType Directory -Path c:\projects\modorganizer-build
|
||||
mkdir c:\projects\modorganizer-build -type directory
|
||||
|
||||
cd c:\projects\modorganizer-umbrella
|
||||
|
||||
($env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH -eq $null) ? ($branch = $env:APPVEYOR_REPO_BRANCH) : ($branch = $env:APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH)
|
||||
|
||||
git checkout $(git show-ref --verify --quiet refs/remotes/origin/${branch} || echo '-b') ${branch}
|
||||
|
||||
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True ${env:APPVEYOR_PROJECT_NAME}
|
||||
|
||||
if($LastExitCode -ne 0) { $host.SetShouldExit($LastExitCode ) }
|
||||
C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True %APPVEYOR_PROJECT_NAME%
|
||||
artifacts:
|
||||
- path: build\src\proxy\plugin_python.dll
|
||||
name: plugin_python_dll
|
||||
|
||||
+218
-108
@@ -26,86 +26,156 @@ along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||
#include <QWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QCoreApplication>
|
||||
#include "log.h"
|
||||
#include "resource.h"
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
|
||||
const char *ProxyPython::s_DownloadPythonURL = "http://www.python.org/download/releases/";
|
||||
|
||||
|
||||
HMODULE GetOwnModuleHandle()
|
||||
{
|
||||
HMODULE hMod = nullptr;
|
||||
GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
|
||||
reinterpret_cast<LPCWSTR>(&GetOwnModuleHandle), &hMod);
|
||||
|
||||
return hMod;
|
||||
}
|
||||
|
||||
|
||||
QString ExtractResource(WORD resourceID, const QString &szFilename)
|
||||
{
|
||||
HMODULE mod = GetOwnModuleHandle();
|
||||
|
||||
HRSRC hResource = FindResourceW(mod, MAKEINTRESOURCE(resourceID), L"BINARY");
|
||||
if (hResource == nullptr) {
|
||||
throw MyException("embedded dll not available: " + windowsErrorString(::GetLastError()));
|
||||
}
|
||||
|
||||
HGLOBAL hFileResource = LoadResource(mod, hResource);
|
||||
if (hFileResource == nullptr) {
|
||||
throw MyException("failed to load embedded dll resource: " + windowsErrorString(::GetLastError()));
|
||||
}
|
||||
|
||||
LPVOID lpFile = LockResource(hFileResource);
|
||||
if (lpFile == nullptr) {
|
||||
throw MyException(QString("failed to lock resource: %1").arg(windowsErrorString(::GetLastError())));
|
||||
}
|
||||
|
||||
DWORD dwSize = SizeofResource(mod, hResource);
|
||||
|
||||
QString outFile = QDir::tempPath() + "/" + szFilename;
|
||||
|
||||
HANDLE hFile = CreateFileW(outFile.toStdWString().c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
if (::GetLastError() == ERROR_SHARING_VIOLATION) {
|
||||
// dll exists and is opened by another instance of MO, shouldn't be outdated then...
|
||||
return outFile;
|
||||
} else {
|
||||
throw MyException(QString("failed to open python runner: %1").arg(windowsErrorString(::GetLastError())));
|
||||
}
|
||||
}
|
||||
HANDLE hFileMap = CreateFileMapping(hFile, nullptr, PAGE_READWRITE, 0, dwSize, nullptr);
|
||||
if (hFileMap == NULL) {
|
||||
throw MyException(QString("failed to map python runner: %1").arg(windowsErrorString(::GetLastError())));
|
||||
}
|
||||
LPVOID lpAddress = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);
|
||||
if (lpAddress == nullptr) {
|
||||
throw MyException(QString("failed to map view of file: %1").arg(windowsErrorString(::GetLastError())));
|
||||
}
|
||||
|
||||
CopyMemory(lpAddress, lpFile, dwSize);
|
||||
|
||||
UnmapViewOfFile(lpAddress);
|
||||
|
||||
CloseHandle(hFileMap);
|
||||
::FlushFileBuffers(hFile);
|
||||
CloseHandle(hFile);
|
||||
|
||||
return outFile;
|
||||
}
|
||||
|
||||
|
||||
ProxyPython::ProxyPython()
|
||||
: m_MOInfo{ nullptr },
|
||||
m_RunnerLib{ nullptr },
|
||||
m_Runner{ nullptr },
|
||||
m_LoadFailure(FailureType::NONE)
|
||||
: m_MOInfo(nullptr), m_Runner(nullptr), m_LoadFailure(FAIL_NOTINIT)
|
||||
{
|
||||
}
|
||||
|
||||
ProxyPython::~ProxyPython()
|
||||
{
|
||||
if (!m_TempRunnerFile.isEmpty()) {
|
||||
::FreeLibrary(m_RunnerLib);
|
||||
QFile(m_TempRunnerFile).remove();
|
||||
}
|
||||
}
|
||||
|
||||
typedef IPythonRunner* (*CreatePythonRunner_func)(const MOBase::IOrganizer *moInfo, const QString &pythonPath);
|
||||
|
||||
|
||||
bool ProxyPython::init(IOrganizer *moInfo)
|
||||
{
|
||||
using CreatePythonRunner_func = IPythonRunner * (*)();
|
||||
|
||||
m_MOInfo = moInfo;
|
||||
|
||||
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
|
||||
if (!m_MOInfo->pluginSetting(name(), "enabled").toBool()) {
|
||||
m_LoadFailure = FAIL_NONE;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_LoadFailure = FAIL_OTHER;
|
||||
if (QCoreApplication::applicationDirPath().contains(';')) {
|
||||
m_LoadFailure = FailureType::SEMICOLON;
|
||||
m_LoadFailure = FAIL_SEMICOLON;
|
||||
return true;
|
||||
}
|
||||
|
||||
// load the pythonrunner library
|
||||
m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(
|
||||
IOrganizer::getPluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
|
||||
QString pythonPath = m_MOInfo->pluginSetting(name(), "python_dir").toString();
|
||||
|
||||
if (!m_RunnerLib) {
|
||||
DWORD error = ::GetLastError();
|
||||
log::error("failed to load python runner ({}): {}", qUtf8Printable(windowsErrorString(error)));
|
||||
if (error == ERROR_MOD_NOT_FOUND) {
|
||||
m_LoadFailure = FailureType::DLL_NOT_FOUND;
|
||||
}
|
||||
else {
|
||||
m_LoadFailure = FailureType::INVALID_DLL;
|
||||
}
|
||||
if (!pythonPath.isEmpty() && !QFile::exists(pythonPath + "/python.exe")) {
|
||||
m_LoadFailure = FAIL_WRONGPYTHONPATH;
|
||||
return true;
|
||||
}
|
||||
|
||||
const CreatePythonRunner_func createPythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
|
||||
if (!createPythonRunner) {
|
||||
m_LoadFailure = FailureType::INVALID_DLL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (m_MOInfo && m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
|
||||
m_LoadFailure = FailureType::INITIALIZATION;
|
||||
if (QMessageBox::question(parentWidget(), tr("Python Initialization failed"),
|
||||
tr("On a previous start the Python Plugin failed to initialize.\n"
|
||||
"Do you want to try initializing python again (at the risk of another crash)?\n"
|
||||
"Suggestion: Select \"no\", and click the warning sign for further help. Afterwards you have to re-enable the python plugin."),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
||||
|
||||
// we force enabled here (note: this is a persistent settings since MO2 2.4 or something), plugin
|
||||
// usually should not handle enabled/disabled themselves but this is a base plugin so...
|
||||
m_MOInfo->setPersistent(name(), "enabled", false, true);
|
||||
return true;
|
||||
m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(m_MOInfo->pluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
|
||||
if (m_RunnerLib != nullptr) {
|
||||
CreatePythonRunner_func CreatePythonRunner = (CreatePythonRunner_func)::GetProcAddress(m_RunnerLib, "CreatePythonRunner");
|
||||
if (CreatePythonRunner == nullptr) {
|
||||
throw MyException("embedded dll is invalid: " + windowsErrorString(::GetLastError()));
|
||||
}
|
||||
if (m_MOInfo->persistent(name(), "tryInit", false).toBool()) {
|
||||
if (pythonPath.isEmpty()) {
|
||||
m_LoadFailure = FAIL_PYTHONDETECTION;
|
||||
} else {
|
||||
m_LoadFailure = FAIL_WRONGPYTHONPATH;
|
||||
}
|
||||
if (QMessageBox::question(parentWidget(), tr("Python Initialization failed"),
|
||||
tr("On a previous start the Python Plugin failed to initialize.\n"
|
||||
"Either the value in Settings->Plugins->ProxyPython->plugin_dir is set incorrectly or it is empty and auto-detection doesn't work "
|
||||
"for whatever reason.\n"
|
||||
"Do you want to try initializing python again (at the risk of another crash)?\n"
|
||||
"Suggestion: Select \"no\", and click the warning sign for further help. Afterwards you have to re-enable the python plugin."),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
||||
m_MOInfo->setPluginSetting(name(), "enabled", false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_MOInfo) {
|
||||
m_MOInfo->setPersistent(name(), "tryInit", true);
|
||||
}
|
||||
|
||||
m_Runner = std::unique_ptr<IPythonRunner>{ createPythonRunner() };
|
||||
|
||||
if (m_MOInfo) {
|
||||
m_Runner = CreatePythonRunner(moInfo, pythonPath);
|
||||
m_MOInfo->setPersistent(name(), "tryInit", false);
|
||||
}
|
||||
|
||||
if (!m_Runner) {
|
||||
m_LoadFailure = FailureType::INITIALIZATION;
|
||||
if (m_Runner != nullptr) {
|
||||
m_LoadFailure = FAIL_NONE;
|
||||
} else {
|
||||
m_LoadFailure = FAIL_INITFAIL;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
DWORD error = ::GetLastError();
|
||||
qCritical("Failed to load python runner (%s): %s", qUtf8Printable(m_TempRunnerFile), qUtf8Printable(windowsErrorString(error)));
|
||||
if (error == ERROR_MOD_NOT_FOUND) {
|
||||
m_LoadFailure = FAIL_MISSINGDEPENDENCIES;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QString ProxyPython::name() const
|
||||
@@ -113,14 +183,9 @@ QString ProxyPython::name() const
|
||||
return "Python Proxy";
|
||||
}
|
||||
|
||||
QString ProxyPython::localizedName() const
|
||||
{
|
||||
return tr("Python Proxy");
|
||||
}
|
||||
|
||||
QString ProxyPython::author() const
|
||||
{
|
||||
return "AnyOldName3, Holt59, Silarn, Tannin";
|
||||
return "Tannin";
|
||||
}
|
||||
|
||||
QString ProxyPython::description() const
|
||||
@@ -130,15 +195,23 @@ QString ProxyPython::description() const
|
||||
|
||||
VersionInfo ProxyPython::version() const
|
||||
{
|
||||
return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
|
||||
return VersionInfo(2, 1, 0, VersionInfo::RELEASE_FINAL);
|
||||
}
|
||||
|
||||
bool ProxyPython::isActive() const
|
||||
{
|
||||
return m_LoadFailure == FAIL_NOTINIT;
|
||||
}
|
||||
|
||||
QList<PluginSetting> ProxyPython::settings() const
|
||||
{
|
||||
return {};
|
||||
QList<PluginSetting> result;
|
||||
result.push_back(PluginSetting("python_dir", "Path to your python installation. Leave empty for auto-detection", ""));
|
||||
result.push_back(PluginSetting("enabled", "Set to true to enable support for python plugins", true));
|
||||
return result;
|
||||
}
|
||||
|
||||
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
|
||||
QStringList ProxyPython::pluginList(const QString &pluginPath) const
|
||||
{
|
||||
QDir dir(pluginPath);
|
||||
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
|
||||
@@ -151,92 +224,129 @@ QStringList ProxyPython::pluginList(const QDir& pluginPath) const
|
||||
QString name = iter.next();
|
||||
QFileInfo info = iter.fileInfo();
|
||||
|
||||
|
||||
if (info.isFile() && name.endsWith(".py")) {
|
||||
result.append(name);
|
||||
}
|
||||
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
|
||||
result.append(name);
|
||||
// result.append(info.baseName());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QList<QObject*> ProxyPython::load(const QString& identifier)
|
||||
|
||||
QList<QObject*> ProxyPython::instantiate(const QString &pluginName)
|
||||
{
|
||||
if (!m_Runner) {
|
||||
return {};
|
||||
if (m_Runner != nullptr) {
|
||||
QList<QObject*> result = m_Runner->instantiate(pluginName);
|
||||
return result;
|
||||
} else {
|
||||
return QList<QObject*>();
|
||||
}
|
||||
return m_Runner->load(identifier);
|
||||
}
|
||||
|
||||
void ProxyPython::unload(const QString& identifier)
|
||||
{
|
||||
if (m_Runner) {
|
||||
return m_Runner->unload(identifier);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned int> ProxyPython::activeProblems() const
|
||||
{
|
||||
auto failure = m_LoadFailure;
|
||||
|
||||
// don't know how this could happen but wth
|
||||
if (m_Runner && !m_Runner->isPythonInitialized()) {
|
||||
failure = FailureType::INITIALIZATION;
|
||||
std::vector<unsigned int> result;
|
||||
if (m_LoadFailure == FAIL_MISSINGDEPENDENCIES) {
|
||||
result.push_back(PROBLEM_PYTHONMISSING);
|
||||
} else if (m_LoadFailure == FAIL_WRONGPYTHONPATH) {
|
||||
result.push_back(PROBLEM_WRONGPYTHONPATH);
|
||||
} else if (m_LoadFailure == FAIL_PYTHONDETECTION) {
|
||||
result.push_back(PROBLEM_PYTHONDETECTION);
|
||||
} else if (m_LoadFailure == FAIL_INITFAIL) {
|
||||
result.push_back(PROBLEM_INITFAIL);
|
||||
} else if (m_LoadFailure == FAIL_SEMICOLON) {
|
||||
result.push_back(PROBLEM_SEMICOLON);
|
||||
} else if (m_Runner != nullptr) {
|
||||
if (!m_Runner->isPythonInstalled()) {
|
||||
// don't know how this could happen but wth
|
||||
result.push_back(PROBLEM_PYTHONMISSING);
|
||||
}
|
||||
if (!m_Runner->isPythonVersionSupported()) {
|
||||
result.push_back(PROBLEM_PYTHONWRONGVERSION);
|
||||
}
|
||||
}
|
||||
|
||||
if (failure != FailureType::NONE) {
|
||||
return { static_cast<std::underlying_type_t<FailureType>>(failure) };
|
||||
}
|
||||
|
||||
return {};
|
||||
return result;
|
||||
}
|
||||
|
||||
QString ProxyPython::shortDescription(unsigned int key) const
|
||||
{
|
||||
switch (static_cast<FailureType>(key)) {
|
||||
case FailureType::SEMICOLON:
|
||||
return tr("ModOrganizer path contains a semicolon");
|
||||
case FailureType::DLL_NOT_FOUND:
|
||||
return tr("Python DLL not found");
|
||||
case FailureType::INVALID_DLL:
|
||||
return tr("Invalid Python DLL");
|
||||
case FailureType::INITIALIZATION:
|
||||
switch (key) {
|
||||
case PROBLEM_PYTHONMISSING: {
|
||||
return tr("Python not installed or not found");
|
||||
} break;
|
||||
case PROBLEM_PYTHONWRONGVERSION: {
|
||||
return tr("Python version is incompatible");
|
||||
} break;
|
||||
case PROBLEM_WRONGPYTHONPATH: {
|
||||
return tr("Invalid python path");
|
||||
} break;
|
||||
case PROBLEM_INITFAIL: {
|
||||
return tr("Initializing Python failed");
|
||||
} break;
|
||||
case PROBLEM_PYTHONDETECTION: {
|
||||
return tr("Python auto-detection failed");
|
||||
} break;
|
||||
case PROBLEM_SEMICOLON: {
|
||||
return tr("ModOrganizer path contains a semicolon");
|
||||
} break;
|
||||
default:
|
||||
return tr("invalid problem key %1").arg(key);
|
||||
throw MyException(tr("invalid problem key %1").arg(key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QString ProxyPython::fullDescription(unsigned int key) const
|
||||
{
|
||||
switch (static_cast<FailureType>(key)) {
|
||||
case FailureType::SEMICOLON:
|
||||
return tr("The path to Mod Organizer (%1) contains a semicolon. <br>"
|
||||
"While this is legal on NTFS drives, many softwares do not handle it correctly.<br>"
|
||||
"Unfortunately MO depends on libraries that seem to fall into that group.<br>"
|
||||
"As a result the python plugin cannot be loaded, and the only solution we can"
|
||||
"offer is to remove the semicolon or move MO to a path without a semicolon.").arg(QCoreApplication::applicationDirPath());
|
||||
case FailureType::DLL_NOT_FOUND:
|
||||
return tr("The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem.");
|
||||
case FailureType::INVALID_DLL:
|
||||
return tr("The Python plugin DLL is invalid, maybe your antivirus is blocking it. "
|
||||
"Re-installing MO2 and adding exclusions for it to your AV might fix the problem.");
|
||||
case FailureType::INITIALIZATION:
|
||||
return tr("The initialization of the Python plugin DLL failed, unfortunately without any details.");
|
||||
default:
|
||||
return tr("invalid problem key %1").arg(key);
|
||||
switch (key) {
|
||||
case PROBLEM_PYTHONMISSING: {
|
||||
return tr("Some MO plugins require the python interpreter to be installed. "
|
||||
"These plugins will not even show up in settings->plugins.<br>"
|
||||
"If you want to use those plugins, please install the 32-bit version of Python 2.7.x from <a href=\"%1\">%1</a>.<br>"
|
||||
"This is only required to use some extended functionality in MO, you do not need Python to play the game.").arg(s_DownloadPythonURL);
|
||||
} break;
|
||||
case PROBLEM_PYTHONWRONGVERSION: {
|
||||
return tr("Your installed python version has a different version than 2.7. "
|
||||
"Some MO plugins may not work.<br>"
|
||||
"If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) "
|
||||
"in the settings dialog.<br>"
|
||||
"This is only required to use some extended functionality in MO, you do not need Python to play the game.");
|
||||
} break;
|
||||
case PROBLEM_WRONGPYTHONPATH: {
|
||||
return tr("Please set python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.");
|
||||
} break;
|
||||
case PROBLEM_PYTHONDETECTION: {
|
||||
return tr("The auto-detection of the python path failed. I don't know why this would happen but you can try to fix it "
|
||||
"by setting python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.");
|
||||
} break;
|
||||
case PROBLEM_INITFAIL: {
|
||||
return tr("Sorry, I don't know any details. Most likely your python installation is not supported.");
|
||||
} break;
|
||||
case PROBLEM_SEMICOLON: {
|
||||
return tr("The path to Mod Organizer (%1) contains a semicolon. <br>"
|
||||
"While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.<br>"
|
||||
"Unfortunately MO depends on libraries that seem to fall into that group.<br>"
|
||||
"As a result the python plugin can't be loaded.<br>"
|
||||
"The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon.").arg(QCoreApplication::applicationDirPath());
|
||||
} break;
|
||||
default:
|
||||
throw MyException(QString("invalid problem key %1").arg(key));
|
||||
}
|
||||
}
|
||||
|
||||
bool ProxyPython::hasGuidedFix(unsigned int key) const
|
||||
{
|
||||
return false;
|
||||
return (key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION);
|
||||
}
|
||||
|
||||
void ProxyPython::startGuidedFix(unsigned int key) const
|
||||
{
|
||||
if ((key == PROBLEM_PYTHONMISSING) || (key == PROBLEM_PYTHONWRONGVERSION)) {
|
||||
::ShellExecuteA(nullptr, "open", s_DownloadPythonURL, nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
+42
-29
@@ -20,14 +20,11 @@ along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
|
||||
#ifndef PROXYPYTHON_H
|
||||
#define PROXYPYTHON_H
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
#include <ipluginproxy.h>
|
||||
#include <iplugindiagnose.h>
|
||||
|
||||
#include <map>
|
||||
#include <Windows.h>
|
||||
#include <pythonrunner.h>
|
||||
|
||||
|
||||
@@ -41,42 +38,58 @@ class ProxyPython : public QObject, public MOBase::IPluginProxy, public MOBase::
|
||||
|
||||
public:
|
||||
ProxyPython();
|
||||
~ProxyPython();
|
||||
|
||||
virtual bool init(MOBase::IOrganizer *moInfo);
|
||||
virtual QString name() const override;
|
||||
virtual QString localizedName() const override;
|
||||
virtual QString author() const override;
|
||||
virtual QString description() const override;
|
||||
virtual MOBase::VersionInfo version() const override;
|
||||
virtual QList<MOBase::PluginSetting> settings() const override;
|
||||
virtual QString name() const;
|
||||
virtual QString author() const;
|
||||
virtual QString description() const;
|
||||
virtual MOBase::VersionInfo version() const;
|
||||
virtual bool isActive() const;
|
||||
virtual QList<MOBase::PluginSetting> settings() const;
|
||||
|
||||
QStringList pluginList(const QDir& pluginPath) const override;
|
||||
QList<QObject*> load(const QString& identifier) override;
|
||||
void unload(const QString& identifier) override;
|
||||
QStringList pluginList(const QString &pluginPath) const;
|
||||
QList<QObject*> instantiate(const QString &pluginName);
|
||||
|
||||
/**
|
||||
* @return the parent widget for newly created dialogs
|
||||
* @note needs to be public so it can be exposed to plugins
|
||||
*/
|
||||
virtual QWidget *getParentWidget() { return parentWidget(); }
|
||||
|
||||
public: // IPluginDiagnose
|
||||
|
||||
virtual std::vector<unsigned int> activeProblems() const override;
|
||||
virtual QString shortDescription(unsigned int key) const override;
|
||||
virtual QString fullDescription(unsigned int key) const override;
|
||||
virtual bool hasGuidedFix(unsigned int key) const override;
|
||||
virtual void startGuidedFix(unsigned int key) const override;
|
||||
virtual std::vector<unsigned int> activeProblems() const;
|
||||
virtual QString shortDescription(unsigned int key) const;
|
||||
virtual QString fullDescription(unsigned int key) const;
|
||||
virtual bool hasGuidedFix(unsigned int key) const;
|
||||
virtual void startGuidedFix(unsigned int key) const;
|
||||
|
||||
private:
|
||||
|
||||
static const unsigned int PROBLEM_PYTHONMISSING = 1;
|
||||
static const unsigned int PROBLEM_PYTHONWRONGVERSION = 2;
|
||||
static const unsigned int PROBLEM_WRONGPYTHONPATH = 3;
|
||||
static const unsigned int PROBLEM_INITFAIL = 4;
|
||||
static const unsigned int PROBLEM_PYTHONDETECTION = 5;
|
||||
static const unsigned int PROBLEM_SEMICOLON = 6;
|
||||
static const char *s_DownloadPythonURL;
|
||||
|
||||
MOBase::IOrganizer *m_MOInfo;
|
||||
QString m_TempRunnerFile;
|
||||
HMODULE m_RunnerLib;
|
||||
std::unique_ptr<IPythonRunner> m_Runner;
|
||||
IPythonRunner *m_Runner;
|
||||
|
||||
enum class FailureType : unsigned int {
|
||||
NONE = 0,
|
||||
SEMICOLON = 1,
|
||||
DLL_NOT_FOUND = 2,
|
||||
INVALID_DLL = 3,
|
||||
INITIALIZATION = 4
|
||||
};
|
||||
|
||||
FailureType m_LoadFailure;
|
||||
enum {
|
||||
FAIL_NONE,
|
||||
FAIL_SEMICOLON,
|
||||
FAIL_NOTINIT,
|
||||
FAIL_MISSINGDEPENDENCIES,
|
||||
FAIL_INITFAIL,
|
||||
FAIL_WRONGPYTHONPATH,
|
||||
FAIL_PYTHONDETECTION,
|
||||
FAIL_OTHER
|
||||
} m_LoadFailure;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,92 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en_US">
|
||||
<context>
|
||||
<name>ProxyPython</name>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="149"/>
|
||||
<source>Python Initialization failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="150"/>
|
||||
<source>On a previous start the Python Plugin failed to initialize.
|
||||
Either the value in Settings->Plugins->ProxyPython->plugin_dir is set incorrectly or it is empty and auto-detection doesn't work for whatever reason.
|
||||
Do you want to try initializing python again (at the risk of another crash)?
|
||||
Suggestion: Select "no", and click the warning sign for further help. Afterwards you have to re-enable the python plugin.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="193"/>
|
||||
<source>Proxy Plugin to allow plugins written in python to be loaded</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="281"/>
|
||||
<source>Python not installed or not found</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="284"/>
|
||||
<source>Python version is incompatible</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="287"/>
|
||||
<source>Invalid python path</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="290"/>
|
||||
<source>Initializing Python failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="293"/>
|
||||
<source>Python auto-detection failed</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="296"/>
|
||||
<source>ModOrganizer path contains a semicolon</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="299"/>
|
||||
<source>invalid problem key %1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="308"/>
|
||||
<source>Some MO plugins require the python interpreter to be installed. These plugins will not even show up in settings-&gt;plugins.<br>If you want to use those plugins, please install the 32-bit version of Python 2.7.x from <a href="%1">%1</a>.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="314"/>
|
||||
<source>Your installed python version has a different version than 2.7. Some MO plugins may not work.<br>If you have multiple versions of python installed you may have to configure the path to 2.7 (32 bit) in the settings dialog.<br>This is only required to use some extended functionality in MO, you do not need Python to play the game.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="321"/>
|
||||
<source>Please set python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="324"/>
|
||||
<source>The auto-detection of the python path failed. I don't know why this would happen but you can try to fix it by setting python_dir in Settings->Plugins->ProxyPython to the path of your python 2.7 (32 bit) installation.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="328"/>
|
||||
<source>Sorry, I don't know any details. Most likely your python installation is not supported.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="proxy/proxypython.cpp" line="331"/>
|
||||
<source>The path to Mod Organizer (%1) contains a semicolon. <br>While this is legal on NTFS drives there is a lot of software that doesn't handle it correctly.<br>Unfortunately MO depends on libraries that seem to fall into that group.<br>As a result the python plugin can't be loaded.<br>The only solution I can offer is to remove the semicolon / move MO to a path without a semicolon.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QObject</name>
|
||||
<message>
|
||||
|
||||
@@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.16)
|
||||
project(pythonrunner)
|
||||
set(project_type plugin)
|
||||
set(enable_warnings OFF)
|
||||
set(enable_bigobj ON)
|
||||
set(install_dir bin/plugins/data)
|
||||
set(create_translations ON)
|
||||
|
||||
@@ -23,30 +22,3 @@ endif()
|
||||
|
||||
requires_project(game_features)
|
||||
requires_library(python)
|
||||
|
||||
add_filter(NAME src/converters GROUPS
|
||||
converters
|
||||
pythonutils
|
||||
shared_ptr_converter
|
||||
tuple_helper
|
||||
variant_helper
|
||||
)
|
||||
|
||||
add_filter(NAME src/runner GROUPS
|
||||
pythonrunner
|
||||
pylogger
|
||||
widgets
|
||||
)
|
||||
|
||||
add_filter(NAME src/utils GROUPS
|
||||
error
|
||||
gilock
|
||||
sipapiaccess
|
||||
)
|
||||
|
||||
add_filter(NAME src/wrappers GROUPS
|
||||
gamefeatureswrappers
|
||||
proxypluginwrappers
|
||||
pythonwrapperutilities
|
||||
uibasewrappers
|
||||
)
|
||||
|
||||
+10
-55
@@ -26,7 +26,7 @@ namespace utils {
|
||||
struct QString_to_python_str
|
||||
{
|
||||
static PyObject* convert(const QString& str) {
|
||||
// It's safer to explicitly convert to unicode as if we don't, this can return
|
||||
// It's safer to explicitly convert to unicode as if we don't, this can return
|
||||
// either str or unicode without it being easy to know which to expect
|
||||
bpy::object pyStr = bpy::object(qUtf8Printable(str));
|
||||
if (SIPBytes_Check(pyStr.ptr()))
|
||||
@@ -66,37 +66,6 @@ namespace utils {
|
||||
|
||||
}
|
||||
|
||||
namespace Enum_converter {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
template <typename Enum>
|
||||
struct Enum_to_int
|
||||
{
|
||||
static PyObject* convert(const Enum& flags) {
|
||||
return bpy::incref(bpy::object(static_cast<int>(flags)).ptr());
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Enum>
|
||||
struct Enum_from_python_obj
|
||||
{
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
return SIPLong_Check(objPtr) ? objPtr : nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
int intVersion = (int)SIPLong_AsLong(objPtr);
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Enum>*)data)->storage.bytes;
|
||||
new (storage) Enum(static_cast<Enum>(intVersion));
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace QFlags_converter {
|
||||
|
||||
/**
|
||||
@@ -362,18 +331,14 @@ namespace utils {
|
||||
};
|
||||
|
||||
template <class T, class... Ws>
|
||||
struct wrap_impl<boost::reference_wrapper<T>, Ws... > : public wrap_impl<> {
|
||||
using wrap_impl<>::apply;
|
||||
|
||||
struct wrap_impl<boost::reference_wrapper<T>, Ws... > {
|
||||
static auto apply(T t) {
|
||||
return boost::ref(t);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, class... Ws>
|
||||
struct wrap_impl<boost::python::pointer_wrapper<T>, Ws... > : public wrap_impl<> {
|
||||
using wrap_impl<>::apply;
|
||||
|
||||
struct wrap_impl<boost::python::pointer_wrapper<T>, Ws... > {
|
||||
static auto apply(T t) {
|
||||
return bpy::ptr(t);
|
||||
}
|
||||
@@ -399,7 +364,7 @@ namespace utils {
|
||||
template <typename R, typename... Args, typename... Wrappers>
|
||||
struct Functor_converter<R(Args...), Wrappers... >
|
||||
{
|
||||
|
||||
|
||||
template <class T>
|
||||
static decltype(auto) wrap(T&& t) {
|
||||
return details::wrap_impl<Wrappers... >::apply(std::forward<T>(t));
|
||||
@@ -514,8 +479,8 @@ namespace utils {
|
||||
using namespace QString_converter;
|
||||
bpy::to_python_converter<QString, QString_to_python_str>();
|
||||
bpy::converter::registry::push_back(
|
||||
&QString_from_python_str::convertible,
|
||||
&QString_from_python_str::construct,
|
||||
&QString_from_python_str::convertible,
|
||||
&QString_from_python_str::construct,
|
||||
bpy::type_id<QString>());
|
||||
}
|
||||
|
||||
@@ -523,8 +488,8 @@ namespace utils {
|
||||
using namespace QVariant_converter;
|
||||
bpy::to_python_converter<QVariant, QVariant_to_python_obj>();
|
||||
bpy::converter::registry::push_back(
|
||||
&QVariant_from_python_obj::convertible,
|
||||
&QVariant_from_python_obj::construct,
|
||||
&QVariant_from_python_obj::convertible,
|
||||
&QVariant_from_python_obj::construct,
|
||||
bpy::type_id<QVariant>());
|
||||
}
|
||||
|
||||
@@ -539,16 +504,6 @@ namespace utils {
|
||||
bpy::type_id<Flags>());
|
||||
}
|
||||
|
||||
template <class Enum>
|
||||
inline void register_enum_converter() {
|
||||
using namespace Enum_converter;
|
||||
bpy::to_python_converter<Enum, Enum_to_int<Enum>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&Enum_from_python_obj<Enum>::convertible,
|
||||
&Enum_from_python_obj<Enum>::construct,
|
||||
bpy::type_id<Enum>());
|
||||
}
|
||||
|
||||
template <class QClass>
|
||||
inline void register_qclass_converter() {
|
||||
using Converter = QClass_converter::QClass_converters<QClass>;
|
||||
@@ -569,8 +524,8 @@ namespace utils {
|
||||
inline void register_functor_converter() {
|
||||
using Converter = Functor_converter<Fn, Wrappers... >;
|
||||
bpy::converter::registry::push_back(
|
||||
&Converter::convertible,
|
||||
&Converter::construct,
|
||||
&Converter::convertible,
|
||||
&Converter::construct,
|
||||
bpy::type_id<std::function<Fn>>());
|
||||
}
|
||||
|
||||
|
||||
+8
-8
@@ -31,10 +31,10 @@ namespace pyexcept {
|
||||
* @brief Exception to throw when a python implementation does not implement
|
||||
* a pure virtual function.
|
||||
*/
|
||||
class MissingImplementation : public MOBase::Exception {
|
||||
class MissingImplementation : public MOBase::MyException {
|
||||
public:
|
||||
MissingImplementation(std::string const& className, std::string const& methodName) :
|
||||
Exception(QString::fromStdString(
|
||||
MyException(QString::fromStdString(
|
||||
fmt::format("Python class implementing \"{}\" has no implementation of method \"{}\".",
|
||||
className, methodName))) { }
|
||||
|
||||
@@ -43,21 +43,21 @@ namespace pyexcept {
|
||||
/**
|
||||
* @brief Exception to throw when a python error occurs.
|
||||
*/
|
||||
class PythonError : public MOBase::Exception {
|
||||
class PythonError : public MOBase::MyException {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Create a new PythonError, fetching the error message from python. If the message
|
||||
* cannot be retrieved, `defaultErrorMessage()` is used instead.
|
||||
*/
|
||||
PythonError() : Exception(getPythonErrorMessage()) { }
|
||||
PythonError() : MyException(getPythonErrorMessage()) { }
|
||||
|
||||
/**
|
||||
* @brief Create a new PythonError with the given message.
|
||||
*
|
||||
* @param message Message for the exception.
|
||||
*/
|
||||
PythonError(QString message) : Exception(message) { }
|
||||
PythonError(QString message) : MyException(message) { }
|
||||
|
||||
protected:
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace pyexcept {
|
||||
* @brief Exception to throw when an unknown error occured. This is typically thrown
|
||||
* from a catch(...) block.
|
||||
*/
|
||||
class UnknownException : public MOBase::Exception {
|
||||
class UnknownException : public MOBase::MyException {
|
||||
public:
|
||||
|
||||
/**
|
||||
@@ -99,14 +99,14 @@ namespace pyexcept {
|
||||
*
|
||||
* @see defaultErrorMessage
|
||||
*/
|
||||
UnknownException() : Exception(defaultErrorMessage()) { }
|
||||
UnknownException() : MyException(defaultErrorMessage()) { }
|
||||
|
||||
/**
|
||||
* @brief Create a new UnknownException with the given message.
|
||||
*
|
||||
* @param message Message for the exception.
|
||||
*/
|
||||
UnknownException(QString message) : Exception(message) { }
|
||||
UnknownException(QString message) : MyException(message) { }
|
||||
|
||||
protected:
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "gamefeatureswrappers.h"
|
||||
|
||||
#include <any>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#include <typeindex>
|
||||
|
||||
#include <ipluginlist.h>
|
||||
@@ -8,7 +9,6 @@
|
||||
#include <isavegame.h>
|
||||
#include <isavegameinfowidget.h>
|
||||
|
||||
#include "shared_ptr_converter.h"
|
||||
#include "ifiletree.h"
|
||||
#include "pythonwrapperutilities.h"
|
||||
|
||||
@@ -108,9 +108,7 @@ ModDataChecker::CheckReturn ModDataCheckerWrapper::dataLooksValid(std::shared_pt
|
||||
}
|
||||
|
||||
std::shared_ptr<MOBase::IFileTree> ModDataCheckerWrapper::fix(std::shared_ptr<MOBase::IFileTree> fileTree) const {
|
||||
return utils::clean_shared_ptr(
|
||||
basicWrapperFunctionImplementationWithDefault<std::shared_ptr<MOBase::IFileTree>>(
|
||||
this, [](auto&&... args) { return nullptr; }, "fix", fileTree));
|
||||
return basicWrapperFunctionImplementationWithDefault<std::shared_ptr<MOBase::IFileTree>>(this, [](auto&&... args) { return nullptr; }, "fix", fileTree);
|
||||
}
|
||||
|
||||
/// end ModDataChecker Wrapper
|
||||
@@ -128,9 +126,15 @@ std::vector<int> ModDataContentWrapper::getContentsFor(std::shared_ptr<const MOB
|
||||
/////////////////////////////
|
||||
/// SaveGameInfo Wrapper
|
||||
|
||||
SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(MOBase::ISaveGame const& save) const
|
||||
|
||||
MOBase::ISaveGame const * SaveGameInfoWrapper::getSaveGameInfo(QString const & file) const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<SaveGameInfoWrapper::MissingAssets>(this, "getMissingAssets", boost::ref(save));
|
||||
return basicWrapperFunctionImplementation<MOBase::ISaveGame*>(this, m_SaveGames[file], "getSaveGameInfo", file);
|
||||
}
|
||||
|
||||
SaveGameInfoWrapper::MissingAssets SaveGameInfoWrapper::getMissingAssets(QString const & file) const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<SaveGameInfoWrapper::MissingAssets>(this, "getMissingAssets", file);
|
||||
}
|
||||
|
||||
MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* parent) const
|
||||
@@ -138,6 +142,10 @@ MOBase::ISaveGameInfoWidget* SaveGameInfoWrapper::getSaveGameWidget(QWidget* par
|
||||
return basicWrapperFunctionImplementation<MOBase::ISaveGameInfoWidget*>(this, m_SaveGameWidget, "getSaveGameWidget", parent);
|
||||
}
|
||||
|
||||
bool SaveGameInfoWrapper::hasScriptExtenderSave(QString const & file) const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<bool>(this, "hasScriptExtenderSave", file);
|
||||
}
|
||||
/// end SaveGameInfo Wrapper
|
||||
/////////////////////////////
|
||||
/// ScriptExtender Wrapper
|
||||
@@ -162,9 +170,9 @@ QString ScriptExtenderWrapper::loaderPath() const
|
||||
return basicWrapperFunctionImplementation<QString>(this, "loaderPath");
|
||||
}
|
||||
|
||||
QString ScriptExtenderWrapper::savegameExtension() const
|
||||
QStringList ScriptExtenderWrapper::saveGameAttachmentExtensions() const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<QString>(this, "savegameExtension");
|
||||
return basicWrapperFunctionImplementation<QStringList>(this, "saveGameAttachmentExtensions");
|
||||
}
|
||||
|
||||
bool ScriptExtenderWrapper::isInstalled() const
|
||||
@@ -212,7 +220,7 @@ QStringList UnmanagedModsWrapper::secondaryFiles(const QString & modName) const
|
||||
|
||||
game_features_map_from_python::game_features_map_from_python()
|
||||
{
|
||||
boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id<std::map<std::type_index, std::any>>());
|
||||
boost::python::converter::registry::push_back(&convertible, &construct, boost::python::type_id<std::map<std::type_index, boost::any>>());
|
||||
}
|
||||
|
||||
void * game_features_map_from_python::convertible(PyObject * objPtr)
|
||||
@@ -221,15 +229,15 @@ void * game_features_map_from_python::convertible(PyObject * objPtr)
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void insertGameFeature(std::map<std::type_index, std::any>& map, const boost::python::object& pyObject)
|
||||
void insertGameFeature(std::map<std::type_index, boost::any>& map, const boost::python::object& pyObject)
|
||||
{
|
||||
map[std::type_index(typeid(T))] = boost::python::extract<T*>(pyObject)();
|
||||
}
|
||||
|
||||
void game_features_map_from_python::construct(PyObject * objPtr, boost::python::converter::rvalue_from_python_stage1_data * data)
|
||||
{
|
||||
void *storage = ((boost::python::converter::rvalue_from_python_storage<std::map<std::type_index, std::any>>*)data)->storage.bytes;
|
||||
std::map<std::type_index, std::any> *result = new (storage) std::map<std::type_index, std::any>();
|
||||
void *storage = ((boost::python::converter::rvalue_from_python_storage<std::map<std::type_index, boost::any>>*)data)->storage.bytes;
|
||||
std::map<std::type_index, boost::any> *result = new (storage) std::map<std::type_index, boost::any>();
|
||||
boost::python::dict source(boost::python::handle<>(boost::python::borrowed(objPtr)));
|
||||
boost::python::list keys = source.keys();
|
||||
int len = boost::python::len(keys);
|
||||
@@ -308,7 +316,7 @@ void registerGameFeaturesPythonConverters()
|
||||
.def("getContentsFor", bpy::pure_virtual(&ModDataContent::getContentsFor), bpy::arg("filetree"))
|
||||
;
|
||||
|
||||
bpy::class_<ModDataContent::Content>("Content",
|
||||
bpy::class_<ModDataContent::Content>("Content",
|
||||
bpy::init<int, QString, QString, bpy::optional<bool>>((bpy::arg("id"), "name", "icon", bpy::arg("filter_only") = false)))
|
||||
.add_property("id", &ModDataContent::Content::id)
|
||||
.add_property("name", &ModDataContent::Content::name)
|
||||
@@ -319,9 +327,12 @@ void registerGameFeaturesPythonConverters()
|
||||
}
|
||||
|
||||
bpy::class_<SaveGameInfoWrapper, boost::noncopyable>("SaveGameInfo")
|
||||
.def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("save"))
|
||||
.def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy<bpy::manage_new_object>(),
|
||||
.def("getSaveGameInfo", bpy::pure_virtual(&SaveGameInfo::getSaveGameInfo), bpy::return_value_policy<bpy::manage_new_object>(),
|
||||
bpy::arg("filepath"))
|
||||
.def("getMissingAssets", bpy::pure_virtual(&SaveGameInfo::getMissingAssets), bpy::arg("filepath"))
|
||||
.def("getSaveGameWidget", bpy::pure_virtual(&SaveGameInfo::getSaveGameWidget), bpy::return_value_policy<bpy::manage_new_object>(),
|
||||
bpy::arg("parent"), "[optional]")
|
||||
.def("hasScriptExtenderSave", bpy::pure_virtual(&SaveGameInfo::hasScriptExtenderSave), bpy::arg("filepath"))
|
||||
;
|
||||
|
||||
bpy::class_<ScriptExtenderWrapper, boost::noncopyable>("ScriptExtender")
|
||||
@@ -329,7 +340,7 @@ void registerGameFeaturesPythonConverters()
|
||||
.def("PluginPath", bpy::pure_virtual(&ScriptExtender::PluginPath))
|
||||
.def("loaderName", bpy::pure_virtual(&ScriptExtender::loaderName))
|
||||
.def("loaderPath", bpy::pure_virtual(&ScriptExtender::loaderPath))
|
||||
.def("savegameExtension", bpy::pure_virtual(&ScriptExtender::savegameExtension))
|
||||
.def("saveGameAttachmentExtensions", bpy::pure_virtual(&ScriptExtender::saveGameAttachmentExtensions))
|
||||
.def("isInstalled", bpy::pure_virtual(&ScriptExtender::isInstalled))
|
||||
.def("getExtenderVersion", bpy::pure_virtual(&ScriptExtender::getExtenderVersion))
|
||||
.def("getArch", bpy::pure_virtual(&ScriptExtender::getArch))
|
||||
|
||||
@@ -106,8 +106,10 @@ public:
|
||||
static constexpr const char* className = "SaveGameInfoWrapper";
|
||||
using boost::python::wrapper<SaveGameInfo>::get_override;
|
||||
|
||||
virtual MissingAssets getMissingAssets(MOBase::ISaveGame const& save) const override;
|
||||
virtual MOBase::ISaveGame const *getSaveGameInfo(QString const &file) const override;
|
||||
virtual MissingAssets getMissingAssets(QString const &file) const override;
|
||||
virtual MOBase::ISaveGameInfoWidget *getSaveGameWidget(QWidget *parent = 0) const override;
|
||||
virtual bool hasScriptExtenderSave(QString const &file) const override;
|
||||
|
||||
private:
|
||||
// We need to keep the python objects alive:
|
||||
@@ -125,7 +127,7 @@ public:
|
||||
virtual QString PluginPath() const override;
|
||||
virtual QString loaderName() const override;
|
||||
virtual QString loaderPath() const override;
|
||||
virtual QString savegameExtension() const override;
|
||||
virtual QStringList saveGameAttachmentExtensions() const override;
|
||||
virtual bool isInstalled() const override;
|
||||
virtual QString getExtenderVersion() const override;
|
||||
virtual WORD getArch() const override;
|
||||
|
||||
@@ -7,6 +7,5 @@ GILock::GILock()
|
||||
|
||||
GILock::~GILock()
|
||||
{
|
||||
PyErr_Clear();
|
||||
PyGILState_Release(m_State);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#include <QUrl>
|
||||
#include <QWidget>
|
||||
|
||||
#include "shared_ptr_converter.h"
|
||||
#include "pythonwrapperutilities.h"
|
||||
#include "uibasewrappers.h"
|
||||
|
||||
#include <variant>
|
||||
#include <tuple>
|
||||
@@ -28,9 +26,8 @@ namespace boost
|
||||
|
||||
using namespace MOBase;
|
||||
|
||||
// See COMMON_I_PLUGIN_WRAPPER_DECLARATIONS__IMPL in proxypluginwrappers.h for explanation on
|
||||
// the "include_requirements".
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, include_requirements) \
|
||||
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) \
|
||||
bool class_name::init(MOBase::IOrganizer *moInfo) \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementation<bool>(this, "init", boost::python::ptr(moInfo)); \
|
||||
@@ -41,16 +38,6 @@ QString class_name::name() const \
|
||||
return basicWrapperFunctionImplementation<QString>(this, "name"); \
|
||||
} \
|
||||
\
|
||||
QString class_name::localizedName() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementationWithDefault<QString>(this, &class_name::localizedName_Default, "localizedName"); \
|
||||
} \
|
||||
\
|
||||
QString class_name::master() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementationWithDefault<QString>(this, &class_name::master_Default, "master"); \
|
||||
} \
|
||||
\
|
||||
QString class_name::author() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementation<QString>(this, "author"); \
|
||||
@@ -66,25 +53,15 @@ MOBase::VersionInfo class_name::version() const \
|
||||
return basicWrapperFunctionImplementation<MOBase::VersionInfo>(this, "version"); \
|
||||
} \
|
||||
\
|
||||
bool class_name::isActive() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementation<bool>(this, "isActive"); \
|
||||
} \
|
||||
\
|
||||
QList<MOBase::PluginSetting> class_name::settings() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementation<QList<MOBase::PluginSetting>>(this, "settings"); \
|
||||
} \
|
||||
QString class_name::localizedName_Default() const { return IPlugin::localizedName(); } \
|
||||
QString class_name::master_Default() const { return IPlugin::master(); } \
|
||||
BOOST_PP_EXPR_IF(include_requirements, \
|
||||
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> class_name::requirements() const { \
|
||||
return basicWrapperFunctionImplementationWithDefault<std::vector<std::shared_ptr<const MOBase::IPluginRequirement>>>( \
|
||||
this, &class_name::requirements_Default, "requirements"); \
|
||||
} \
|
||||
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> class_name::requirements_Default() const { return IPlugin::requirements(); } \
|
||||
bool class_name::enabledByDefault() const \
|
||||
{ \
|
||||
return basicWrapperFunctionImplementationWithDefault<bool>(this, &class_name::enabledByDefault_Default, "enabledByDefault"); \
|
||||
} \
|
||||
bool class_name::enabledByDefault_Default() const { return IPlugin::enabledByDefault(); })
|
||||
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(class_name) COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(class_name, 1)
|
||||
}
|
||||
|
||||
/// end COMMON_I_PLUGIN_WRAPPER_DEFINITIONS
|
||||
/////////////////////////////
|
||||
@@ -139,10 +116,6 @@ MappingType IPluginFileMapperWrapper::mappings() const
|
||||
/////////////////////////////////////
|
||||
/// IPluginGame Wrapper
|
||||
|
||||
void IPluginGameWrapper::detectGame()
|
||||
{
|
||||
return basicWrapperFunctionImplementation<void>(this, "detectGame");
|
||||
}
|
||||
|
||||
QString IPluginGameWrapper::gameName() const
|
||||
{
|
||||
@@ -154,11 +127,14 @@ void IPluginGameWrapper::initializeProfile(const QDir & directory, ProfileSettin
|
||||
basicWrapperFunctionImplementation<void>(this, "initializeProfile", directory, settings);
|
||||
}
|
||||
|
||||
std::vector<std::shared_ptr<const MOBase::ISaveGame>> IPluginGameWrapper::listSaves(QDir folder) const
|
||||
QString IPluginGameWrapper::savegameExtension() const
|
||||
{
|
||||
// Why do I not need to hold python references here? Is it because those are wrapped
|
||||
// in shared_ptr?
|
||||
return basicWrapperFunctionImplementation<std::vector<std::shared_ptr<const MOBase::ISaveGame>>>(this, "listSaves", folder);
|
||||
return basicWrapperFunctionImplementation<QString>(this, "savegameExtension");
|
||||
}
|
||||
|
||||
QString IPluginGameWrapper::savegameSEExtension() const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<QString>(this, "savegameSEExtension");
|
||||
}
|
||||
|
||||
bool IPluginGameWrapper::isInstalled() const
|
||||
@@ -301,11 +277,11 @@ QString IPluginGameWrapper::getLauncherName() const
|
||||
return basicWrapperFunctionImplementation<QString>(this, "getLauncherName");
|
||||
}
|
||||
|
||||
COMMON_I_PLUGIN_WRAPPER_DEFINITIONS_IMPL(IPluginGameWrapper, 0)
|
||||
COMMON_I_PLUGIN_WRAPPER_DEFINITIONS(IPluginGameWrapper)
|
||||
|
||||
std::map<std::type_index, std::any> IPluginGameWrapper::featureList() const
|
||||
std::map<std::type_index, boost::any> IPluginGameWrapper::featureList() const
|
||||
{
|
||||
return basicWrapperFunctionImplementation<std::map<std::type_index, std::any>>(this, "_featureList");
|
||||
return basicWrapperFunctionImplementation<std::map<std::type_index, boost::any>>(this, "_featureList");
|
||||
}
|
||||
/// end IPluginGame Wrapper
|
||||
/////////////////////////////////////
|
||||
@@ -314,11 +290,7 @@ std::map<std::type_index, std::any> IPluginGameWrapper::featureList() const
|
||||
#define COMMON_I_PLUGIN_INSTALLER_WRAPPER_DEFINITIONS(class_name) \
|
||||
unsigned int class_name::priority() const { return basicWrapperFunctionImplementation<unsigned int>(this, "priority"); } \
|
||||
bool class_name::isManualInstaller() const { return basicWrapperFunctionImplementation<bool>(this, "isManualInstaller"); } \
|
||||
void class_name::onInstallationStart(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) { \
|
||||
basicWrapperFunctionImplementationWithDefault<void>(this, &class_name::onInstallationStart_Default, "onInstallationStart", archive, reinstallation, boost::python::ptr(currentMod)); } \
|
||||
void class_name::onInstallationEnd(EInstallResult result, MOBase::IModInterface* newMod) { \
|
||||
basicWrapperFunctionImplementationWithDefault<void>(this, &class_name::onInstallationEnd_Default, "onInstallationEnd", result, boost::python::ptr(newMod)); } \
|
||||
bool class_name::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const { return basicWrapperFunctionImplementation<bool>(this, "isArchiveSupported", tree); }
|
||||
bool class_name::isArchiveSupported(std::shared_ptr<const IFileTree> tree) const { return basicWrapperFunctionImplementation<bool>(this, "isArchiveSupported", tree); }
|
||||
|
||||
/// end IPluginInstaller macro
|
||||
/////////////////////////////////////
|
||||
@@ -335,11 +307,11 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install(
|
||||
|
||||
using return_type = std::variant<
|
||||
IPluginInstaller::EInstallResult,
|
||||
std::shared_ptr<IFileTree>,
|
||||
std::tuple<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>, QString, int>>;
|
||||
std::shared_ptr<IFileTree>,
|
||||
std::tuple<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>, QString, int>> ;
|
||||
auto ret = basicWrapperFunctionImplementation<return_type>(this, "install", boost::ref(modName), tree, version, nexusID);
|
||||
|
||||
auto result = std::visit([&](auto const& t) {
|
||||
return std::visit([&](auto const& t) {
|
||||
using type = std::decay_t<decltype(t)>;
|
||||
if constexpr (std::is_same_v<type, IPluginInstaller::EInstallResult>) {
|
||||
return t;
|
||||
@@ -355,9 +327,6 @@ IPluginInstaller::EInstallResult IPluginInstallerSimpleWrapper::install(
|
||||
return std::get<0>(t);
|
||||
}
|
||||
}, ret);
|
||||
|
||||
tree = utils::clean_shared_ptr(tree);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// end IPluginInstallerSimple Wrapper
|
||||
@@ -379,7 +348,7 @@ std::set<QString> IPluginInstallerCustomWrapper::supportedExtensions() const
|
||||
IPluginInstaller::EInstallResult IPluginInstallerCustomWrapper::install(
|
||||
GuessedValue<QString> &modName, QString gameName, const QString &archiveName, const QString &version, int modID)
|
||||
{
|
||||
// Note: This requires far more less trouble than the "Simple" installer version since 1) there is no tree
|
||||
// Note: This requires far more less trouble than the "Simple" installer version since 1) there is no tree
|
||||
// and 2) there version and modId cannot be modified:
|
||||
return basicWrapperFunctionImplementation<IPluginInstaller::EInstallResult>(
|
||||
this, "install", boost::ref(modName), gameName, archiveName, version, modID);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define PROXYPLUGINWRAPPERS_H
|
||||
|
||||
|
||||
#include <iplugin.h>
|
||||
#include <iplugindiagnose.h>
|
||||
#include <ipluginfilemapper.h>
|
||||
#include <iplugingame.h>
|
||||
@@ -14,30 +13,18 @@
|
||||
|
||||
#ifndef Q_MOC_RUN
|
||||
#include <boost/python.hpp>
|
||||
#include <boost/preprocessor/control/expr_if.hpp>
|
||||
#endif
|
||||
|
||||
// The wrapper for IPluginGame cannot override requirements or enabledByDefault since they're final,
|
||||
// so we need to be able to exclude the declarations.
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(include_requirements) \
|
||||
public: \
|
||||
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS public: \
|
||||
virtual bool init(MOBase::IOrganizer *moInfo) override; \
|
||||
virtual QString name() const override; \
|
||||
virtual QString localizedName() const override; \
|
||||
virtual QString master() const override; \
|
||||
virtual QString author() const override; \
|
||||
virtual QString description() const override; \
|
||||
virtual MOBase::VersionInfo version() const override; \
|
||||
virtual QList<MOBase::PluginSetting> settings() const override; \
|
||||
QString localizedName_Default() const; \
|
||||
QString master_Default() const; \
|
||||
BOOST_PP_EXPR_IF(include_requirements, \
|
||||
virtual std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> requirements() const override; \
|
||||
std::vector<std::shared_ptr<const MOBase::IPluginRequirement>> requirements_Default() const; \
|
||||
virtual bool enabledByDefault() const override; \
|
||||
bool enabledByDefault_Default() const;)
|
||||
virtual bool isActive() const override; \
|
||||
virtual QList<MOBase::PluginSetting> settings() const override;
|
||||
|
||||
#define COMMON_I_PLUGIN_WRAPPER_DECLARATIONS COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(1)
|
||||
|
||||
// Even though the base interface is not a QObject, this has to be because we have no way to pass Mod Organizer a plugin that implements multiple interfaces.
|
||||
// QObject must be the first base class because moc assumes the first base class is a QObject
|
||||
@@ -102,10 +89,10 @@ public:
|
||||
static constexpr const char* className = "IPluginGameWrapper";
|
||||
using boost::python::wrapper<MOBase::IPluginGame>::get_override;
|
||||
|
||||
virtual void detectGame() override;
|
||||
virtual QString gameName() const override;
|
||||
virtual void initializeProfile(const QDir &directory, ProfileSettings settings) const override;
|
||||
virtual std::vector<std::shared_ptr<const MOBase::ISaveGame>> listSaves(QDir folder) const override;
|
||||
virtual QString savegameExtension() const override;
|
||||
virtual QString savegameSEExtension() const override;
|
||||
virtual bool isInstalled() const override;
|
||||
virtual QIcon gameIcon() const override;
|
||||
virtual QDir gameDirectory() const override;
|
||||
@@ -135,11 +122,11 @@ public:
|
||||
virtual QString gameVersion() const override;
|
||||
virtual QString getLauncherName() const override;
|
||||
|
||||
COMMON_I_PLUGIN_WRAPPER_DECLARATIONS_IMPL(0)
|
||||
COMMON_I_PLUGIN_WRAPPER_DECLARATIONS
|
||||
|
||||
protected:
|
||||
// Apparently, Python developers interpret an underscore in a function name as it being protected
|
||||
virtual std::map<std::type_index, std::any> featureList() const override;
|
||||
virtual std::map<std::type_index, boost::any> featureList() const override;
|
||||
|
||||
// Thankfully, the default implementation of the templated 'T *feature()' function should allow us to get away without overriding it.
|
||||
};
|
||||
@@ -150,13 +137,7 @@ using IPluginInstaller::parentWidget; \
|
||||
using IPluginInstaller::manager; \
|
||||
virtual unsigned int priority() const override; \
|
||||
virtual bool isManualInstaller() const override; \
|
||||
virtual void onInstallationStart(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) override; \
|
||||
void onInstallationStart_Default(QString const& archive, bool reinstallation, MOBase::IModInterface* currentMod) { \
|
||||
return IPluginInstaller::onInstallationStart(archive, reinstallation, currentMod); } \
|
||||
virtual void onInstallationEnd(EInstallResult result, MOBase::IModInterface* newMod) override; \
|
||||
void onInstallationEnd_Default(EInstallResult result, MOBase::IModInterface* newMod) { \
|
||||
return IPluginInstaller::onInstallationEnd(result, newMod); } \
|
||||
virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const override;
|
||||
virtual bool isArchiveSupported(std::shared_ptr<const MOBase::IFileTree> tree) const override;
|
||||
|
||||
|
||||
class IPluginInstallerSimpleWrapper : public MOBase::IPluginInstallerSimple, public boost::python::wrapper<MOBase::IPluginInstallerSimple>
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
#include "pylogger.h"
|
||||
|
||||
#include "log.h"
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
namespace bpy = boost::python;
|
||||
|
||||
// Small structure to hold the levels - There are copy paste from
|
||||
// my Python version and I assume these will not change soon:
|
||||
struct PyLogLevel {
|
||||
static constexpr int CRITICAL = 50;
|
||||
static constexpr int ERROR = 40;
|
||||
static constexpr int WARNING = 30;
|
||||
static constexpr int INFO = 20;
|
||||
static constexpr int DEBUG = 10;
|
||||
};
|
||||
|
||||
// This is the function we are going to use as our Handler .emit
|
||||
// method.
|
||||
void emit_function(bpy::object self, bpy::object record) {
|
||||
|
||||
// There are other parameters that could be used, but this is minimal for
|
||||
// now (filename, line number, etc.).
|
||||
const int level = bpy::extract<int>(record.attr("levelno"));
|
||||
const std::wstring msg = bpy::extract<std::wstring>(bpy::str(record.attr("msg")));
|
||||
|
||||
switch (level) {
|
||||
case PyLogLevel::CRITICAL:
|
||||
case PyLogLevel::ERROR:
|
||||
MOBase::log::error("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::WARNING:
|
||||
MOBase::log::warn("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::INFO:
|
||||
MOBase::log::info("{}", msg);
|
||||
break;
|
||||
case PyLogLevel::DEBUG:
|
||||
default: // There is a "NOTSET" level in theory:
|
||||
MOBase::log::debug("{}", msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
void configure_python_logging(bpy::object mobase)
|
||||
{
|
||||
// Most of this is dealing with actual Python objects since it is not possible
|
||||
// to derive from logging.Handler in C++ using Boost.Python, and since a lot of
|
||||
// this would require extra register only for this.
|
||||
|
||||
// Retrieve the logging module and the Handler class.
|
||||
auto logging = bpy::import("logging");
|
||||
auto Handler = logging.attr("Handler");
|
||||
|
||||
// This is ugly but that's how it's done in C Python.
|
||||
auto type = (PyObject*)&PyType_Type;
|
||||
|
||||
// Create the "MO2Handler" python class:
|
||||
auto methods = bpy::dict();
|
||||
methods["emit"] = bpy::make_function(emit_function);
|
||||
auto MO2Handler = bpy::call<bpy::object>(type, "LogHandler", bpy::make_tuple(Handler), methods);
|
||||
|
||||
// Create the default logger:
|
||||
auto handler = MO2Handler();
|
||||
handler.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
auto logger = logging.attr("getLogger")(bpy::object(mobase.attr("__name__")));
|
||||
logger.attr("setLevel")(PyLogLevel::DEBUG);
|
||||
logger.attr("addHandler")(handler);
|
||||
|
||||
// Set mobase attributes:
|
||||
mobase.attr("LogHandler") = MO2Handler;
|
||||
mobase.attr("logger") = logger;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#ifndef MO2_PYTHON_LOGGER_H
|
||||
#define MO2_PYTHON_LOGGER_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
/**
|
||||
* @brief Configure logging for MO2 python plugin.
|
||||
*
|
||||
* @param mobase The mobase module.
|
||||
*/
|
||||
void configure_python_logging(boost::python::object mobase);
|
||||
|
||||
#endif
|
||||
+109
-455
File diff suppressed because it is too large
Load Diff
@@ -10,13 +10,9 @@
|
||||
|
||||
class IPythonRunner {
|
||||
public:
|
||||
|
||||
virtual QList<QObject*> load(const QString& identifier) = 0;
|
||||
virtual void unload(const QString& identifier) = 0;
|
||||
|
||||
virtual bool isPythonInitialized() const = 0;
|
||||
|
||||
virtual ~IPythonRunner() { }
|
||||
virtual QList<QObject*> instantiate(const QString &pluginName) = 0;
|
||||
virtual bool isPythonInstalled() const = 0;
|
||||
virtual bool isPythonVersionSupported() const = 0;
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +22,7 @@ public:
|
||||
#define PYDLLEXPORT Q_DECL_IMPORT
|
||||
#endif // PYTHONRUNNER_LIBRARY
|
||||
|
||||
extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner();
|
||||
extern "C" PYDLLEXPORT IPythonRunner *CreatePythonRunner(MOBase::IOrganizer *moInfo, const QString &pythonDir);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
#include "pythonutils.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <set>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message, bool show_once) {
|
||||
|
||||
// Contains the list of filename / line number for which a deprecation warning has already been shown.
|
||||
static std::set<std::pair<std::string, int>> DeprecatedLines;
|
||||
|
||||
// Find the caller:
|
||||
auto inspect = bpy::import("inspect");
|
||||
auto current_frame = inspect.attr("currentframe")();
|
||||
auto callable_frame = inspect.attr("getouterframes")(current_frame, 2);
|
||||
auto filename = bpy::extract<std::string>(callable_frame[-1].attr("filename"))();
|
||||
auto function = bpy::extract<std::string>(callable_frame[-1].attr("function"))();
|
||||
auto lineno = bpy::extract<int>(callable_frame[-1].attr("lineno"));
|
||||
|
||||
// Only show once if requested:
|
||||
if (show_once && DeprecatedLines.contains({ filename, lineno })) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register the deprecation:
|
||||
DeprecatedLines.emplace(filename, lineno);
|
||||
|
||||
auto path = relative(std::filesystem::path(filename), QCoreApplication::applicationDirPath().toStdWString());
|
||||
|
||||
// Show the message:
|
||||
if (message.empty()) {
|
||||
MOBase::log::warn(
|
||||
"[deprecated] {} in {} [{}:{}].", name, function, path.native(), lineno);
|
||||
}
|
||||
else {
|
||||
MOBase::log::warn(
|
||||
"[deprecated] {} in {} [{}:{}]: {}", name, function, path.native(), lineno, message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
-82
@@ -3,8 +3,6 @@
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
#include "error.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
namespace bpy = boost::python;
|
||||
@@ -91,19 +89,19 @@ namespace utils {
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
// Check that the object can be iterated or is a sequence. There is no "clean"
|
||||
// way checking that an object is iterable apparently (PyIter_Check checks that
|
||||
// an object is an iterator, which is very different).
|
||||
if (objPtr->ob_type->tp_iter != 0 || PySequence_Check(objPtr)) return objPtr;
|
||||
if (PySequence_Check(objPtr)) return objPtr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
|
||||
Container* result = new (storage) Container();
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
bpy::stl_input_iterator<value_type> begin(source), end;
|
||||
std::copy(begin, end, std::back_inserter(*result));
|
||||
bpy::list source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
int length = bpy::len(source);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
result->push_back(bpy::extract<value_type>(source[i]));
|
||||
}
|
||||
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
@@ -132,8 +130,7 @@ namespace utils {
|
||||
using value_type = typename Container::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
// See container_from_python.
|
||||
if (objPtr->ob_type->tp_iter != 0 && PySequence_Check(objPtr)) return objPtr;
|
||||
if (PySequence_Check(objPtr)) return objPtr;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -141,55 +138,15 @@ namespace utils {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Container>*)data)->storage.bytes;
|
||||
Container* result = new (storage) Container();
|
||||
bpy::list source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
bpy::stl_input_iterator<value_type> begin(source), end;
|
||||
std::copy(begin, end, std::inserter(*result, result->begin()));
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Optional>
|
||||
struct optional_to_python {
|
||||
static PyObject* convert(const Optional& optional) {
|
||||
if (optional) {
|
||||
return bpy::incref(bpy::object(*optional).ptr());
|
||||
}
|
||||
else {
|
||||
return bpy::incref(Py_None);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <class Optional>
|
||||
struct optional_from_python {
|
||||
|
||||
using value_type = typename Optional::value_type;
|
||||
|
||||
static void* convertible(PyObject* objPtr) {
|
||||
|
||||
if (objPtr == Py_None) {
|
||||
return objPtr;
|
||||
}
|
||||
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
return bpy::extract<value_type>(source).check() ? objPtr : nullptr;
|
||||
}
|
||||
|
||||
static void construct(PyObject* objPtr, bpy::converter::rvalue_from_python_stage1_data* data) {
|
||||
void* storage = ((bpy::converter::rvalue_from_python_storage<Optional>*)data)->storage.bytes;
|
||||
Optional* result = new (storage) Optional();
|
||||
|
||||
bpy::object source(bpy::handle<>(bpy::borrowed(objPtr)));
|
||||
if (!source.is_none()) {
|
||||
*result = bpy::extract<value_type>(source)();
|
||||
int length = bpy::len(source);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
result->insert(bpy::extract<value_type>(source[i]));
|
||||
}
|
||||
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for (at least) map, unordered_map and QMap.
|
||||
*
|
||||
@@ -238,34 +195,6 @@ namespace utils {
|
||||
, bpy::type_id<Container>());
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Register from and to python converters for optional.
|
||||
*
|
||||
* @tparam T The optional type (std::optional<X> or boost::optional<X>).
|
||||
*/
|
||||
template <class Optional>
|
||||
void register_optional() {
|
||||
bpy::to_python_converter<Optional, optional_to_python<Optional>>();
|
||||
bpy::converter::registry::push_back(
|
||||
&optional_from_python<Optional>::convertible
|
||||
, &optional_from_python<Optional>::construct
|
||||
, bpy::type_id<Optional>());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Show a deprecation warning.
|
||||
*
|
||||
* This methods will print a warning in MO2 log containing the location of the call to
|
||||
* the deprecated function. If show_once is true, the deprecation warning will only be
|
||||
* logged the first time the function is called at this location.
|
||||
*
|
||||
* @param name Name of the deprecated function.
|
||||
* @param message Deprecation message.
|
||||
* @param show_once Only show the message once per call location.
|
||||
*/
|
||||
void show_deprecation_warning(std::string_view name, std::string_view message = "", bool show_once = true);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -19,10 +19,8 @@ namespace details {
|
||||
*/
|
||||
template <class ReturnType, class WrapperTypePtr, class Fn, class... Args>
|
||||
ReturnType wrapperFunctionImplementation(WrapperTypePtr wrapper, bool apiTransfer, Fn fn, boost::python::object* objPtr, const char *methodName, Args... args) {
|
||||
boost::python::override implementation = [&]() {
|
||||
GILock lock;
|
||||
return wrapper->get_override(methodName);
|
||||
}();
|
||||
GILock lock;
|
||||
boost::python::override implementation = wrapper->get_override(methodName);
|
||||
if (!implementation) {
|
||||
if constexpr (std::is_same_v<Fn, std::nullptr_t>) {
|
||||
throw pyexcept::MissingImplementation(wrapper->className, methodName);
|
||||
@@ -31,8 +29,6 @@ namespace details {
|
||||
return std::invoke(fn, wrapper, args...);
|
||||
}
|
||||
}
|
||||
|
||||
GILock lock;
|
||||
try {
|
||||
boost::python::object result = implementation(args...);
|
||||
if (objPtr) {
|
||||
@@ -143,30 +139,4 @@ ReturnType basicWrapperFunctionImplementationWithDefault(WrapperTypePtr wrapper,
|
||||
return details::wrapperFunctionImplementation<ReturnType>(wrapper, false, fn, nullptr, methodName, args...);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Call the given method on the wrapper with the given arguments, with proper
|
||||
* exception handling, and store the intermediate result in the given python object,
|
||||
* falling back to the given function if the method does not exist.
|
||||
*
|
||||
* @param wrapper The wrapper object to use to retrieve the python method. Must have a publicly
|
||||
* available `className` attribute.
|
||||
* @param fn The function to call if the method does not exists.
|
||||
* @param ref Python object to which the result of `get_override()` should be stored.
|
||||
* @param methodName The name of the method.
|
||||
* @param args... Arguments for the method.
|
||||
*
|
||||
* Note: `fn` does not have to be a member-function of `wrapper` but `std::invoke(fn, wrapper, args...)` must be valid.
|
||||
*
|
||||
* @return the result of calling the given Python method on the wrapper.
|
||||
*
|
||||
* @throw pyexcept::PythonError if an error occurs while executing the python method.
|
||||
* @throw pyexecpt::UnknownException if an unknown error occurs.
|
||||
*/
|
||||
template <class ReturnType, class WrapperType, class Fn, class... Args>
|
||||
ReturnType basicWrapperFunctionImplementationWithDefault(const WrapperType* wrapper, Fn fn, boost::python::object& ref, const char* methodName, Args... args)
|
||||
{
|
||||
return details::wrapperFunctionImplementation<ReturnType>(wrapper, false, fn, &ref, methodName, args...);
|
||||
}
|
||||
|
||||
|
||||
#endif // PYTHONWRAPPERUTILITIES_H
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
#ifndef PYTHONRUNNER_SHARED_PTR_CONVERTER_H
|
||||
#define PYTHONRUNNER_SHARED_PTR_CONVERTER_H
|
||||
|
||||
#include <boost/python.hpp>
|
||||
|
||||
#include "error.h"
|
||||
#include "gilock.h"
|
||||
|
||||
namespace utils {
|
||||
|
||||
// Shared pointers are handled in a special way by Boost.Python since they hold
|
||||
// the wrapped Python object and only release it when the ref counter of the shared
|
||||
// ptr drops to 0 using shared_ptr_deleter.
|
||||
//
|
||||
// Unfortunately for us, this will happen outside of the Python proxy for some objects
|
||||
// and thus without the GIL lock, making everything crash, so we need a custom deleter
|
||||
// that holds the GIL while releasing the lock.
|
||||
//
|
||||
// Note that this is only useful for Python -> C++ conversion, and without this, Boost
|
||||
// will automatically wrapped the pointer. The C++ -> Python conversion is handled
|
||||
// separately by boost::python::register_ptr_to_python.
|
||||
//
|
||||
// This is an open Boost.Python problem: https://github.com/boostorg/python/pull/11
|
||||
|
||||
template <class SharedPtr>
|
||||
struct shared_ptr_from_python;
|
||||
|
||||
namespace details {
|
||||
|
||||
struct shared_ptr_deleter_with_gil_lock : boost::python::converter::shared_ptr_deleter {
|
||||
|
||||
using shared_ptr_deleter::shared_ptr_deleter;
|
||||
|
||||
void operator()(void const* o) {
|
||||
GILock lock;
|
||||
shared_ptr_deleter::operator()(o);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <class SharedPtr>
|
||||
struct shared_ptr_void;
|
||||
|
||||
template <class T>
|
||||
struct shared_ptr_void<std::shared_ptr<T>> { using type = std::shared_ptr<void>; };
|
||||
|
||||
template <class T>
|
||||
struct shared_ptr_void<boost::shared_ptr<T>> { using type = boost::shared_ptr<void>; };
|
||||
|
||||
template <class SharedPtr>
|
||||
using shared_ptr_void_t = typename shared_ptr_void<SharedPtr>::type;
|
||||
|
||||
}
|
||||
|
||||
template <class SharedPtr>
|
||||
struct shared_ptr_from_python
|
||||
{
|
||||
using T = typename SharedPtr::element_type;
|
||||
|
||||
shared_ptr_from_python()
|
||||
{
|
||||
using namespace boost::python;
|
||||
converter::registry::insert(&convertible, &construct, type_id<SharedPtr>()
|
||||
#ifndef BOOST_PYTHON_NO_PY_SIGNATURES
|
||||
, &converter::expected_from_python_type_direct<T>::get_pytype
|
||||
#endif
|
||||
);
|
||||
}
|
||||
|
||||
private:
|
||||
static void* convertible(PyObject* p)
|
||||
{
|
||||
if (p == Py_None)
|
||||
return p;
|
||||
|
||||
return boost::python::converter::get_lvalue_from_python(p, boost::python::converter::registered<T>::converters);
|
||||
}
|
||||
|
||||
static void construct(PyObject* source, boost::python::converter::rvalue_from_python_stage1_data* data)
|
||||
{
|
||||
using namespace boost::python;
|
||||
void* const storage = ((converter::rvalue_from_python_storage<SharedPtr>*)data)->storage.bytes;
|
||||
// Deal with the "None" case.
|
||||
if (data->convertible == source)
|
||||
new (storage) SharedPtr();
|
||||
else
|
||||
{
|
||||
details::shared_ptr_void_t<SharedPtr> hold_convertible_ref_count(
|
||||
(void*)0, details::shared_ptr_deleter_with_gil_lock(handle<>(borrowed(source))));
|
||||
// use aliasing constructor
|
||||
new (storage) SharedPtr(hold_convertible_ref_count,
|
||||
static_cast<T*>(data->convertible));
|
||||
}
|
||||
|
||||
data->convertible = storage;
|
||||
}
|
||||
};
|
||||
|
||||
// release the bpy::object associated with the deleter of the given shared_ptr,
|
||||
// if the given shared_ptr has a Boost.Python deleter
|
||||
//
|
||||
// this should only be used when returning from Python objects that have been created
|
||||
// on the C++ side, e.g. if IFileTree.createOrphanTree() from Python and then return
|
||||
// the tree
|
||||
//
|
||||
// for reason yet to be known, Boost.Python had a custom deleter in this case that tries
|
||||
// to delete the bpy::object and fails, so we have to release the object manually
|
||||
//
|
||||
template <class SharedPtr>
|
||||
SharedPtr clean_shared_ptr(SharedPtr&& ptr) {
|
||||
if (auto* d = get_deleter<boost::python::converter::shared_ptr_deleter>(ptr); d != nullptr) {
|
||||
// we cannot do a proper reset() here, even with the GIL lock, for unknown reason,
|
||||
// so we only release
|
||||
//
|
||||
// this might create lost references to Python object but this should not happen
|
||||
// too often so hopefully it's not a big issue
|
||||
//
|
||||
d->owner.release();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user