From e9a1df5f7e5c22e22c879b14381ad76febfe2ea4 Mon Sep 17 00:00:00 2001 From: Joachim Schmitz Date: Sat, 4 Jan 2025 19:01:31 +0100 Subject: [PATCH] Add back webengine seems to be needed to sabe scores online, for the login --- CMakeLists.txt | 13 ++++ build/FindQt5.cmake | 8 +++ build/package_mac | 18 ++++- main/CMakeLists.txt | 22 +++++- mscore/cloud/loginmanager.cpp | 126 ++++++++++++++++++++++++++++++++++ mscore/cloud/loginmanager.h | 3 + mscore/cloud/loginmanager_p.h | 14 ++++ mscore/globals.h | 1 + mscore/startcenter.cpp | 95 +++++++++++++++++++++++++ mscore/startcenter.h | 50 ++++++++++++++ 10 files changed, 347 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a0a68dd2438d1..2c600f7d53c27 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,19 @@ if (MSVC) set (MINGW false) endif (MSVC) +# We need this early, before FindQt5 +option(BUILD_WEBENGINE "Built in webengine support" OFF) + +if (BUILD_WEBENGINE) + if (MINGW OR MSVC) + SET (USE_WEBENGINE 0) + else (MINGW OR MSVC) + SET (USE_WEBENGINE 1) + endif(MINGW OR MSVC) +else (BUILD_WEBENGINE) + SET (USE_WEBENGINE 0) +endif (BUILD_WEBENGINE) + set(SCRIPT_INTERFACE TRUE) # Look for Qt5 if (SCRIPT_INTERFACE) diff --git a/build/FindQt5.cmake b/build/FindQt5.cmake index a849f1791804c..d4282d254286a 100644 --- a/build/FindQt5.cmake +++ b/build/FindQt5.cmake @@ -19,6 +19,14 @@ set(_components LinguistTools Help ) +if (USE_WEBENGINE) + set(_components + ${_components} + WebEngine + WebEngineCore + WebEngineWidgets + ) +endif(USE_WEBENGINE) if (WIN32) set(_components diff --git a/build/package_mac b/build/package_mac index e5db2ed4a7d91..9416bd522da92 100755 --- a/build/package_mac +++ b/build/package_mac @@ -92,7 +92,20 @@ function change_rpath() { done } -rm -f ${WORKING_DIRECTORY}/${COMPRESSEDDMGNAME} +function change_rpath_QWebEngine() { + for P in `otool -L $1 | awk '{print $1}'` + do + if [[ "$P" == *@rpath* ]] + then + PSLASH=$(echo $P | sed 's,@rpath,@loader_path/../../../../../../..,g') + FNAME=$(echo $P | sed "s,@rpath,${VOLUME}/${APPNAME}.app/Contents/Frameworks,g") + install_name_tool -change $P $PSLASH $1 + fi + done +} + + +rm ${WORKING_DIRECTORY}/${COMPRESSEDDMGNAME} #tip: increase the size if error on copy or macdeployqt hdiutil create -size 800m -fs HFS+ -volname ${VOLNAME} ${WORKING_DIRECTORY}/${DMGNAME} @@ -138,6 +151,9 @@ macdeployqt ${VOLUME}/${APPNAME}.app # fix the libs, qt5.6 has @rpath... BIN_FILE=${VOLUME}/${APPNAME}.app/Contents/MacOS/mscore change_rpath $BIN_FILE +# fix the QWebEngineProcess, qt5.9 has @rpath... +WebEngineProcess=${VOLUME}/${APPNAME}.app/Contents/Frameworks/QtWebEngineCore.framework/Versions/5/Helpers/QtWebEngineProcess.app/Contents/MacOS/QtWebEngineProcess +change_rpath_QWebEngine $WebEngineProcess # Workaround: # fix Homebrew libraries with hard coded absolute path, see QTBUG-56814 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 3ae49ad8c55d8..9a75ad6a349f8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -229,7 +229,7 @@ else (MINGW) if ( NOT MSVC ) ## install qwebengine core - if (NOT APPLE) + if (NOT APPLE AND USE_WEBENGINE) install(PROGRAMS ${QT_INSTALL_LIBEXECS}/QtWebEngineProcess DESTINATION bin @@ -242,7 +242,7 @@ else (MINGW) ${QT_INSTALL_TRANSLATIONS}/qtwebengine_locales DESTINATION lib/qt5/translations ) - endif(NOT APPLE) + endif(NOT APPLE AND USE_WEBENGINE) set_target_properties ( mscore @@ -352,6 +352,9 @@ else (MINGW) "${QT_INSTALL_BINS}/libEGL.dll" "${QT_INSTALL_BINS}/libGLESv2.dll" "${QT_INSTALL_BINS}/opengl32sw.dll" "${QT_INSTALL_BINS}/d3dcompiler_47.dll" ) + if (USE_WEBENGINE) + list(APPEND dlls_to_copy "${QT_INSTALL_BINS}/Qt5WebEngineWidgets.dll" "${QT_INSTALL_BINS}/Qt5WebEngineCore.dll") + endif(USE_WEBENGINE) if (Qt5Widgets_VERSION VERSION_GREATER_EQUAL "5.14.1") list(APPEND dlls_to_copy "${QT_INSTALL_BINS}/Qt5QmlModels.dll") list(APPEND dlls_to_copy "${QT_INSTALL_BINS}/Qt5QmlWorkerScript.dll") @@ -449,6 +452,21 @@ else (MINGW) install( TARGETS mscore RUNTIME DESTINATION bin ) # this duplicate due to the correctly package step + if (USE_WEBENGINE) + install(FILES + ${QT_INSTALL_LIBEXECS}/QtWebEngineProcess.exe + DESTINATION bin + ) + install(DIRECTORY + ${QT_INSTALL_DATA}/resources + DESTINATION bin/webengineresources + ) + install(DIRECTORY + ${QT_INSTALL_TRANSLATIONS}/qtwebengine_locales + DESTINATION bin/webengineresources/translations + ) + endif (USE_WEBENGINE) + install(DIRECTORY ${QT_INSTALL_QML} DESTINATION . diff --git a/mscore/cloud/loginmanager.cpp b/mscore/cloud/loginmanager.cpp index a8045d6fa3adc..9f36f897a3df2 100644 --- a/mscore/cloud/loginmanager.cpp +++ b/mscore/cloud/loginmanager.cpp @@ -16,6 +16,10 @@ #include "libmscore/score.h" #include "preferences.h" +#ifdef USE_WEBENGINE +#include +#endif + namespace Ms { extern QString dataPath; @@ -131,7 +135,13 @@ QUrl ApiInfo::getUpdateScoreInfoUrl(const QString& scoreId, const QString& acces QUrlQuery query; query.addQueryItem("id", scoreId); query.addQueryItem("newScore", QString::number(newScore)); + +#ifdef USE_WEBENGINE + query.addQueryItem("_token", accessToken); +#else Q_UNUSED(accessToken); // we'll be redirected to a browser, don't put access token there +#endif + url.setQuery(query); return url; @@ -321,10 +331,82 @@ void LoginManager::onTryLoginError(const QString& error) disconnect(this, SIGNAL(getUserError(QString)), this, SLOT(onTryLoginError(QString))); connect(this, SIGNAL(loginSuccess()), this, SLOT(tryLogin())); logout(); +#ifdef USE_WEBENGINE + loginInteractive(); +#else mscore->showLoginDialog(); +#endif } /*------- END - TRY LOGIN ROUTINES ----------------------------*/ +//--------------------------------------------------------- +// clearHttpCacheOnRenderFinish +//--------------------------------------------------------- + +#ifdef USE_WEBENGINE +static void clearHttpCacheOnRenderFinish(QWebEngineView* webView) + { + QWebEnginePage* page = webView->page(); + QWebEngineProfile* profile = page->profile(); + + // workaround for the crashes sometimes happening in Chromium on macOS with Qt 5.12 + QObject::connect(webView, &QWebEngineView::renderProcessTerminated, webView, [profile, webView](QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) + { + qDebug() << "Login page loading terminated" << terminationStatus << " " << exitCode; + profile->clearHttpCache(); + webView->show(); + }); + } +#endif +//--------------------------------------------------------- +// loginInteractive +//--------------------------------------------------------- + +#ifdef USE_WEBENGINE +void LoginManager::loginInteractive() + { +#if defined(WIN_PORTABLE) + QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile(); + defaultProfile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + defaultProfile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + QWebEngineView* webView = new QWebEngineView; + webView->setWindowModality(Qt::ApplicationModal); + webView->setAttribute(Qt::WA_DeleteOnClose); + + QWebEnginePage* page = webView->page(); + QWebEngineProfile* profile = page->profile(); + // TODO: logout in editor does not log out in web view + profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); +#if defined(WIN_PORTABLE) + profile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + profile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + profile->setRequestInterceptor(new ApiWebEngineRequestInterceptor(profile)); + + clearHttpCacheOnRenderFinish(webView); + + connect(page, &QWebEnginePage::loadFinished, this, [this, page, webView](bool ok) { + if (!ok) + return; + constexpr QUrl::FormattingOptions cmpOpt = QUrl::RemoveQuery | QUrl::RemoveFragment | QUrl::StripTrailingSlash; + if (!page->url().matches(ApiInfo::loginSuccessUrl, cmpOpt)) + return; + + page->runJavaScript("JSON.stringify(muGetAuthInfo())", [this, page, webView](const QVariant& v) { + onLoginReply(nullptr, HTTP_OK, QJsonDocument::fromJson(v.toString().toUtf8()).object()); + // We have retrieved an access token, do not remain logged + // in with web view profile. + page->profile()->cookieStore()->deleteAllCookies(); + webView->close(); + }); + }); + + webView->load(ApiInfo::loginUrl); + webView->show(); + } +#endif + //--------------------------------------------------------- // login //--------------------------------------------------------- @@ -762,7 +844,35 @@ bool LoginManager::syncUpload(const QString& path, int nid, const QString& title void LoginManager::updateScoreData(const QString& nid, bool newScore) { const QUrl url(ApiInfo::getUpdateScoreInfoUrl(nid, _accessToken, newScore, _updateScoreDataPath)); +#ifdef USE_WEBENGINE +#if defined(WIN_PORTABLE) + QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile(); + defaultProfile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + defaultProfile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + QWebEngineView* webView = new QWebEngineView; + webView->setWindowModality(Qt::ApplicationModal); + webView->setAttribute(Qt::WA_DeleteOnClose); + + QWebEnginePage* page = webView->page(); + QWebEngineProfile* profile = page->profile(); + + profile->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); +#if defined(WIN_PORTABLE) + profile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + profile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + profile->setRequestInterceptor(new ApiWebEngineRequestInterceptor(profile)); + + connect(page, &QWebEnginePage::windowCloseRequested, webView, &QWebEngineView::close); + + clearHttpCacheOnRenderFinish(webView); + + webView->load(url); + webView->show(); +#else QDesktopServices::openUrl(url); +#endif } //--------------------------------------------------------- @@ -865,4 +975,20 @@ void ApiRequest::executeRequest(QNetworkAccessManager* networkManager) _reply->setParent(this); connect(_reply, &QNetworkReply::finished, this, [this]() { emit replyFinished(this); }); } + +//--------------------------------------------------------- +// ApiWebEngineRequestInterceptor::interceptRequest +// Sets the appropriate API headers for requests to +// musescore.com +//--------------------------------------------------------- + +#ifdef USE_WEBENGINE +void ApiWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& request) + { + const ApiInfo& apiInfo = ApiInfo::instance(); + request.setHttpHeader("User-Agent", apiInfo.userAgent); + request.setHttpHeader(apiInfo.clientIdHeader, apiInfo.clientId); + request.setHttpHeader(apiInfo.apiKeyHeader, apiInfo.apiKey); + } +#endif } diff --git a/mscore/cloud/loginmanager.h b/mscore/cloud/loginmanager.h index 78ef33db03365..2c0972c1512a0 100644 --- a/mscore/cloud/loginmanager.h +++ b/mscore/cloud/loginmanager.h @@ -100,6 +100,9 @@ class LoginManager : public QObject public: LoginManager(QAction* uploadAudioMenuAction, QObject* parent = 0); void login(QString login, QString password); +#ifdef USE_WEBENGINE + void loginInteractive(); +#endif void upload(const QString& path, int nid, const QString& title); void updateScoreData(const QString& nid, bool newScore); bool hasAccessToken(); diff --git a/mscore/cloud/loginmanager_p.h b/mscore/cloud/loginmanager_p.h index 6414452409200..3ad0bd6c813dc 100644 --- a/mscore/cloud/loginmanager_p.h +++ b/mscore/cloud/loginmanager_p.h @@ -121,6 +121,20 @@ class ApiRequest : public QObject int retryCount() const { return _retryCount; } }; +//--------------------------------------------------------- +// ApiWebEngineRequestInterceptor +//--------------------------------------------------------- + +#ifdef USE_WEBENGINE +class ApiWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor + { + Q_OBJECT + public: + ApiWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {} + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + }; +#endif + //--------------------------------------------------------- // AsyncWait //--------------------------------------------------------- diff --git a/mscore/globals.h b/mscore/globals.h index 1e14ae02fc431..b9cb54b680d4c 100644 --- a/mscore/globals.h +++ b/mscore/globals.h @@ -31,6 +31,7 @@ extern bool converterMode; extern bool pluginMode; extern double guiScaling; extern int trimMargin; +extern bool noWebView; extern bool ignoreWarnings; enum TelemetryDataCollectionType : unsigned char { diff --git a/mscore/startcenter.cpp b/mscore/startcenter.cpp index 9bad2037f5cbb..994e7dd16003e 100644 --- a/mscore/startcenter.cpp +++ b/mscore/startcenter.cpp @@ -58,6 +58,43 @@ Startcenter::Startcenter(QWidget* parent) connect(closeButton, SIGNAL(clicked()), this, SLOT(close())); setStyleSheet(QString("QPushButton { background-color: %1 }").arg(openScore->palette().color(QPalette::Base).name())); +#ifdef USE_WEBENGINE + if (!noWebView) { +#if defined(WIN_PORTABLE) + QWebEngineProfile* defaultProfile = QWebEngineProfile::defaultProfile(); + defaultProfile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + defaultProfile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + _webView = new MyWebView(this); + _webView->setMaximumWidth(200); + + MyWebEnginePage* page = new MyWebEnginePage(this); + MyWebUrlRequestInterceptor* wuri = new MyWebUrlRequestInterceptor(page); + QWebEngineProfile* profile = page->profile(); +#if defined(WIN_PORTABLE) + profile->setCachePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); + profile->setPersistentStoragePath(QDir::cleanPath(QString("%1/../../../Data/settings/QWebEngine").arg(QCoreApplication::applicationDirPath()))); +#endif + profile->setRequestInterceptor(wuri); + _webView->setPage(page); + + auto extendedVer = QString(VERSION) + "." + QString(BUILD_NUMBER); + QUrl connectPageUrl = QUrl(QString("https://connect2.musescore.com/?version=%1").arg(extendedVer)); + _webView->setUrl(connectPageUrl); + + horizontalLayout->addWidget(_webView); + + //workaround for the crashes sometimes happening in Chromium on macOS with Qt 5.12 + connect(_webView, &QWebEngineView::renderProcessTerminated, this, [this, profile, connectPageUrl](QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) + { + qDebug() << "Login page loading terminated" << terminationStatus << " " << exitCode; + profile->clearHttpCache(); + _webView->load(connectPageUrl); + _webView->show(); + }); + } +#endif + // if (enableExperimental) // right now don’t know how it use in WebEngine @handrok // QWebSettings::globalSettings()->setAttribute(QWebSettings::DeveloperExtrasEnabled, true); @@ -179,5 +216,63 @@ void Startcenter::keyReleaseEvent(QKeyEvent *event) else AbstractDialog::keyReleaseEvent(event); } + +#ifdef USE_WEBENGINE + +//--------------------------------------------------------- +// MyWebView +//--------------------------------------------------------- + +MyWebView::MyWebView(QWidget *parent): + QWebEngineView(parent) + { + if (!enableExperimental) + setContextMenuPolicy(Qt::NoContextMenu); + } + +//--------------------------------------------------------- +// ~MyWebView +//--------------------------------------------------------- + +MyWebView::~MyWebView() + { + disconnect(this, SIGNAL(loadFinished(bool)), this, SLOT(stopBusy(bool))); + } + +//--------------------------------------------------------- +// sizeHint +//--------------------------------------------------------- + +QSize MyWebView::sizeHint() const + { + return QSize(200 , 600); + } + + +bool MyWebEnginePage::acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame) + { + qDebug() << "acceptNavigationRequest(" << url << "," << type << "," << isMainFrame << ")"; + + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { + QString path(url.path()); + QFileInfo fi(path); + if (fi.suffix() == "mscz" || fi.suffix() == "xml" + || fi.suffix() == "musicxml" || fi.suffix() == "mxl") { + mscore->loadFile(url); + QAction* a = getAction("startcenter"); + a->setChecked(false); + mscore->showStartcenter(false); + } + else + QDesktopServices::openUrl(url); + + return false; + } + return true; + } + + +#endif //USE_WEBENGINE } diff --git a/mscore/startcenter.h b/mscore/startcenter.h index d6b5214d38c09..698ac671c12de 100644 --- a/mscore/startcenter.h +++ b/mscore/startcenter.h @@ -19,12 +19,62 @@ namespace Ms { +#ifdef USE_WEBENGINE + +class MyWebUrlRequestInterceptor : public QWebEngineUrlRequestInterceptor { + Q_OBJECT + + public: + MyWebUrlRequestInterceptor(QObject* p = Q_NULLPTR) + : QWebEngineUrlRequestInterceptor(p) {} + + void interceptRequest(QWebEngineUrlRequestInfo& info) + { + info.setHttpHeader("Accept-Language", + QString("%1;q=0.8,en-US;q=0.6,en;q=0.4").arg(mscore->getLocaleISOCode()).toUtf8()); + } + }; + +//--------------------------------------------------------- +// MyWebEnginePage +//--------------------------------------------------------- + +class MyWebEnginePage : public QWebEnginePage { + Q_OBJECT + + public: + MyWebEnginePage(QObject* parent = Q_NULLPTR) + : QWebEnginePage(parent) {} + + bool acceptNavigationRequest(const QUrl& url, QWebEnginePage::NavigationType type, bool isMainFrame); + }; + +//--------------------------------------------------------- +// MyWebEngineView +//--------------------------------------------------------- + +class MyWebView : public QWebEngineView { + Q_OBJECT + + public slots: + + public: + MyWebView(QWidget* parent = 0); + ~MyWebView(); + virtual QSize sizeHint() const; + }; + +#endif //USE_WEBENGINE + //--------------------------------------------------------- // Startcenter //--------------------------------------------------------- class Startcenter : public AbstractDialog, public Ui::Startcenter { Q_OBJECT +#ifdef USE_WEBENGINE + MyWebView* _webView; +#endif virtual void closeEvent(QCloseEvent*); private slots: