From 688e0fbc2c7539e7c5599f9d153a5521fb1a58a6 Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sat, 10 Jan 2026 21:34:56 +0100 Subject: [PATCH 1/6] Add instanceName and profiles methods to plugin API --- src/mobase/mobase.cpp | 2 +- src/mobase/wrappers/basic_classes.cpp | 18 ++------- src/mobase/wrappers/wrappers.cpp | 54 +++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 16 deletions(-) diff --git a/src/mobase/mobase.cpp b/src/mobase/mobase.cpp index b1daa2b..0c76f1b 100644 --- a/src/mobase/mobase.cpp +++ b/src/mobase/mobase.cpp @@ -26,8 +26,8 @@ PYBIND11_MODULE(mobase, m) // bindings // - mo2::python::add_basic_bindings(m); mo2::python::add_wrapper_bindings(m); + mo2::python::add_basic_bindings(m); // game features must be added before plugins mo2::python::add_game_feature_bindings(m); diff --git a/src/mobase/wrappers/basic_classes.cpp b/src/mobase/wrappers/basic_classes.cpp index bdeecaa..3554800 100644 --- a/src/mobase/wrappers/basic_classes.cpp +++ b/src/mobase/wrappers/basic_classes.cpp @@ -3,6 +3,7 @@ #include "../pybind11_all.h" #include +#include #include #include @@ -536,6 +537,7 @@ namespace mo2::python { py::class_(m, "IOrganizer") .def("createNexusBridge", &IOrganizer::createNexusBridge, py::return_value_policy::reference) + .def("instanceName", &IOrganizer::instanceName) .def("profileName", &IOrganizer::profileName) .def("profilePath", &IOrganizer::profilePath) .def("downloadsPath", &IOrganizer::downloadsPath) @@ -626,6 +628,7 @@ namespace mo2::python { .def("gameFeatures", &IOrganizer::gameFeatures, py::return_value_policy::reference) .def("profile", &IOrganizer::profile, py::return_value_policy::reference) + .def("profiles", &IOrganizer::profiles) // custom implementation for startApplication and // waitForApplication because 1) HANDLE (= void*) is not properly @@ -878,21 +881,6 @@ namespace mo2::python { m.createTarget); }); - // must be done BEFORE imodlist because there is a default argument to a - // IProfile* in the modlist class - py::class_(m, "IProfile") - .def("name", &IProfile::name) - .def("absolutePath", &IProfile::absolutePath) - .def("localSavesEnabled", &IProfile::localSavesEnabled) - .def("localSettingsEnabled", &IProfile::localSettingsEnabled) - .def("invalidationActive", - [](const IProfile* p) { - bool supported; - bool active = p->invalidationActive(&supported); - return std::make_tuple(active, supported); - }) - .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a); - add_ipluginlist_classes(m); add_imodlist_classes(m); add_idownload_manager_classes(m); diff --git a/src/mobase/wrappers/wrappers.cpp b/src/mobase/wrappers/wrappers.cpp index 10f043d..6fd121e 100644 --- a/src/mobase/wrappers/wrappers.cpp +++ b/src/mobase/wrappers/wrappers.cpp @@ -11,6 +11,7 @@ // IOrganizer must be bring here to properly compile the Python bindings of // plugin requirements #include +#include #include #include #include @@ -75,6 +76,41 @@ namespace mo2::python { ~PySaveGameInfoWidget() { std::cout << "~PySaveGameInfoWidget()" << std::endl; } }; + class PyProfile : public IProfile { + public: + QString name() const override + { + PYBIND11_OVERRIDE_PURE(QString, IProfile, name, ); + } + + QString absolutePath() const override + { + PYBIND11_OVERRIDE_PURE(QString, IProfile, absolutePath, ); + } + + bool localSavesEnabled() const override + { + PYBIND11_OVERRIDE_PURE(bool, IProfile, localSavesEnabled, ); + } + + bool localSettingsEnabled() const override + { + PYBIND11_OVERRIDE_PURE(bool, IProfile, localSettingsEnabled, ); + } + + bool invalidationActive(bool* supported) const override + { + PYBIND11_OVERRIDE_PURE(bool, IProfile, invalidationActive, supported); + } + + QString absoluteIniFilePath(QString iniFile) const override + { + PYBIND11_OVERRIDE_PURE(QString, IProfile, absoluteIniFilePath, iniFile); + } + + ~PyProfile() { std::cout << "~PyProfile()" << std::endl; } + }; + void add_wrapper_bindings(pybind11::module_ m) { // ISaveGame - custom type_caster<> for shared_ptr<> to keep the Python object @@ -115,6 +151,24 @@ namespace mo2::python { .def("longDescription", &IPluginRequirement::Problem::longDescription); iPluginRequirementClass.def("check", &IPluginRequirement::check, "organizer"_a); + + // IProfile - custom type_caster<> for shared_ptr<> to keep the Python object + // alive when returned from Python (see shared_cpp_owner.h) + + // must be done BEFORE imodlist because there is a default argument to a + // IProfile* in the modlist class + py::class_>(m, "IProfile") + .def("name", &IProfile::name) + .def("absolutePath", &IProfile::absolutePath) + .def("localSavesEnabled", &IProfile::localSavesEnabled) + .def("localSettingsEnabled", &IProfile::localSettingsEnabled) + .def("invalidationActive", + [](const IProfile* p) { + bool supported; + bool active = p->invalidationActive(&supported); + return std::make_tuple(active, supported); + }) + .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a); } } // namespace mo2::python From 483ac352edc6d8a024ef9e593c4d83a4ea7d35b7 Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sat, 10 Jan 2026 22:16:03 +0100 Subject: [PATCH 2/6] Add mock methods --- tests/mocks/MockOrganizer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/mocks/MockOrganizer.h b/tests/mocks/MockOrganizer.h index cdae36a..ebd7cf3 100644 --- a/tests/mocks/MockOrganizer.h +++ b/tests/mocks/MockOrganizer.h @@ -7,6 +7,7 @@ class MockOrganizer : public IOrganizer { public: // clang-format off MOCK_METHOD(IModRepositoryBridge*, createNexusBridge, (), (const, override)); + MOCK_METHOD(QString, instanceName, (), (const, override)); MOCK_METHOD(QString, profileName, (), (const, override)); MOCK_METHOD(QString, profilePath, (), (const, override)); MOCK_METHOD(QString, downloadsPath, (), (const, override)); @@ -37,6 +38,7 @@ class MockOrganizer : public IOrganizer { MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override)); MOCK_METHOD(MOBase::IModList*, modList, (), (const, override)); MOCK_METHOD(MOBase::IProfile*, profile, (), (const, override)); + MOCK_METHOD(std::vector>, profiles, (), (const, override)); MOCK_METHOD(MOBase::IGameFeatures*, gameFeatures, (), (const, override)); MOCK_METHOD(HANDLE, startApplication, (const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite), (override)); MOCK_METHOD(bool, waitForApplication, (HANDLE handle, bool refresh, LPDWORD exitCode), (const, override)); From 63afb038d086d3f512e66c79ae998d0219da706b Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sun, 11 Jan 2026 10:49:52 +0100 Subject: [PATCH 3/6] Split profiles method into profileNames and getProfile --- src/mobase/wrappers/basic_classes.cpp | 3 ++- tests/mocks/MockOrganizer.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/mobase/wrappers/basic_classes.cpp b/src/mobase/wrappers/basic_classes.cpp index 3554800..3ca7efc 100644 --- a/src/mobase/wrappers/basic_classes.cpp +++ b/src/mobase/wrappers/basic_classes.cpp @@ -628,7 +628,8 @@ namespace mo2::python { .def("gameFeatures", &IOrganizer::gameFeatures, py::return_value_policy::reference) .def("profile", &IOrganizer::profile, py::return_value_policy::reference) - .def("profiles", &IOrganizer::profiles) + .def("profileNames", &IOrganizer::profileNames) + .def("getProfile", &IOrganizer::getProfile, "name"_a) // custom implementation for startApplication and // waitForApplication because 1) HANDLE (= void*) is not properly diff --git a/tests/mocks/MockOrganizer.h b/tests/mocks/MockOrganizer.h index ebd7cf3..aace0b8 100644 --- a/tests/mocks/MockOrganizer.h +++ b/tests/mocks/MockOrganizer.h @@ -38,7 +38,8 @@ class MockOrganizer : public IOrganizer { MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override)); MOCK_METHOD(MOBase::IModList*, modList, (), (const, override)); MOCK_METHOD(MOBase::IProfile*, profile, (), (const, override)); - MOCK_METHOD(std::vector>, profiles, (), (const, override)); + MOCK_METHOD(QStringList, profileNames, (), (const, override)); + MOCK_METHOD(std::shared_ptr, getProfile, (const QString& name), (const, override)); MOCK_METHOD(MOBase::IGameFeatures*, gameFeatures, (), (const, override)); MOCK_METHOD(HANDLE, startApplication, (const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite), (override)); MOCK_METHOD(bool, waitForApplication, (HANDLE handle, bool refresh, LPDWORD exitCode), (const, override)); From 913cb79f42d9d9a1173827eb7d23f8a4dec8ac68 Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sun, 11 Jan 2026 16:41:24 +0100 Subject: [PATCH 4/6] Change IOrganizer::profile return type to a shared_ptr and remove IProfile wrapper --- src/mobase/mobase.cpp | 2 +- src/mobase/pybind11_all.h | 2 + src/mobase/wrappers/basic_classes.cpp | 17 ++++++++- src/mobase/wrappers/wrappers.cpp | 54 --------------------------- tests/mocks/MockOrganizer.h | 2 +- 5 files changed, 20 insertions(+), 57 deletions(-) diff --git a/src/mobase/mobase.cpp b/src/mobase/mobase.cpp index 0c76f1b..b1daa2b 100644 --- a/src/mobase/mobase.cpp +++ b/src/mobase/mobase.cpp @@ -26,8 +26,8 @@ PYBIND11_MODULE(mobase, m) // bindings // - mo2::python::add_wrapper_bindings(m); mo2::python::add_basic_bindings(m); + mo2::python::add_wrapper_bindings(m); // game features must be added before plugins mo2::python::add_game_feature_bindings(m); diff --git a/src/mobase/pybind11_all.h b/src/mobase/pybind11_all.h index a293fb8..ae83169 100644 --- a/src/mobase/pybind11_all.h +++ b/src/mobase/pybind11_all.h @@ -18,6 +18,7 @@ #include "pybind11_utils/smart_variant_wrapper.h" #include +#include #include #include @@ -140,6 +141,7 @@ namespace mo2::python { } // namespace mo2::python MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) +MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IProfile) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::GameFeature) diff --git a/src/mobase/wrappers/basic_classes.cpp b/src/mobase/wrappers/basic_classes.cpp index 3ca7efc..de28e71 100644 --- a/src/mobase/wrappers/basic_classes.cpp +++ b/src/mobase/wrappers/basic_classes.cpp @@ -627,7 +627,7 @@ namespace mo2::python { .def("modList", &IOrganizer::modList, py::return_value_policy::reference) .def("gameFeatures", &IOrganizer::gameFeatures, py::return_value_policy::reference) - .def("profile", &IOrganizer::profile, py::return_value_policy::reference) + .def("profile", &IOrganizer::profile) .def("profileNames", &IOrganizer::profileNames) .def("getProfile", &IOrganizer::getProfile, "name"_a) @@ -882,6 +882,21 @@ namespace mo2::python { m.createTarget); }); + // must be done BEFORE imodlist because there is a default argument to a + // IProfile* in the modlist class + py::class_>(m, "IProfile") + .def("name", &IProfile::name) + .def("absolutePath", &IProfile::absolutePath) + .def("localSavesEnabled", &IProfile::localSavesEnabled) + .def("localSettingsEnabled", &IProfile::localSettingsEnabled) + .def("invalidationActive", + [](const IProfile* p) { + bool supported; + bool active = p->invalidationActive(&supported); + return std::make_tuple(active, supported); + }) + .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a); + add_ipluginlist_classes(m); add_imodlist_classes(m); add_idownload_manager_classes(m); diff --git a/src/mobase/wrappers/wrappers.cpp b/src/mobase/wrappers/wrappers.cpp index 6fd121e..10f043d 100644 --- a/src/mobase/wrappers/wrappers.cpp +++ b/src/mobase/wrappers/wrappers.cpp @@ -11,7 +11,6 @@ // IOrganizer must be bring here to properly compile the Python bindings of // plugin requirements #include -#include #include #include #include @@ -76,41 +75,6 @@ namespace mo2::python { ~PySaveGameInfoWidget() { std::cout << "~PySaveGameInfoWidget()" << std::endl; } }; - class PyProfile : public IProfile { - public: - QString name() const override - { - PYBIND11_OVERRIDE_PURE(QString, IProfile, name, ); - } - - QString absolutePath() const override - { - PYBIND11_OVERRIDE_PURE(QString, IProfile, absolutePath, ); - } - - bool localSavesEnabled() const override - { - PYBIND11_OVERRIDE_PURE(bool, IProfile, localSavesEnabled, ); - } - - bool localSettingsEnabled() const override - { - PYBIND11_OVERRIDE_PURE(bool, IProfile, localSettingsEnabled, ); - } - - bool invalidationActive(bool* supported) const override - { - PYBIND11_OVERRIDE_PURE(bool, IProfile, invalidationActive, supported); - } - - QString absoluteIniFilePath(QString iniFile) const override - { - PYBIND11_OVERRIDE_PURE(QString, IProfile, absoluteIniFilePath, iniFile); - } - - ~PyProfile() { std::cout << "~PyProfile()" << std::endl; } - }; - void add_wrapper_bindings(pybind11::module_ m) { // ISaveGame - custom type_caster<> for shared_ptr<> to keep the Python object @@ -151,24 +115,6 @@ namespace mo2::python { .def("longDescription", &IPluginRequirement::Problem::longDescription); iPluginRequirementClass.def("check", &IPluginRequirement::check, "organizer"_a); - - // IProfile - custom type_caster<> for shared_ptr<> to keep the Python object - // alive when returned from Python (see shared_cpp_owner.h) - - // must be done BEFORE imodlist because there is a default argument to a - // IProfile* in the modlist class - py::class_>(m, "IProfile") - .def("name", &IProfile::name) - .def("absolutePath", &IProfile::absolutePath) - .def("localSavesEnabled", &IProfile::localSavesEnabled) - .def("localSettingsEnabled", &IProfile::localSettingsEnabled) - .def("invalidationActive", - [](const IProfile* p) { - bool supported; - bool active = p->invalidationActive(&supported); - return std::make_tuple(active, supported); - }) - .def("absoluteIniFilePath", &IProfile::absoluteIniFilePath, "inifile"_a); } } // namespace mo2::python diff --git a/tests/mocks/MockOrganizer.h b/tests/mocks/MockOrganizer.h index aace0b8..72b2402 100644 --- a/tests/mocks/MockOrganizer.h +++ b/tests/mocks/MockOrganizer.h @@ -37,7 +37,7 @@ class MockOrganizer : public IOrganizer { MOCK_METHOD(MOBase::IDownloadManager*, downloadManager, (), (const, override)); MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override)); MOCK_METHOD(MOBase::IModList*, modList, (), (const, override)); - MOCK_METHOD(MOBase::IProfile*, profile, (), (const, override)); + MOCK_METHOD(std::shared_ptr, profile, (), (const, override)); MOCK_METHOD(QStringList, profileNames, (), (const, override)); MOCK_METHOD(std::shared_ptr, getProfile, (const QString& name), (const, override)); MOCK_METHOD(MOBase::IGameFeatures*, gameFeatures, (), (const, override)); From 7288b34eb88e353773a15ee3e5e92a8a39a987b0 Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:08:02 +0100 Subject: [PATCH 5/6] Fix missing namespace for IProfile in mocks --- tests/mocks/MockOrganizer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocks/MockOrganizer.h b/tests/mocks/MockOrganizer.h index 72b2402..6508fc0 100644 --- a/tests/mocks/MockOrganizer.h +++ b/tests/mocks/MockOrganizer.h @@ -37,9 +37,9 @@ class MockOrganizer : public IOrganizer { MOCK_METHOD(MOBase::IDownloadManager*, downloadManager, (), (const, override)); MOCK_METHOD(MOBase::IPluginList*, pluginList, (), (const, override)); MOCK_METHOD(MOBase::IModList*, modList, (), (const, override)); - MOCK_METHOD(std::shared_ptr, profile, (), (const, override)); + MOCK_METHOD(std::shared_ptr, profile, (), (const, override)); MOCK_METHOD(QStringList, profileNames, (), (const, override)); - MOCK_METHOD(std::shared_ptr, getProfile, (const QString& name), (const, override)); + MOCK_METHOD(std::shared_ptr, getProfile, (const QString& name), (const, override)); MOCK_METHOD(MOBase::IGameFeatures*, gameFeatures, (), (const, override)); MOCK_METHOD(HANDLE, startApplication, (const QString &executable, const QStringList &args, const QString &cwd, const QString &profile, const QString &forcedCustomOverwrite, bool ignoreCustomOverwrite), (override)); MOCK_METHOD(bool, waitForApplication, (HANDLE handle, bool refresh, LPDWORD exitCode), (const, override)); From 38d687c3f42f59aa70add48c310731c9efa73595 Mon Sep 17 00:00:00 2001 From: Jonathan Feenstra <26406078+JonathanFeenstra@users.noreply.github.com> Date: Sun, 11 Jan 2026 17:46:59 +0100 Subject: [PATCH 6/6] Remove MO2_PYBIND11_SHARED_CPP_HOLDER --- src/mobase/pybind11_all.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/mobase/pybind11_all.h b/src/mobase/pybind11_all.h index ae83169..a293fb8 100644 --- a/src/mobase/pybind11_all.h +++ b/src/mobase/pybind11_all.h @@ -18,7 +18,6 @@ #include "pybind11_utils/smart_variant_wrapper.h" #include -#include #include #include @@ -141,7 +140,6 @@ namespace mo2::python { } // namespace mo2::python MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IPluginRequirement) -MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::IProfile) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::ISaveGame) MO2_PYBIND11_SHARED_CPP_HOLDER(MOBase::GameFeature)