diff --git a/CMakeLists.txt b/CMakeLists.txt index 12efdf8..30add94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,12 +11,6 @@ SET(DEPENDENCIES_DIR CACHE PATH "") # hint to find qt in dependencies path LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake) -FILE(GLOB_RECURSE PYTHON_ROOT ${DEPENDENCIES_DIR}/Python*/pyconfig.h.in) -GET_FILENAME_COMPONENT(PYTHON_ROOT ${PYTHON_ROOT} DIRECTORY) - -FILE(GLOB_RECURSE SIP_ROOT ${DEPENDENCIES_DIR}/sip-*/siplib/sip.h) -GET_FILENAME_COMPONENT(SIP_ROOT ${SIP_ROOT} DIRECTORY) - # need to install python IF(EXISTS "${PYTHON_ROOT}/PCbuild/python27.dll") INSTALL(FILES ${PYTHON_ROOT}/PCbuild/python27.dll DESTINATION bin) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..6069403 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,36 @@ +version: 1.0.{build} +skip_branch_with_pr: true +image: Visual Studio 2017 +environment: + WEBHOOK_URL: + secure: gOKbXaZM9ImtMD5XrYITvdyZUW/az082G9OIN1EC1Vbg57wBaeLhi49uGjxPw5GVujHku6kxN6ab89zhbS5GVeluR76GM83IbKV4Sh7udXzoYZZdg6YudtYHzdhCgUeiedpswbuczTq9ceIkkfSEWZuh/lMAAVVwvcGsJAnoPFw= +build_script: +- cmd: >- + git clone --depth=1 --branch=%APPVEYOR_REPO_BRANCH% https://github.com/ModOrganizer2/modorganizer-umbrella.git c:\projects\modorganizer-umbrella 2> $null + + mkdir c:\projects\modorganizer-build -type directory + + cd c:\projects\modorganizer-umbrella + + C:\Python37-x64\python.exe unimake.py -d c:\projects\modorganizer-build -s Appveyor_Build=True %APPVEYOR_PROJECT_NAME% +artifacts: +- path: vsbuild\src\proxy\RelWithDebInfo\plugin_python.dll + name: plugin_python_dll +- path: vsbuild\src\proxy\RelWithDebInfo\plugin_python.pdb + name: plugin_python_pdb +- path: vsbuild\src\proxy\RelWithDebInfo\plugin_python.lib + name: plugin_python_lib +- path: vsbuild\src\runner\RelWithDebInfo\pythonrunner.dll + name: pythonrunner_dll +- path: vsbuild\src\runner\RelWithDebInfo\pythonrunner.pdb + name: pythonrunner_pdb +- path: vsbuild\src\runner\RelWithDebInfo\pythonrunner.lib + name: pythonrunner_lib +on_success: + - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER + - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 + - ps: ./send.ps1 success $env:WEBHOOK_URL +on_failure: + - ps: Set-Location -Path $env:APPVEYOR_BUILD_FOLDER + - ps: Invoke-RestMethod https://raw.githubusercontent.com/DiscordHooks/appveyor-discord-webhook/master/send.ps1 -o send.ps1 + - ps: ./send.ps1 failure $env:WEBHOOK_URL \ No newline at end of file diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt index 4df9b28..ff53fef 100644 --- a/src/proxy/CMakeLists.txt +++ b/src/proxy/CMakeLists.txt @@ -13,6 +13,7 @@ SET(CMAKE_INCLUDE_CURRENT_DIR ON) SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTOUIC ON) FIND_PACKAGE(Qt5Widgets REQUIRED) +FIND_PACKAGE(Qt5Network REQUIRED) #QT5_WRAP_UI(${PROJ_NAME}_UIHDRS ${${PROJ_NAME}_FORMS}) SET(Boost_USE_STATIC_LIBS ON) @@ -24,7 +25,7 @@ IF (Boost_FOUND) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) ENDIF (Boost_FOUND) -SET(default_project_path "${CMAKE_SOURCE_DIR}/../..") +SET(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super") GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH) SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects") @@ -33,15 +34,15 @@ SET(lib_path "${project_path}/../../install/libs") ADD_DEFINITIONS(-DUNICODE -D_UNICODE) INCLUDE_DIRECTORIES(${project_path}/uibase/src - ${project_path}/plugin_python/src/runner/ - ${PYTHON_ROOT}/Include - ${PYTHON_ROOT}/PC) + ${CMAKE_SOURCE_DIR}/src/runner/ + ${PYTHON_ROOT}/Include) LINK_DIRECTORIES(${lib_path}) ADD_LIBRARY(${PROJ_NAME} SHARED ${${PROJ_NAME}_HDRS} ${${PROJ_NAME}_SRCS} ${${PROJ_NAME}_UIHDRS}) TARGET_LINK_LIBRARIES(${PROJ_NAME} Qt5::Widgets + Qt5::Network ${Boost_LIBRARIES} uibase) diff --git a/src/proxy/proxypython.cpp b/src/proxy/proxypython.cpp index 7220470..57c4511 100644 --- a/src/proxy/proxypython.cpp +++ b/src/proxy/proxypython.cpp @@ -171,7 +171,7 @@ bool ProxyPython::init(IOrganizer *moInfo) return true; } else { DWORD error = ::GetLastError(); - qCritical("Failed to load python runner (%s): %s", qPrintable(m_TempRunnerFile), qPrintable(windowsErrorString(error))); + qCritical("Failed to load python runner (%s): %s", qUtf8Printable(m_TempRunnerFile), qUtf8Printable(windowsErrorString(error))); if (error == ERROR_MOD_NOT_FOUND) { m_LoadFailure = FAIL_MISSINGDEPENDENCIES; } @@ -196,7 +196,7 @@ QString ProxyPython::description() const VersionInfo ProxyPython::version() const { - return VersionInfo(2, 0, 0, VersionInfo::RELEASE_FINAL); + return VersionInfo(2, 0, 1, VersionInfo::RELEASE_FINAL); } bool ProxyPython::isActive() const diff --git a/src/runner/CMakeLists.txt b/src/runner/CMakeLists.txt index 0b5194d..e237e07 100644 --- a/src/runner/CMakeLists.txt +++ b/src/runner/CMakeLists.txt @@ -15,6 +15,7 @@ SET(CMAKE_AUTOMOC ON) SET(CMAKE_AUTOUIC ON) ADD_DEFINITIONS(-DQT_NO_KEYWORDS) FIND_PACKAGE(Qt5Widgets REQUIRED) +FIND_PACKAGE(Qt5Network REQUIRED) #QT5_WRAP_UI(${PROJ_NAME}_UIHDRS ${${PROJ_NAME}_FORMS}) FIND_PACKAGE(Qt5LinguistTools) QT5_CREATE_TRANSLATION(${PROJ_NAME}_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/${PROJ_NAME}_en.ts) @@ -28,7 +29,7 @@ IF (Boost_FOUND) INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIRS}) ENDIF (Boost_FOUND) -SET(default_project_path "${CMAKE_SOURCE_DIR}/..") +SET(default_project_path "${DEPENDENCIES_DIR}/modorganizer_super") GET_FILENAME_COMPONENT(${default_project_path} ${default_project_path} REALPATH) SET(project_path "${default_project_path}" CACHE PATH "path to the other mo projects") @@ -37,17 +38,16 @@ SET(lib_path "${project_path}/../../install/libs") INCLUDE_DIRECTORIES(${project_path}/uibase/src ${project_path}/game_features/src ${PYTHON_ROOT}/Include - ${SIP_ROOT} ${PYTHON_ROOT}/PC) LINK_DIRECTORIES(${lib_path} ${PYTHON_ROOT}/PCbuild - ${Boost_LIBRARY_DIRS} - ${SIP_ROOT}) + ${Boost_LIBRARY_DIRS}) ADD_LIBRARY(${PROJ_NAME} SHARED ${${PROJ_NAME}_HDRS} ${${PROJ_NAME}_SRCS} ${${PROJ_NAME}_translations_qm}) TARGET_LINK_LIBRARIES(${PROJ_NAME} Qt5::Widgets + Qt5::Network uibase) ADD_DEFINITIONS(-DPYTHONRUNNER_LIBRARY) diff --git a/src/runner/proxypluginwrappers.cpp b/src/runner/proxypluginwrappers.cpp index d8b4672..2b55101 100644 --- a/src/runner/proxypluginwrappers.cpp +++ b/src/runner/proxypluginwrappers.cpp @@ -178,6 +178,11 @@ QList IPluginGameWrapper::executables() const return basicWrapperFunctionImplementation>(this, "executables"); } +QList IPluginGameWrapper::executableForcedLoads() const +{ + return basicWrapperFunctionImplementation>(this, "executableForcedLoads"); +} + QString IPluginGameWrapper::steamAPPId() const { return basicWrapperFunctionImplementation(this, "steamAPPId"); diff --git a/src/runner/proxypluginwrappers.h b/src/runner/proxypluginwrappers.h index 61a8bae..712707a 100644 --- a/src/runner/proxypluginwrappers.h +++ b/src/runner/proxypluginwrappers.h @@ -101,6 +101,7 @@ public: virtual QDir documentsDirectory() const override; virtual QDir savesDirectory() const override; virtual QList executables() const override; + virtual QList executableForcedLoads() const override; virtual QString steamAPPId() const override; virtual QStringList primaryPlugins() const override; virtual QStringList gameVariants() const override; diff --git a/src/runner/pythonrunner.cpp b/src/runner/pythonrunner.cpp index 2f2e01f..c1e00d0 100644 --- a/src/runner/pythonrunner.cpp +++ b/src/runner/pythonrunner.cpp @@ -73,7 +73,7 @@ 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 either str or unicode without it being easy to know which to expect - bpy::object pyStr = bpy::object(str.toUtf8().constData()); + bpy::object pyStr = bpy::object(qUtf8Printable(str)); if (SIPBytes_Check(pyStr.ptr())) pyStr = pyStr.attr("decode")("utf-8"); return bpy::incref(pyStr.ptr()); @@ -960,6 +960,7 @@ BOOST_PYTHON_MODULE(mobase) .def("downloadsPath", bpy::pure_virtual(&IOrganizer::downloadsPath)) .def("overwritePath", bpy::pure_virtual(&IOrganizer::overwritePath)) .def("basePath", bpy::pure_virtual(&IOrganizer::basePath)) + .def("modsPath", bpy::pure_virtual(&IOrganizer::modsPath)) .def("appVersion", bpy::pure_virtual(&IOrganizer::appVersion)) .def("getMod", bpy::pure_virtual(&IOrganizer::getMod), bpy::return_value_policy()) .def("createMod", bpy::pure_virtual(&IOrganizer::createMod), bpy::return_value_policy()) @@ -981,7 +982,7 @@ BOOST_PYTHON_MODULE(mobase) .def("pluginList", bpy::pure_virtual(&IOrganizer::pluginList), bpy::return_value_policy()) .def("modList", bpy::pure_virtual(&IOrganizer::modList), bpy::return_value_policy()) .def("profile", bpy::pure_virtual(&IOrganizer::profile), bpy::return_value_policy()) - .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), ((bpy::arg("args")=QStringList()), (bpy::arg("cwd")=""), (bpy::arg("profile")="")), bpy::return_value_policy()) + .def("startApplication", bpy::pure_virtual(&IOrganizer::startApplication), ((bpy::arg("args")=QStringList()), (bpy::arg("cwd")=""), (bpy::arg("profile")=""), (bpy::arg("forcedCustomOverwrite")=""), (bpy::arg("ignoreCustomOverwrite")=false)), bpy::return_value_policy()) //.def("waitForApplication", bpy::pure_virtual(&IOrganizer::waitForApplication), (bpy::arg("exitCode")=nullptr), bpy::return_value_policy()) // Use wrapped version .def("waitForApplication", waitForApplication) @@ -1069,6 +1070,7 @@ BOOST_PYTHON_MODULE(mobase) .def("addCategory", bpy::pure_virtual(&IModInterface::addCategory)) .def("removeCategory", bpy::pure_virtual(&IModInterface::removeCategory)) .def("categories", bpy::pure_virtual(&IModInterface::categories)) + .def("setGameName", bpy::pure_virtual(&IModInterface::setGameName)) .def("setName", bpy::pure_virtual(&IModInterface::setName)) .def("remove", bpy::pure_virtual(&IModInterface::remove)) ; @@ -1093,6 +1095,12 @@ BOOST_PYTHON_MODULE(mobase) QFlags_from_python_obj(); Functor0_converter(); // converter for the onRefreshed-callback + bpy::enum_("PluginState") + .value("missing", IPluginList::STATE_MISSING) + .value("inactive", IPluginList::STATE_INACTIVE) + .value("active", IPluginList::STATE_ACTIVE) + ; + bpy::class_("IPluginList") .def("state", bpy::pure_virtual(&MOBase::IPluginList::state)) .def("priority", bpy::pure_virtual(&MOBase::IPluginList::priority)) @@ -1111,6 +1119,16 @@ BOOST_PYTHON_MODULE(mobase) QFlags_from_python_obj(); Functor2_converter(); // converter for the onModStateChanged-callback + bpy::enum_("ModState") + .value("exists", IModList::STATE_EXISTS) + .value("active", IModList::STATE_ACTIVE) + .value("essential", IModList::STATE_ESSENTIAL) + .value("empty", IModList::STATE_EMPTY) + .value("endorsed", IModList::STATE_ENDORSED) + .value("valid", IModList::STATE_VALID) + .value("alternate", IModList::STATE_ALTERNATE) + ; + bpy::class_("IModList") .def("displayName", bpy::pure_virtual(&MOBase::IModList::displayName)) .def("allMods", bpy::pure_virtual(&MOBase::IModList::allMods)) @@ -1392,7 +1410,7 @@ QList PythonRunner::instantiate(const QString &pluginName) return interfaceList; } catch (const bpy::error_already_set&) { - qWarning("failed to run python script \"%s\"", qPrintable(pluginName)); + qWarning("failed to run python script \"%s\"", qUtf8Printable(pluginName)); reportPythonError(); } return QList(); diff --git a/src/runner/uibasewrappers.h b/src/runner/uibasewrappers.h index ad6db79..b31bb60 100644 --- a/src/runner/uibasewrappers.h +++ b/src/runner/uibasewrappers.h @@ -63,8 +63,8 @@ public: { m_Wrapped->requestFiles(gameName, modID, userData); } void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) { m_Wrapped->requestFileInfo(gameName, modID, fileID, userData); } - void requestToggleEndorsement(QString gameName, int modID, bool endorse, QVariant userData) - { m_Wrapped->requestToggleEndorsement(gameName, modID, endorse, userData); } + void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) + { m_Wrapped->requestToggleEndorsement(gameName, modID, modVersion, endorse, userData); } void onFilesAvailable(boost::python::object callback) { m_FilesAvailableHandler = callback; @@ -94,6 +94,14 @@ public: Qt::UniqueConnection); } + void onTrackingToggled(boost::python::object callback) { + m_TrackingToggledHandler = callback; + connect(m_Wrapped, SIGNAL(trackingToggled(int,QVariant,bool)), + this, SLOT(trackingToggled(int,QVariant,bool)), + Qt::UniqueConnection); + + } + void onRequestFailed(boost::python::object callback) { m_FailedHandler = callback; connect(m_Wrapped, SIGNAL(requestFailed(int,int,QVariant,QString)), @@ -180,6 +188,26 @@ private Q_SLOTS: } } + void trackingToggled(int modID, QVariant userData, bool tracked) + { + try { + if (m_TrackingToggledHandler.is_none()) { + qCritical("no handler connected"); + return; + } + try { + GILock lock; + m_TrackingToggledHandler(modID, userData, tracked); + } catch (const boost::python::error_already_set&) { + reportPythonError(); + } + } catch (const std::exception &e) { + qCritical("failed to report event: %s", e.what()); + } catch (...) { + qCritical("failed to report event"); + } + } + void requestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage) { try { @@ -197,6 +225,7 @@ private: boost::python::object m_DescriptionAvailableHandler; boost::python::object m_FileInfoHandler; boost::python::object m_EndorsementToggledHandler; + boost::python::object m_TrackingToggledHandler; boost::python::object m_FailedHandler; }; @@ -228,6 +257,10 @@ struct IOrganizerWrapper : MOBase::IOrganizer, { return this->get_override("basePath")(); } + virtual QString modsPath() const override + { + return this->get_override("modsPath")(); + } virtual MOBase::VersionInfo appVersion() const override { return this->get_override("appVersion")(); @@ -323,9 +356,11 @@ struct IOrganizerWrapper : MOBase::IOrganizer, virtual HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", - const QString &profile = "") override + const QString &profile = "", + const QString &forcedCustomOverwrite = "", + bool ignoreCustomOverwrite = false) override { - return reinterpret_cast(this->get_override("startApplication")(executable, args, cwd, profile).as()); + return reinterpret_cast(this->get_override("startApplication")(executable, args, cwd, profile, forcedCustomOverwrite, ignoreCustomOverwrite).as()); } virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const override @@ -387,7 +422,7 @@ struct IModRepositoryBridgeWrapper: MOBase::IModRepositoryBridge, boost::python: virtual void requestFiles(QString gameName, int modID, QVariant userData) { this->get_override("requestFiles")(gameName, modID, userData); } virtual void requestFileInfo(QString gameName, int modID, int fileID, QVariant userData) { this->get_override("requestFileInfo")(gameName, modID, fileID, userData); } virtual void requestDownloadURL(QString gameName, int modID, int fileID, QVariant userData) { this->get_override("requestDownloadURL")(gameName, modID, fileID, userData); } - virtual void requestToggleEndorsement(QString gameName, int modID, bool endorse, QVariant userData) { this->get_override("requestToggleEndorsement")(gameName, modID, endorse, userData); } + virtual void requestToggleEndorsement(QString gameName, int modID, QString modVersion, bool endorse, QVariant userData) { this->get_override("requestToggleEndorsement")(gameName, modID, endorse, userData); } }; struct IInstallationManagerWrapper: MOBase::IInstallationManager, boost::python::wrapper @@ -408,6 +443,7 @@ struct IModInterfaceWrapper: MOBase::IModInterface, boost::python::wrapperget_override("setNexusID")(nexusID); } virtual void setInstallationFile(const QString &fileName) override { this->get_override("setInstallationFile")(fileName); } virtual void addNexusCategory(int categoryID) override { this->get_override("addNexusCategory")(categoryID); } + virtual void setGameName(const QString &gameName) override { this->get_override("setGameName")(gameName); } virtual bool setName(const QString &name) override { return this->get_override("setName")(name); } virtual bool remove() override { return this->get_override("remove")(); } virtual void addCategory(const QString &categoryName) override { this->get_override("addCategory")(categoryName); }