aboutsummaryrefslogtreecommitdiff
path: root/libs/plugin_python/src/proxy
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-11 02:37:39 -0600
commit7ee008e150bc5bcf76082d726f719ee0fdfda982 (patch)
tree27fb39be241fdb5ac2734c574de678977d1856d0 /libs/plugin_python/src/proxy
Fluorine Manager: full Linux port of Mod Organizer 2
Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/plugin_python/src/proxy')
-rw-r--r--libs/plugin_python/src/proxy/CMakeLists.txt118
-rw-r--r--libs/plugin_python/src/proxy/build_pythoncore.py15
-rw-r--r--libs/plugin_python/src/proxy/proxypython.cpp368
-rw-r--r--libs/plugin_python/src/proxy/proxypython.h80
4 files changed, 581 insertions, 0 deletions
diff --git a/libs/plugin_python/src/proxy/CMakeLists.txt b/libs/plugin_python/src/proxy/CMakeLists.txt
new file mode 100644
index 0000000..ed212a3
--- /dev/null
+++ b/libs/plugin_python/src/proxy/CMakeLists.txt
@@ -0,0 +1,118 @@
+cmake_minimum_required(VERSION 3.16)
+
+if(NOT TARGET mo2::uibase)
+ find_package(mo2-uibase CONFIG REQUIRED)
+endif()
+
+set(PLUGIN_NAME "plugin_python")
+
+add_library(proxy SHARED proxypython.cpp proxypython.h)
+mo2_configure_plugin(proxy
+ NO_SOURCES
+ WARNINGS 4
+ EXTERNAL_WARNINGS 4
+ TRANSLATIONS OFF
+ EXTRA_TRANSLATIONS
+ ${CMAKE_CURRENT_SOURCE_DIR}/../runner
+ ${CMAKE_CURRENT_SOURCE_DIR}/../mobase
+ ${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
+mo2_default_source_group()
+target_link_libraries(proxy PRIVATE runner mo2::uibase)
+if(NOT WIN32)
+ target_compile_definitions(proxy PRIVATE
+ MO2_PYTHON_PURELIB_DIR="${Python_PURELIB_DIR}"
+ MO2_PYTHON_PLATLIB_DIR="${Python_PLATLIB_DIR}")
+endif()
+set_target_properties(proxy PROPERTIES OUTPUT_NAME ${PLUGIN_NAME})
+mo2_install_plugin(proxy FOLDER)
+
+set(PLUGIN_PYTHON_DIR bin/plugins/${PLUGIN_NAME})
+
+# install runner
+if(WIN32)
+ target_link_options(proxy PRIVATE "/DELAYLOAD:runner.dll")
+ install(FILES $<TARGET_FILE:runner> DESTINATION ${PLUGIN_PYTHON_DIR}/dlls)
+endif()
+
+# translations (custom location)
+mo2_add_translations(proxy
+ TS_FILE ${CMAKE_CURRENT_SOURCE_DIR}/../${PLUGIN_NAME}_en.ts
+ SOURCES
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../runner
+ ${CMAKE_CURRENT_SOURCE_DIR}/../mobase
+ ${CMAKE_CURRENT_SOURCE_DIR}/../pybind11-qt)
+
+# install DLLs files needed
+set(DLL_DIRS ${PLUGIN_PYTHON_DIR}/dlls)
+if(WIN32)
+ file(GLOB dlls_to_install
+ ${Python_HOME}/dlls/libffi*.dll
+ ${Python_HOME}/dlls/sqlite*.dll
+ ${Python_HOME}/dlls/libssl*.dll
+ ${Python_HOME}/dlls/libcrypto*.dll
+ ${Python_HOME}/python${Python_VERSION_MAJOR}*.dll)
+ install(FILES ${dlls_to_install} DESTINATION ${DLL_DIRS})
+endif()
+
+# install Python .pyd files
+set(PYLIB_DIR ${PLUGIN_PYTHON_DIR}/libs)
+if(WIN32)
+ file(GLOB libs_to_install ${Python_DLL_DIR}/*.pyd)
+ install(FILES ${libs_to_install} DESTINATION ${PYLIB_DIR})
+endif()
+
+# generate + install standard library
+if(WIN32)
+ set(pythoncore_zip "${CMAKE_CURRENT_BINARY_DIR}/pythoncore.zip")
+ add_custom_command(
+ TARGET proxy POST_BUILD
+ COMMAND ${Python_EXECUTABLE}
+ "${CMAKE_CURRENT_SOURCE_DIR}/build_pythoncore.py"
+ ${pythoncore_zip}
+ )
+ install(FILES ${pythoncore_zip} DESTINATION ${PYLIB_DIR})
+endif()
+
+# install mobase
+install(TARGETS mobase DESTINATION ${PYLIB_DIR})
+
+# install PyQt6
+if(WIN32)
+ install(
+ DIRECTORY ${CMAKE_BINARY_DIR}/pylibs/PyQt${MO2_QT_VERSION_MAJOR}
+ DESTINATION ${PYLIB_DIR}
+ PATTERN "*.pyd"
+ PATTERN "*.pyi"
+ PATTERN "__pycache__" EXCLUDE
+ PATTERN "bindings" EXCLUDE
+ PATTERN "lupdate" EXCLUDE
+ PATTERN "Qt6" EXCLUDE
+ PATTERN "uic" EXCLUDE
+ )
+endif()
+
+if(NOT WIN32)
+ # Build-tree runtime layout expected by proxypython.cpp.
+ add_custom_command(TARGET proxy POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/src/src/plugins"
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/src/src/plugins/dlls"
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/src/src/plugins/libs"
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/src/src/plugins/plugin_python/dlls"
+ COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/src/src/plugins/plugin_python/libs"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "$<TARGET_FILE:proxy>"
+ "${CMAKE_BINARY_DIR}/src/src/plugins/$<TARGET_FILE_NAME:proxy>"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "$<TARGET_FILE:runner>"
+ "${CMAKE_BINARY_DIR}/src/src/plugins/dlls/$<TARGET_FILE_NAME:runner>"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "$<TARGET_FILE:runner>"
+ "${CMAKE_BINARY_DIR}/src/src/plugins/plugin_python/dlls/$<TARGET_FILE_NAME:runner>"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "$<TARGET_FILE:mobase>"
+ "${CMAKE_BINARY_DIR}/src/src/plugins/libs/$<TARGET_FILE_NAME:mobase>"
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different
+ "$<TARGET_FILE:mobase>"
+ "${CMAKE_BINARY_DIR}/src/src/plugins/plugin_python/libs/$<TARGET_FILE_NAME:mobase>")
+endif()
diff --git a/libs/plugin_python/src/proxy/build_pythoncore.py b/libs/plugin_python/src/proxy/build_pythoncore.py
new file mode 100644
index 0000000..01c42f9
--- /dev/null
+++ b/libs/plugin_python/src/proxy/build_pythoncore.py
@@ -0,0 +1,15 @@
+import sys
+import sysconfig
+import zipfile
+from pathlib import Path
+
+_EXCLUDE_MODULES = ["ensurepip", "idlelib", "test", "tkinter", "turtle_demo", "venv"]
+
+libdir = Path(sysconfig.get_path("stdlib"))
+assert libdir.exists()
+
+with zipfile.PyZipFile(sys.argv[1], optimize=2, mode="w") as fp:
+ fp.writepy(libdir) # pyright: ignore[reportArgumentType]
+ for path in libdir.iterdir():
+ if path.is_dir() and path.name not in _EXCLUDE_MODULES:
+ fp.writepy(path) # pyright: ignore[reportArgumentType]
diff --git a/libs/plugin_python/src/proxy/proxypython.cpp b/libs/plugin_python/src/proxy/proxypython.cpp
new file mode 100644
index 0000000..af897db
--- /dev/null
+++ b/libs/plugin_python/src/proxy/proxypython.cpp
@@ -0,0 +1,368 @@
+/*
+Copyright (C) 2022 Sebastian Herbord & MO2 Team. All rights reserved.
+
+This file is part of python proxy plugin for MO
+
+python proxy plugin is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Python proxy plugin is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "proxypython.h"
+
+#include <filesystem>
+#include <cerrno>
+#include <cstring>
+#include <cstdlib>
+
+#ifdef _WIN32
+#include <Windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+#include <QCoreApplication>
+#include <QDirIterator>
+#include <QMessageBox>
+#include <QWidget>
+#include <QtPlugin>
+
+#include <uibase/log.h>
+#include <uibase/utility.h>
+#include <uibase/versioninfo.h>
+
+namespace fs = std::filesystem;
+using namespace MOBase;
+
+// retrieve the path to the folder containing the proxy DLL
+fs::path getPluginFolder()
+{
+#ifdef _WIN32
+ wchar_t path[MAX_PATH];
+ HMODULE hm = NULL;
+
+ if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ (LPCWSTR)&getPluginFolder, &hm) == 0) {
+ return {};
+ }
+ if (GetModuleFileName(hm, path, sizeof(path)) == 0) {
+ return {};
+ }
+
+ return fs::path(path).parent_path();
+#else
+ Dl_info info;
+ if (dladdr((void*)&getPluginFolder, &info) == 0 || info.dli_fname == nullptr) {
+ return {};
+ }
+ return fs::path(info.dli_fname).parent_path();
+#endif
+}
+
+ProxyPython::ProxyPython()
+ : m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr},
+ m_LoadFailure(FailureType::NONE)
+{
+}
+
+bool ProxyPython::init(IOrganizer* moInfo)
+{
+ m_MOInfo = moInfo;
+
+ if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
+ return false;
+ }
+
+ if (QCoreApplication::applicationDirPath().contains(';')) {
+ m_LoadFailure = FailureType::SEMICOLON;
+ return true;
+ }
+
+ const auto pluginFolder = getPluginFolder();
+#ifdef _WIN32
+ const auto pluginDataRoot =
+ fs::exists(pluginFolder / "plugin_python") ? (pluginFolder / "plugin_python")
+ : pluginFolder;
+#else
+ // Linux portable installs can contain a legacy Windows "plugin_python"
+ // payload (pythoncore.zip/.pyd) that is incompatible with our bundled
+ // Linux runtime. Always use the plugin root on Linux.
+ const auto pluginDataRoot = pluginFolder;
+#endif
+
+ if (pluginFolder.empty()) {
+#ifdef _WIN32
+ DWORD error = ::GetLastError();
+ m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ log::error("failed to resolve Python proxy directory ({}): {}", error,
+ qUtf8Printable(windowsErrorString(::GetLastError())));
+#else
+ m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ log::error("failed to resolve Python proxy directory: {}",
+ std::strerror(errno));
+#endif
+ return false;
+ }
+
+ 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;
+ }
+ }
+
+ if (m_MOInfo) {
+ m_MOInfo->setPersistent(name(), "tryInit", true);
+ }
+
+ // load the pythonrunner library, this is done in multiple steps:
+ //
+ // 1. we set the dlls/ subfolder (from the plugin) as the DLL directory so Windows
+ // will look for DLLs in it, this is required to find the Python and libffi DLL, but
+ // also the runner DLL
+ //
+ const auto dllPaths = pluginDataRoot / "dlls";
+#ifdef _WIN32
+ if (SetDllDirectoryW(dllPaths.c_str()) == 0) {
+ DWORD error = ::GetLastError();
+ m_LoadFailure = FailureType::DLL_NOT_FOUND;
+ log::error("failed to add python DLL directory ({}): {}", error,
+ qUtf8Printable(windowsErrorString(::GetLastError())));
+ return false;
+ }
+#endif
+
+ // 2. we create the Python runner, we do not need to use ::LinkLibrary and
+ // ::GetProcAddress because we use delayed load for the runner DLL (see the
+ // CMakeLists.txt)
+ //
+ m_Runner = mo2::python::createPythonRunner();
+
+ if (m_Runner) {
+ const auto libpath = pluginDataRoot / "libs";
+#ifdef _WIN32
+ const std::vector<fs::path> paths{
+ libpath / "pythoncore.zip", libpath,
+ std::filesystem::path{IOrganizer::getPluginDataPath().toStdWString()}};
+ m_Runner->initialize(paths);
+#else
+ // On Linux, rely on the unpacked stdlib from PYTHONHOME (AppRun sets
+ // MO2_PYTHON_DIR/PYTHONHOME). Do not prepend pythoncore.zip here:
+ // forcing zipimport can fail when zlib is unavailable in embedded mode.
+ std::vector<fs::path> paths{
+ libpath,
+ std::filesystem::path{IOrganizer::getPluginDataPath().toStdWString()}};
+
+ // Allow portable/AppImage builds to ship Python packages next to the app.
+ // MO2_PYTHON_DIR (set by AppRun) points to the writable python/ dir
+ // next to the AppImage; fall back to <exe_dir>/python.
+ fs::path pythonDir;
+ const char* envPy = std::getenv("MO2_PYTHON_DIR");
+ if (envPy && envPy[0] != '\0') {
+ pythonDir = fs::path(envPy);
+ } else {
+ pythonDir = fs::path(QCoreApplication::applicationDirPath().toStdWString()) / "python";
+ }
+ paths.emplace_back(pythonDir / "site-packages");
+ paths.emplace_back(pythonDir);
+
+#ifdef MO2_PYTHON_PURELIB_DIR
+ if (std::strlen(MO2_PYTHON_PURELIB_DIR) > 0) {
+ paths.emplace_back(MO2_PYTHON_PURELIB_DIR);
+ }
+#endif
+#ifdef MO2_PYTHON_PLATLIB_DIR
+ if (std::strlen(MO2_PYTHON_PLATLIB_DIR) > 0 &&
+ std::strcmp(MO2_PYTHON_PLATLIB_DIR, MO2_PYTHON_PURELIB_DIR) != 0) {
+ paths.emplace_back(MO2_PYTHON_PLATLIB_DIR);
+ }
+#endif
+ m_Runner->initialize(paths);
+#endif
+ }
+
+ if (m_MOInfo) {
+ m_MOInfo->setPersistent(name(), "tryInit", false);
+ }
+
+ // reset DLL directory
+#ifdef _WIN32
+ SetDllDirectoryW(NULL);
+#endif
+
+ if (!m_Runner || !m_Runner->isInitialized()) {
+ m_LoadFailure = FailureType::INITIALIZATION;
+ }
+ else {
+ m_Runner->addDllSearchPath(pluginDataRoot / "dlls");
+ }
+
+ return true;
+}
+
+QString ProxyPython::name() const
+{
+ return "Python Proxy";
+}
+
+QString ProxyPython::localizedName() const
+{
+ return tr("Python Proxy");
+}
+
+QString ProxyPython::author() const
+{
+ return "AnyOldName3, Holt59, Silarn, Tannin";
+}
+
+QString ProxyPython::description() const
+{
+ return tr("Proxy Plugin to allow plugins written in python to be loaded");
+}
+
+VersionInfo ProxyPython::version() const
+{
+ return VersionInfo(3, 0, 0, VersionInfo::RELEASE_FINAL);
+}
+
+QList<PluginSetting> ProxyPython::settings() const
+{
+ return {};
+}
+
+QStringList ProxyPython::pluginList(const QDir& pluginPath) const
+{
+ QDir dir(pluginPath);
+ dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
+ QDirIterator iter(dir);
+
+ // Note: We put python script (.py) and directory names, not the __init__.py
+ // files in those since it is easier for the runner to import them.
+ QStringList result;
+ while (iter.hasNext()) {
+ QString name = iter.next();
+ QFileInfo info = iter.fileInfo();
+ const QString baseName = info.fileName();
+
+ if (info.isFile() && name.endsWith(".py")) {
+ // Compatibility shims and disabled plugins staged on Linux; they are not
+ // loaded as MO2 plugins.
+ if (baseName == "winreg.py" || baseName == "lzokay.py" ||
+ baseName == "FNISPatches.py" || baseName == "FNISTool.py" ||
+ baseName == "FNISToolReset.py") {
+ continue;
+ }
+ result.append(name);
+ }
+ else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
+ result.append(name);
+ }
+ }
+
+ return result;
+}
+
+QList<QObject*> ProxyPython::load(const QString& identifier)
+{
+ if (!m_Runner) {
+ return {};
+ }
+ 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->isInitialized()) {
+ failure = FailureType::INITIALIZATION;
+ }
+
+ if (failure != FailureType::NONE) {
+ return {static_cast<std::underlying_type_t<FailureType>>(failure)};
+ }
+
+ return {};
+}
+
+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:
+ return tr("Initializing Python failed");
+ default:
+ return 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 applications 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);
+ }
+}
+
+bool ProxyPython::hasGuidedFix(unsigned int) const
+{
+ return false;
+}
+
+void ProxyPython::startGuidedFix(unsigned int) const {}
diff --git a/libs/plugin_python/src/proxy/proxypython.h b/libs/plugin_python/src/proxy/proxypython.h
new file mode 100644
index 0000000..e824e2a
--- /dev/null
+++ b/libs/plugin_python/src/proxy/proxypython.h
@@ -0,0 +1,80 @@
+/*
+Copyright (C) 2022 Sebastian Herbord & MO2 Team. All rights reserved.
+
+This file is part of python proxy plugin for MO
+
+python proxy plugin is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Python proxy plugin is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with python proxy plugin. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PROXYPYTHON_H
+#define PROXYPYTHON_H
+
+#include <map>
+#include <memory>
+
+#include <uibase/iplugindiagnose.h>
+#include <uibase/ipluginproxy.h>
+
+#include <pythonrunner.h>
+
+class ProxyPython : public QObject,
+ public MOBase::IPluginProxy,
+ public MOBase::IPluginDiagnose {
+ Q_OBJECT
+ Q_INTERFACES(MOBase::IPlugin MOBase::IPluginProxy MOBase::IPluginDiagnose)
+ Q_PLUGIN_METADATA(IID "org.mo2.ProxyPython")
+
+public:
+ 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;
+
+ QStringList pluginList(const QDir& pluginPath) const override;
+ QList<QObject*> load(const QString& identifier) override;
+ void unload(const QString& identifier) override;
+
+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;
+
+private:
+ MOBase::IOrganizer* m_MOInfo;
+#ifdef _WIN32
+ HMODULE m_RunnerLib;
+#else
+ void* m_RunnerLib;
+#endif
+ std::unique_ptr<mo2::python::IPythonRunner> m_Runner;
+
+ enum class FailureType : unsigned int {
+ NONE = 0,
+ SEMICOLON = 1,
+ DLL_NOT_FOUND = 2,
+ INVALID_DLL = 3,
+ INITIALIZATION = 4
+ };
+
+ FailureType m_LoadFailure;
+};
+
+#endif // PROXYPYTHON_H