Continue implementing plugins.

This commit is contained in:
Mikaël Capelle
2022-04-25 21:21:06 +02:00
parent b92e3de535
commit 5ec456f880
6 changed files with 850 additions and 177 deletions
+12 -12
View File
@@ -4,58 +4,58 @@
<context>
<name>ProxyPython</name>
<message>
<location filename="proxypython.cpp" line="118"/>
<location filename="proxypython.cpp" line="120"/>
<source>Python Proxy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="128"/>
<location filename="proxypython.cpp" line="130"/>
<source>Proxy Plugin to allow plugins written in python to be loaded</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="204"/>
<location filename="proxypython.cpp" line="206"/>
<source>ModOrganizer path contains a semicolon</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="206"/>
<location filename="proxypython.cpp" line="208"/>
<source>Python DLL not found</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="208"/>
<location filename="proxypython.cpp" line="210"/>
<source>Invalid Python DLL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="210"/>
<location filename="proxypython.cpp" line="212"/>
<source>Initializing Python failed</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="212"/>
<location filename="proxypython.cpp" line="234"/>
<location filename="proxypython.cpp" line="214"/>
<location filename="proxypython.cpp" line="244"/>
<source>invalid problem key %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="221"/>
<location filename="proxypython.cpp" line="222"/>
<source>The path to Mod Organizer (%1) contains a semicolon. &lt;br&gt;While this is legal on NTFS drives, many softwares do not handle it correctly.&lt;br&gt;Unfortunately MO depends on libraries that seem to fall into that group.&lt;br&gt;As a result the python plugin cannot be loaded, and the only solution we canoffer is to remove the semicolon or move MO to a path without a semicolon.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="227"/>
<location filename="proxypython.cpp" line="233"/>
<source>The Python plugin DLL was not found, maybe your antivirus deleted it. Re-installing MO2 might fix the problem.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="229"/>
<location filename="proxypython.cpp" line="236"/>
<source>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.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="proxypython.cpp" line="232"/>
<location filename="proxypython.cpp" line="241"/>
<source>The initialization of the Python plugin DLL failed, unfortunately without any details.</source>
<translation type="unfinished"></translation>
</message>
+152 -144
View File
@@ -17,229 +17,237 @@ 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 "log.h"
#include <QCoreApplication>
#include <QDirIterator>
#include <QMessageBox>
#include <QWidget>
#include <QtPlugin>
#include <utility.h>
#include <versioninfo.h>
#include <QtPlugin>
#include <QDirIterator>
#include <QWidget>
#include <QMessageBox>
#include <QCoreApplication>
#include "log.h"
using namespace MOBase;
ProxyPython::ProxyPython()
: m_MOInfo{ nullptr },
m_RunnerLib{ nullptr },
m_Runner{ nullptr },
m_LoadFailure(FailureType::NONE)
: m_MOInfo{nullptr}, m_RunnerLib{nullptr}, m_Runner{nullptr},
m_LoadFailure(FailureType::NONE)
{
}
bool ProxyPython::init(IOrganizer *moInfo)
bool ProxyPython::init(IOrganizer* moInfo)
{
using CreatePythonRunner_func = IPythonRunner * (*)();
using CreatePythonRunner_func = IPythonRunner* (*)();
m_MOInfo = moInfo;
m_MOInfo = moInfo;
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
return false;
}
if (QCoreApplication::applicationDirPath().contains(';')) {
m_LoadFailure = FailureType::SEMICOLON;
return true;
}
// load the pythonrunner library
m_RunnerLib = ::LoadLibraryW(QDir::toNativeSeparators(
IOrganizer::getPluginDataPath() + "/pythonRunner.dll").toStdWString().c_str());
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;
if (m_MOInfo && !m_MOInfo->isPluginEnabled(this)) {
return false;
}
else {
m_LoadFailure = FailureType::INVALID_DLL;
if (QCoreApplication::applicationDirPath().contains(';')) {
m_LoadFailure = FailureType::SEMICOLON;
return true;
}
// load the pythonrunner library
m_RunnerLib = ::LoadLibraryW(
QDir::toNativeSeparators(IOrganizer::getPluginDataPath() + "/pythonRunner.dll")
.toStdWString()
.c_str());
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;
}
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;
// }
}
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", true);
}
m_Runner = std::unique_ptr<IPythonRunner>{createPythonRunner()};
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", false);
}
if (!m_Runner) {
m_LoadFailure = FailureType::INITIALIZATION;
}
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;
// }
}
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", true);
}
m_Runner = std::unique_ptr<IPythonRunner>{ createPythonRunner() };
if (m_MOInfo) {
m_MOInfo->setPersistent(name(), "tryInit", false);
}
if (!m_Runner) {
m_LoadFailure = FailureType::INITIALIZATION;
}
return true;
}
QString ProxyPython::name() const
{
return "Python Proxy";
return "Python Proxy";
}
QString ProxyPython::localizedName() const
{
return tr("Python Proxy");
return tr("Python Proxy");
}
QString ProxyPython::author() const
{
return "AnyOldName3, Holt59, Silarn, Tannin";
return "AnyOldName3, Holt59, Silarn, Tannin";
}
QString ProxyPython::description() const
{
return tr("Proxy Plugin to allow plugins written in python to be loaded");
return tr("Proxy Plugin to allow plugins written in python to be loaded");
}
VersionInfo ProxyPython::version() const
{
return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
return VersionInfo(2, 3, 0, VersionInfo::RELEASE_FINAL);
}
QList<PluginSetting> ProxyPython::settings() const
{
return {};
return {};
}
QStringList ProxyPython::pluginList(const QDir& pluginPath) const
{
QDir dir(pluginPath);
dir.setFilter(dir.filter() | QDir::NoDotAndDotDot);
QDirIterator iter(dir);
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();
// 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();
if (info.fileName() == "test.py") {
result.append(name);
if (info.fileName() == "pyCfg.py") {
result.append(name);
}
if (info.isFile() && name.endsWith(".py")) {
// result.append(name);
}
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
// result.append(name);
}
}
if (info.isFile() && name.endsWith(".py")) {
// result.append(name);
}
else if (info.isDir() && QDir(info.absoluteFilePath()).exists("__init__.py")) {
// result.append(name);
}
}
return result;
return result;
}
QList<QObject*> ProxyPython::load(const QString& identifier)
{
if (!m_Runner) {
return {};
}
return m_Runner->load(identifier);
if (!m_Runner) {
return {};
}
return m_Runner->load(identifier);
}
void ProxyPython::unload(const QString& identifier)
{
if (m_Runner) {
return m_Runner->unload(identifier);
}
if (m_Runner) {
return m_Runner->unload(identifier);
}
}
std::vector<unsigned int> ProxyPython::activeProblems() const
{
auto failure = m_LoadFailure;
auto failure = m_LoadFailure;
// don't know how this could happen but wth
if (m_Runner && !m_Runner->isPythonInitialized()) {
failure = FailureType::INITIALIZATION;
}
// don't know how this could happen but wth
if (m_Runner && !m_Runner->isPythonInitialized()) {
failure = FailureType::INITIALIZATION;
}
if (failure != FailureType::NONE) {
return { static_cast<std::underlying_type_t<FailureType>>(failure) };
}
if (failure != FailureType::NONE) {
return {static_cast<std::underlying_type_t<FailureType>>(failure)};
}
return {};
return {};
}
QString ProxyPython::shortDescription(unsigned int key) const
{
switch (static_cast<FailureType>(key)) {
switch (static_cast<FailureType>(key)) {
case FailureType::SEMICOLON:
return tr("ModOrganizer path contains a semicolon");
return tr("ModOrganizer path contains a semicolon");
case FailureType::DLL_NOT_FOUND:
return tr("Python DLL not found");
return tr("Python DLL not found");
case FailureType::INVALID_DLL:
return tr("Invalid Python DLL");
return tr("Invalid Python DLL");
case FailureType::INITIALIZATION:
return tr("Initializing Python failed");
return tr("Initializing Python failed");
default:
return tr("invalid problem key %1").arg(key);
}
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 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 (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);
}
}
bool ProxyPython::hasGuidedFix(unsigned int key) const
{
return false;
return false;
}
void ProxyPython::startGuidedFix(unsigned int key) const
{
}
void ProxyPython::startGuidedFix(unsigned int key) const {}
+13 -19
View File
@@ -300,19 +300,19 @@ PythonRunner::PythonRunner() {}
PythonRunner::~PythonRunner()
{
// We need the GIL lock when destroying Python objects.
py::gil_scoped_acquire lock;
// m_Interpreter.reset();
// Boost.Python does not handle cyclic garbace collection, so we need to
// release everything hold by the objects before deleting the objects
// themselves: for (auto& [name, objects] : m_PythonObjects) {
// for (auto& obj : objects) {
// obj.attr("__dict__").attr("clear")();
// }
// Boost.Python does not handle cyclic garbace collection, so we need to release
// everything hold by the objects before deleting the objects themselves:
// for (auto& [name, objects] : m_PythonObjects) {
// for (auto& obj : objects) {
// obj.attr("__dict__").attr("clear")();
// }
// }
// m_PythonObjects.clear();
// we need to clear this here otherwise there is a crash in
// finalize_interpreter()
m_PythonObjects.clear();
py::finalize_interpreter();
}
// ErrWrapper is in error.h
@@ -331,13 +331,6 @@ bool PythonRunner::initPython()
try {
static const char* argv0 = "ModOrganizer.exe";
// we initialize the interpreter "the old way" because
// scoped_interpreter does not seem to work well with PyQt
wchar_t argBuffer[MAX_PATH];
const size_t cSize = strlen(argv0) + 1;
mbstowcs(argBuffer, argv0, MAX_PATH);
Py_SetProgramName(argBuffer);
PyImport_AppendInittab("mobase", &PyInit_mobase);
PyImport_AppendInittab("moprivate", &PyInit_moprivate);
@@ -345,7 +338,8 @@ bool PythonRunner::initPython()
Py_NoSiteFlag = 1;
initPath();
Py_InitializeEx(0);
py::initialize_interpreter(false, 1, &argv0);
if (!Py_IsInitialized()) {
return false;
+4 -2
View File
@@ -56,13 +56,15 @@ namespace mo2::python {
py::arg("write") = py::cpp_function([](std::string_view message) {
static PrintWrapper wrapper(MOBase::log::Debug);
wrapper.write(message);
}));
}),
py::arg("flush") = py::cpp_function([] {}));
auto errorWrapper = make_python_type(
"MO2ErrorWrapper", py::make_tuple(),
py::arg("write") = py::cpp_function([](std::string_view message) {
static PrintWrapper wrapper(MOBase::log::Error);
wrapper.write(message);
}));
}),
py::arg("flush") = py::cpp_function([] {}));
py::module_ sys = py::module_::import("sys");
sys.attr("stdout") = printWrapper();
sys.attr("stderr") = errorWrapper();
+335
View File
@@ -0,0 +1,335 @@
#include "wrappers.h"
#include "pyplugins.h"
namespace py = pybind11;
using namespace pybind11::literals;
using namespace MOBase;
namespace mo2::python {
// this one is kind of big so it has its own function
void add_iplugingame_bindings(pybind11::module_ m)
{
py::enum_<MOBase::IPluginGame::LoadOrderMechanism>(m, "LoadOrderMechanism")
.value("FileTime", MOBase::IPluginGame::LoadOrderMechanism::FileTime)
.value("PluginsTxt", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt)
.value("FILE_TIME", MOBase::IPluginGame::LoadOrderMechanism::FileTime)
.value("PLUGINS_TXT", MOBase::IPluginGame::LoadOrderMechanism::PluginsTxt);
py::enum_<MOBase::IPluginGame::SortMechanism>(m, "SortMechanism")
.value("NONE", MOBase::IPluginGame::SortMechanism::NONE)
.value("MLOX", MOBase::IPluginGame::SortMechanism::MLOX)
.value("BOSS", MOBase::IPluginGame::SortMechanism::BOSS)
.value("LOOT", MOBase::IPluginGame::SortMechanism::LOOT);
// this does not actually do the conversion, but might be convenient
// for accessing the names for enum bits
py::enum_<MOBase::IPluginGame::ProfileSetting>(m, "ProfileSetting")
.value("mods", MOBase::IPluginGame::MODS)
.value("configuration", MOBase::IPluginGame::CONFIGURATION)
.value("savegames", MOBase::IPluginGame::SAVEGAMES)
.value("preferDefaults", MOBase::IPluginGame::PREFER_DEFAULTS)
.value("MODS", MOBase::IPluginGame::MODS)
.value("CONFIGURATION", MOBase::IPluginGame::CONFIGURATION)
.value("SAVEGAMES", MOBase::IPluginGame::SAVEGAMES)
.value("PREFER_DEFAULTS", MOBase::IPluginGame::PREFER_DEFAULTS);
// py::class_<IPluginGameWrapper, py::bases<IPlugin>,
// boost::noncopyable>("IPluginGame")
// .def("localizedName", &MOBase::IPlugin::localizedName,
// &IPluginGameWrapper::localizedName_Default) .def("master",
// &MOBase::IPlugin::master, &IPluginGameWrapper::master_Default)
// .def("detectGame",
// py::pure_virtual(&MOBase::IPluginGame::detectGame))
// .def("gameName",
// py::pure_virtual(&MOBase::IPluginGame::gameName))
// .def("initializeProfile",
// py::pure_virtual(&MOBase::IPluginGame::initializeProfile),
// (py::arg("directory"), "settings")) .def("listSaves",
// py::pure_virtual(&MOBase::IPluginGame::listSaves),
// py::arg("folder")) .def("isInstalled",
// py::pure_virtual(&MOBase::IPluginGame::isInstalled))
// .def("gameIcon",
// py::pure_virtual(&MOBase::IPluginGame::gameIcon))
// .def("gameDirectory",
// py::pure_virtual(&MOBase::IPluginGame::gameDirectory))
// .def("dataDirectory",
// py::pure_virtual(&MOBase::IPluginGame::dataDirectory))
// .def("setGamePath",
// py::pure_virtual(&MOBase::IPluginGame::setGamePath),
// py::arg("path")) .def("documentsDirectory",
// py::pure_virtual(&MOBase::IPluginGame::documentsDirectory))
// .def("savesDirectory",
// py::pure_virtual(&MOBase::IPluginGame::savesDirectory))
// .def("executables",
// py::pure_virtual(&MOBase::IPluginGame::executables))
// .def("executableForcedLoads",
// py::pure_virtual(&MOBase::IPluginGame::executableForcedLoads))
// .def("steamAPPId",
// py::pure_virtual(&MOBase::IPluginGame::steamAPPId))
// .def("primaryPlugins",
// py::pure_virtual(&MOBase::IPluginGame::primaryPlugins))
// .def("gameVariants",
// py::pure_virtual(&MOBase::IPluginGame::gameVariants))
// .def("setGameVariant",
// py::pure_virtual(&MOBase::IPluginGame::setGameVariant),
// py::arg("variant")) .def("binaryName",
// py::pure_virtual(&MOBase::IPluginGame::binaryName))
// .def("gameShortName",
// py::pure_virtual(&MOBase::IPluginGame::gameShortName))
// .def("primarySources",
// py::pure_virtual(&MOBase::IPluginGame::primarySources))
// .def("validShortNames",
// py::pure_virtual(&MOBase::IPluginGame::validShortNames))
// .def("gameNexusName",
// py::pure_virtual(&MOBase::IPluginGame::gameNexusName))
// .def("iniFiles",
// py::pure_virtual(&MOBase::IPluginGame::iniFiles))
// .def("DLCPlugins",
// py::pure_virtual(&MOBase::IPluginGame::DLCPlugins))
// .def("CCPlugins",
// py::pure_virtual(&MOBase::IPluginGame::CCPlugins))
// .def("loadOrderMechanism",
// py::pure_virtual(&MOBase::IPluginGame::loadOrderMechanism))
// .def("sortMechanism",
// py::pure_virtual(&MOBase::IPluginGame::sortMechanism))
// .def("nexusModOrganizerID",
// py::pure_virtual(&MOBase::IPluginGame::nexusModOrganizerID))
// .def("nexusGameID",
// py::pure_virtual(&MOBase::IPluginGame::nexusGameID))
// .def("looksValid",
// py::pure_virtual(&MOBase::IPluginGame::looksValid),
// py::arg("directory")) .def("gameVersion",
// py::pure_virtual(&MOBase::IPluginGame::gameVersion))
// .def("getLauncherName",
// py::pure_virtual(&MOBase::IPluginGame::getLauncherName))
// .def("featureList", +[](MOBase::IPluginGame* p) {
// // Constructing a dict from class name to actual object:
// py::dict dict;
// mp11::mp_for_each<
// // Must user pointers because mp_for_each construct object:
// mp11::mp_transform<std::add_pointer_t, MpGameFeaturesList>
// >([&](auto* pt) {
// using T = std::remove_pointer_t<decltype(pt)>;
// typename py::reference_existing_object::apply<T*>::type
// converter;
// // Retrieve the python class object:
// const py::converter::registration* registration =
// py::converter::registry::query(py::type_id<T>()); py::object
// key
// =
// py::object(py::handle<>(py::borrowed(registration->get_class_object())));
// // Set the object:
// dict[key] = py::handle<>(converter(p->feature<T>()));
// });
// return dict;
// })
// .def("feature", +[](MOBase::IPluginGame* p, py::object clsObj) {
// py::object feature;
// mp11::mp_for_each<
// // Must user pointers because mp_for_each construct object:
// mp11::mp_transform<std::add_pointer_t, MpGameFeaturesList>
// >([&](auto* pt) {
// using T = std::remove_pointer_t<decltype(pt)>;
// typename py::reference_existing_object::apply<T*>::type
// converter;
// // Retrieve the python class object:
// const py::converter::registration* registration =
// py::converter::registry::query(py::type_id<T>());
// if (clsObj.ptr() == (PyObject*)
// registration->get_class_object())
// {
// feature =
// py::object(py::handle<>(converter(p->feature<T>())));
// }
// });
// return feature;
// }, py::arg("feature_type"))
// ;
}
// multiple installers
void add_iplugininstaller_bindings(pybind11::module_ m)
{
py::enum_<MOBase::IPluginInstaller::EInstallResult>(m, "InstallResult")
.value("SUCCESS", MOBase::IPluginInstaller::RESULT_SUCCESS)
.value("FAILED", MOBase::IPluginInstaller::RESULT_FAILED)
.value("CANCELED", MOBase::IPluginInstaller::RESULT_CANCELED)
.value("MANUAL_REQUESTED", MOBase::IPluginInstaller::RESULT_MANUALREQUESTED)
.value("NOT_ATTEMPTED", MOBase::IPluginInstaller::RESULT_NOTATTEMPTED);
// this is bind but should not be inherited in Python - does not make sense,
// having it makes it simpler to bind the Simple and Custom installers
py::class_<IPluginInstaller, PyPluginInstallerBase<IPluginInstaller>, IPlugin,
std::unique_ptr<IPluginInstaller, py::nodelete>>(
m, "IPluginInstaller", py::multiple_inheritance())
.def("isArchiveSupported", &IPluginInstaller::isArchiveSupported, "tree"_a)
.def("priority", &IPluginInstaller::priority)
.def("onInstallationStart", &IPluginInstaller::onInstallationStart,
"archive"_a, "reinstallation"_a, "current_mod"_a)
.def("onInstallationEnd", &IPluginInstaller::onInstallationEnd, "result"_a,
"new_mod"_a)
.def("isManualInstaller", &IPluginInstaller::isManualInstaller)
.def("setParentWidget", &IPluginInstaller::setParentWidget, "parent"_a)
.def("setInstallationManager", &IPluginInstaller::setInstallationManager,
"manager"_a)
.def("_parentWidget",
&PyPluginInstallerBase<IPluginInstaller>::parentWidget)
.def("_manager", &PyPluginInstallerBase<IPluginInstaller>::manager,
py::return_value_policy::reference);
py::class_<IPluginInstallerSimple, PyPluginInstallerSimple, IPluginInstaller,
std::unique_ptr<IPluginInstallerSimple, py::nodelete>>(
m, "IPluginInstallerSimple", py::multiple_inheritance())
.def(py::init<>())
// note: keeping the variant here even if we always return a tuple
// to be consistent with the wrapper and have proper stubs generation.
.def(
"install",
[](IPluginInstallerSimple* p, GuessedValue<QString>& modName,
std::shared_ptr<IFileTree>& tree, QString& version,
int& nexusID) -> PyPluginInstallerSimple::py_install_return_type {
auto result = p->install(modName, tree, version, nexusID);
return std::make_tuple(result, tree, version, nexusID);
},
"name"_a, "tree"_a, "version"_a, "nexus_id"_a);
py::class_<IPluginInstallerCustom, PyPluginInstallerCustom, IPluginInstaller,
std::unique_ptr<IPluginInstallerCustom, py::nodelete>>(
m, "IPluginInstallerCustom", py::multiple_inheritance())
.def(py::init<>())
.def("isArchiveSupported", &IPluginInstallerCustom::isArchiveSupported,
"archive_name"_a)
.def("supportedExtensions", &IPluginInstallerCustom::supportedExtensions)
.def("install", &IPluginInstallerCustom::install, "mod_name"_a,
"game_name"_a, "archive_name"_a, "version"_a, "nexus_id"_a);
}
void add_plugins_bindings(pybind11::module_ m)
{
py::class_<IPlugin, PyPlugin, std::unique_ptr<IPlugin, py::nodelete>>(
m, "IPluginBase", py::multiple_inheritance())
.def(py::init<>())
.def("init", &MOBase::IPlugin::init, "organizer"_a)
.def("name", &MOBase::IPlugin::name)
.def("localizedName", &MOBase::IPlugin::localizedName)
.def("master", &MOBase::IPlugin::master)
.def("author", &MOBase::IPlugin::author)
.def("description", &MOBase::IPlugin::description)
.def("version", &MOBase::IPlugin::version)
.def("requirements", &MOBase::IPlugin::requirements)
.def("settings", &MOBase::IPlugin::settings)
.def("enabledByDefault", &MOBase::IPlugin::enabledByDefault);
py::class_<IPyPlugin, PyPlugin, IPlugin,
std::unique_ptr<IPyPlugin, py::nodelete>>(m, "IPlugin",
py::multiple_inheritance())
.def(py::init<>());
py::class_<IPyPluginFileMapper, PyPluginFileMapper, IPlugin,
std::unique_ptr<IPyPluginFileMapper, py::nodelete>>(
m, "IPluginFileMapper", py::multiple_inheritance())
.def(py::init<>())
.def("mappings", &IPluginFileMapper::mappings);
py::class_<IPyPluginDiagnose, PyPluginDiagnose, IPlugin,
std::unique_ptr<IPyPluginDiagnose, py::nodelete>>(
m, "IPluginDiagnose", py::multiple_inheritance())
.def(py::init<>())
.def("activeProblems", &MOBase::IPluginDiagnose::activeProblems)
.def("shortDescription", &MOBase::IPluginDiagnose::shortDescription,
py::arg("key"))
.def("fullDescription", &MOBase::IPluginDiagnose::fullDescription,
py::arg("key"))
.def("hasGuidedFix", &MOBase::IPluginDiagnose::hasGuidedFix, py::arg("key"))
.def("startGuidedFix", &MOBase::IPluginDiagnose::startGuidedFix,
py::arg("key"))
.def("_invalidate", &PyPluginDiagnose::invalidate);
py::class_<IPluginTool, PyPluginTool, IPlugin,
std::unique_ptr<IPluginTool, py::nodelete>>(
m, "IPluginTool", py::multiple_inheritance())
.def(py::init<>())
.def("displayName", &IPluginTool::displayName)
.def("tooltip", &IPluginTool::tooltip)
.def("icon", &IPluginTool::icon)
.def("display", &IPluginTool::display)
.def("setParentWidget", &IPluginTool::setParentWidget)
.def("_parentWidget", &PyPluginTool::parentWidget);
py::class_<IPluginPreview, PyPluginPreview, IPlugin,
std::unique_ptr<IPluginPreview, py::nodelete>>(
m, "IPluginPreview", py::multiple_inheritance())
.def(py::init<>())
.def("supportedExtensions", &IPluginPreview::supportedExtensions)
.def("genFilePreview", &IPluginPreview::genFilePreview, "filename"_a,
"max_size"_a);
py::class_<IPluginModPage, PyPluginModPage, IPluginModPage,
std::unique_ptr<IPluginModPage, py::nodelete>>(
m, "IPluginModPage", py::multiple_inheritance())
.def(py::init<>())
.def("displayName", &IPluginModPage::displayName)
.def("icon", &IPluginModPage::icon)
.def("pageURL", &IPluginModPage::pageURL)
.def("useIntegratedBrowser", &IPluginModPage::useIntegratedBrowser)
.def("handlesDownload", &IPluginModPage::handlesDownload, "page_url"_a,
"download_url"_a, "fileinfo"_a)
.def("setParentWidget", &IPluginModPage::setParentWidget, "parent"_a)
.def("_parentWidget", &PyPluginModPage::parentWidget);
add_iplugingame_bindings(m);
add_iplugininstaller_bindings(m);
}
struct extract_plugins_helper {
QList<QObject*> objects;
template <class IPluginClass>
void append_if_instance(pybind11::object plugin_obj)
{
if (py::isinstance<IPluginClass>(plugin_obj)) {
objects.append(plugin_obj.cast<IPluginClass*>());
}
}
};
QList<QObject*> extract_plugins(pybind11::object plugin_obj)
{
extract_plugins_helper helper;
// we need to check the trampoline class for these since the interfaces do not
// extend IPlugin
helper.append_if_instance<IPyPluginFileMapper>(plugin_obj);
helper.append_if_instance<IPyPluginDiagnose>(plugin_obj);
helper.append_if_instance<IPluginModPage>(plugin_obj);
helper.append_if_instance<IPluginPreview>(plugin_obj);
helper.append_if_instance<IPluginTool>(plugin_obj);
// helper.append_if_instance<IPluginGame>(plugin_obj);
helper.append_if_instance<IPluginInstallerSimple>(plugin_obj);
helper.append_if_instance<IPluginInstallerCustom>(plugin_obj);
if (helper.objects.isEmpty()) {
if (py::isinstance<IPyPlugin>(plugin_obj)) {
helper.objects.append(plugin_obj.cast<IPyPlugin*>());
}
}
return helper.objects;
}
} // namespace mo2::python
+334
View File
@@ -0,0 +1,334 @@
#ifndef PYTHON_WRAPPERS_PYPLUGINS_H
#define PYTHON_WRAPPERS_PYPLUGINS_H
#include <pybind11/functional.h>
#include <pybind11/operators.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../pybind11_qt/pybind11_qt.h"
#include <iinstallationmanager.h>
#include <iplugin.h>
#include <iplugindiagnose.h>
#include <ipluginfilemapper.h>
#include <iplugingame.h>
#include <iplugininstaller.h>
#include <iplugininstallercustom.h>
#include <iplugininstallersimple.h>
#include <ipluginmodpage.h>
#include <ipluginpreview.h>
#include <iplugintool.h>
// these needs to be defined in a header file for automoc - this file is included only
// in pyplugins.cpp
namespace mo2::python {
using namespace MOBase;
class IPyPlugin : public QObject, public IPlugin {};
class IPyPluginFileMapper : public IPyPlugin, public IPluginFileMapper {};
class IPyPluginDiagnose : public IPyPlugin, public IPluginDiagnose {};
template <class PluginBase>
class PyPluginBase : public PluginBase {
public:
using PluginBase::PluginBase;
bool init(IOrganizer* organizer) override
{
PYBIND11_OVERRIDE_PURE(bool, PluginBase, init, organizer);
}
QString name() const override
{
PYBIND11_OVERRIDE_PURE(QString, PluginBase, name, );
}
QString localizedName() const override
{
PYBIND11_OVERRIDE(QString, PluginBase, localizedName, );
}
QString master() const override
{
PYBIND11_OVERRIDE(QString, PluginBase, master, );
}
std::vector<std::shared_ptr<const IPluginRequirement>> requirements() const
{
PYBIND11_OVERRIDE(std::vector<std::shared_ptr<const IPluginRequirement>>,
PluginBase, requirements, );
}
QString author() const override
{
PYBIND11_OVERRIDE_PURE(QString, PluginBase, author, );
}
QString description() const override
{
PYBIND11_OVERRIDE_PURE(QString, PluginBase, description, );
}
VersionInfo version() const override
{
PYBIND11_OVERRIDE_PURE(VersionInfo, PluginBase, version, );
}
QList<PluginSetting> settings() const override
{
PYBIND11_OVERRIDE_PURE(QList<PluginSetting>, PluginBase, settings, );
}
bool enabledByDefault() const override
{
PYBIND11_OVERRIDE(bool, PluginBase, enabledByDefault, );
}
};
class PyPlugin : public PyPluginBase<IPyPlugin> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin)
};
class PyPluginFileMapper : public PyPluginBase<IPyPluginFileMapper> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginFileMapper)
public:
MappingType mappings() const override
{
PYBIND11_OVERRIDE_PURE(MappingType, IPluginFileMapper, mappings, );
}
};
class PyPluginDiagnose : public PyPluginBase<IPyPluginDiagnose> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginDiagnose)
public:
std::vector<unsigned int> activeProblems() const
{
PYBIND11_OVERRIDE_PURE(std::vector<unsigned int>, IPluginDiagnose,
activeProblems, );
}
QString shortDescription(unsigned int key) const
{
PYBIND11_OVERRIDE_PURE(QString, IPluginDiagnose, shortDescription, key);
}
QString fullDescription(unsigned int key) const
{
PYBIND11_OVERRIDE_PURE(QString, IPluginDiagnose, fullDescription, key);
}
bool hasGuidedFix(unsigned int key) const
{
PYBIND11_OVERRIDE_PURE(bool, IPluginDiagnose, hasGuidedFix, key);
}
void startGuidedFix(unsigned int key) const
{
PYBIND11_OVERRIDE_PURE(void, IPluginDiagnose, startGuidedFix, key);
}
// we need to bring this in public scope
using IPluginDiagnose::invalidate;
};
class PyPluginTool : public PyPluginBase<IPluginTool> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginTool)
public:
QString displayName() const override
{
PYBIND11_OVERRIDE_PURE(QString, IPluginTool, displayName, );
}
QString tooltip() const override
{
PYBIND11_OVERRIDE_PURE(QString, IPluginTool, tooltip, );
}
QIcon icon() const override
{
PYBIND11_OVERRIDE_PURE(QIcon, IPluginTool, icon, );
}
void setParentWidget(QWidget* widget) override
{
PYBIND11_OVERRIDE(void, IPluginTool, setParentWidget, widget);
}
void display() const override
{
PYBIND11_OVERRIDE_PURE(void, IPluginTool, display, );
}
// we need to bring this in public scope
using IPluginTool::parentWidget;
};
class PyPluginPreview : public PyPluginBase<IPluginPreview> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginPreview)
public:
std::set<QString> supportedExtensions() const override
{
PYBIND11_OVERRIDE_PURE(std::set<QString>, IPluginPreview,
supportedExtensions, );
}
QWidget* genFilePreview(const QString& fileName,
const QSize& maxSize) const override
{
// TODO: transfer ownership to C++
PYBIND11_OVERRIDE_PURE(QWidget*, IPluginPreview, genFilePreview, fileName,
maxSize);
}
};
class PyPluginModPage : public PyPluginBase<IPluginModPage> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginModPage)
public:
QString displayName() const override
{
PYBIND11_OVERRIDE_PURE(QString, IPluginModPage, displayName, );
}
QIcon icon() const override
{
PYBIND11_OVERRIDE_PURE(QIcon, IPluginModPage, icon, );
}
QUrl pageURL() const override
{
PYBIND11_OVERRIDE_PURE(QUrl, IPluginModPage, pageURL, );
}
bool useIntegratedBrowser() const override
{
PYBIND11_OVERRIDE_PURE(bool, IPluginModPage, useIntegratedBrowser, );
}
bool handlesDownload(const QUrl& pageURL, const QUrl& downloadURL,
ModRepositoryFileInfo& fileInfo) const override
{
// TODO: cannot modify fileInfo from Python
PYBIND11_OVERRIDE_PURE(bool, IPluginModPage, handlesDownload, pageURL,
downloadURL, &fileInfo);
}
void setParentWidget(QWidget* widget) override
{
PYBIND11_OVERRIDE(void, IPluginModPage, setParentWidget, widget);
}
// we need to bring this in public scope
using IPluginModPage::parentWidget;
};
// installers
template <class PluginInstallerBase>
class PyPluginInstallerBase : public PyPluginBase<PluginInstallerBase> {
public:
using PyPluginBase<PluginInstallerBase>::PyPluginBase;
unsigned int priority() const override
{
PYBIND11_OVERRIDE_PURE(unsigned int, PluginInstallerBase, priority);
}
bool isManualInstaller() const override
{
PYBIND11_OVERRIDE_PURE(bool, PluginInstallerBase, isManualInstaller, );
}
void onInstallationStart(QString const& archive, bool reinstallation,
IModInterface* currentMod)
{
PYBIND11_OVERRIDE(void, PluginInstallerBase, onInstallationStart, archive,
reinstallation, currentMod);
}
void onInstallationEnd(IPluginInstaller::EInstallResult result,
IModInterface* newMod)
{
PYBIND11_OVERRIDE(void, PluginInstallerBase, onInstallationEnd, result,
newMod);
}
bool isArchiveSupported(std::shared_ptr<const IFileTree> tree) const override
{
PYBIND11_OVERRIDE_PURE(bool, PluginInstallerBase, isArchiveSupported, tree);
}
// we need to bring these in public scope
using PluginInstallerBase::manager;
using PluginInstallerBase::parentWidget;
};
class PyPluginInstallerCustom
: public PyPluginInstallerBase<IPluginInstallerCustom> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstallerCustom)
public:
bool isArchiveSupported(const QString& archiveName) const
{
PYBIND11_OVERRIDE_PURE(bool, IPluginInstallerCustom, isArchiveSupported,
archiveName);
}
std::set<QString> supportedExtensions() const
{
PYBIND11_OVERRIDE_PURE(std::set<QString>, IPluginInstallerCustom,
supportedExtensions, );
}
EInstallResult install(GuessedValue<QString>& modName, QString gameName,
const QString& archiveName, const QString& version,
int nexusID) override
{
PYBIND11_OVERRIDE_PURE(EInstallResult, IPluginInstallerCustom, install,
modName, gameName, archiveName, version, nexusID);
}
};
class PyPluginInstallerSimple
: public PyPluginInstallerBase<IPluginInstallerSimple> {
Q_OBJECT
Q_INTERFACES(MOBase::IPlugin MOBase::IPluginInstallerSimple)
public:
using py_install_return_type =
std::variant<IPluginInstaller::EInstallResult, std::shared_ptr<IFileTree>,
std::tuple<IPluginInstaller::EInstallResult,
std::shared_ptr<IFileTree>, QString, int>>;
EInstallResult install(GuessedValue<QString>& modName,
std::shared_ptr<IFileTree>& tree, QString& version,
int& nexusID) override
{
const auto result = [&, this]() {
PYBIND11_OVERRIDE_PURE(py_install_return_type, IPluginInstallerSimple,
install, modName, tree, version, nexusID);
}();
return std::visit(
[&modName, &tree, &version, &nexusID](auto const& t) {
using type = std::decay_t<decltype(t)>;
if constexpr (std::is_same_v<type, EInstallResult>) {
return t;
}
else if constexpr (std::is_same_v<type,
std::shared_ptr<IFileTree>>) {
tree = t;
return RESULT_SUCCESS;
}
else if constexpr (std::is_same_v<
type, std::tuple<EInstallResult,
std::shared_ptr<IFileTree>,
QString, int>>) {
tree = std::get<1>(t);
version = std::get<2>(t);
nexusID = std::get<3>(t);
return std::get<0>(t);
}
},
result);
}
};
} // namespace mo2::python
#endif