diff --git a/spark-webapp-runtime.pro b/spark-webapp-runtime.pro index 20e159b..a41f2b4 100644 --- a/spark-webapp-runtime.pro +++ b/spark-webapp-runtime.pro @@ -1,6 +1,6 @@ TEMPLATE = subdirs SUBDIRS += \ - spark-webapp-runtime + spark-webapp-runtime CONFIG += ordered diff --git a/spark-webapp-runtime/application.cpp b/spark-webapp-runtime/application.cpp new file mode 100644 index 0000000..9ac7b4d --- /dev/null +++ b/spark-webapp-runtime/application.cpp @@ -0,0 +1,81 @@ +#include "application.h" +#include "globaldefine.h" + +#include <DPlatformWindowHandle> +#include <DAboutDialog> + +Application::Application(int &argc, char **argv) + : DApplication(argc, argv) +{ + loadTranslator(); + + setAttribute(Qt::AA_UseHighDpiPixmaps); + if (!DPlatformWindowHandle::pluginVersion().isEmpty()) { + setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); + } + + setApplicationVersion(QString(CURRENT_VER)); + setOrganizationName(ORGANIZATION_NAME); // 添加组织名称,和商店主体的文件夹同在 ~/.local/share/spark-union 文件夹下 + setApplicationName(APPLICATION_NAME); // 这里不要翻译,否则 ~/.local/share 中文件夹名也会被翻译 + setProductName(DEFAULT_TITLE); + setApplicationDisplayName(DEFAULT_TITLE); + setApplicationLicense(" <a href='https://www.gnu.org/licenses/gpl-3.0.html'>GPLv3</a> "); + + initAboutDialog(); +} + +void Application::handleAboutAction() +{ + if (aboutDialog()) { + DApplication::handleAboutAction(); + return; + } + + initAboutDialog(); + DApplication::handleAboutAction(); +} + +void Application::initAboutDialog() +{ + // Customized DAboutDialog + DAboutDialog *dialog = new DAboutDialog(activeWindow()); + // WindowIcon + dialog->setWindowIcon(QIcon(":/images/spark-webapp-runtime.svg")); + // ProductIcon + dialog->setProductIcon(QIcon(":/images/spark-webapp-runtime.svg")); + // ProductName + dialog->setProductName(productName()); + // Version + dialog->setVersion(translate("DAboutDialog", "Version: %1").arg(applicationVersion())); + // CompanyLogo + dialog->setCompanyLogo(QPixmap(":/images/Logo-Spark.png")); + // Description + + QString szDefaultDesc = QString("<a href='https://gitee.com/deepin-community-store/spark-web-app-runtime'><span style='font-size:12pt;font-weight:500;'>%1</span></a><br/>" + "<span style='font-size:12pt;'>%2</span>") + .arg(DEFAULT_TITLE) + .arg(QObject::tr("Presented By Spark developers # HadesStudio")); + + dialog->setDescription(szDefaultDesc); + // WebsiteName + dialog->setWebsiteName("Spark Project"); + // WebsiteLink + dialog->setWebsiteLink("https://gitee.com/deepin-community-store/"); + // License + dialog->setLicense(translate("DAboutDialog", "%1 is released under %2").arg(productName()).arg(applicationLicense())); + + setAboutDialog(dialog); + connect(aboutDialog(), &DAboutDialog::destroyed, this, [=] { + setAboutDialog(nullptr); + }); + + dialog->hide(); +} + +void Application::slotMainWindowClose() +{ + if (aboutDialog()) { + aboutDialog()->close(); + aboutDialog()->deleteLater(); + } +} diff --git a/spark-webapp-runtime/application.h b/spark-webapp-runtime/application.h new file mode 100644 index 0000000..30f7597 --- /dev/null +++ b/spark-webapp-runtime/application.h @@ -0,0 +1,26 @@ +#ifndef APPLICATION_H +#define APPLICATION_H + +#include <DApplication> + +DWIDGET_USE_NAMESPACE + +class Application : public DApplication +{ + Q_OBJECT + +public: + Application(int &argc, char **argv); + void handleAboutAction() override; + +private: + void initAboutDialog(); + +signals: + void sigQuit(); + +public slots: + void slotMainWindowClose(); +}; + +#endif // APPLICATION_H diff --git a/spark-webapp-runtime/globaldefine.h b/spark-webapp-runtime/globaldefine.h index 276638f..606903f 100644 --- a/spark-webapp-runtime/globaldefine.h +++ b/spark-webapp-runtime/globaldefine.h @@ -10,8 +10,8 @@ #define DEFAULT_WIDTH (1024) #define DEFAULT_HEIGHT (768) -#define DEFAULT_DESC QString() #define DEFAULT_ICON QString() +#define DEFAULT_DESC QString() #define DEFAULT_CFG QString() #define DEFAULT_ROOT QString() diff --git a/spark-webapp-runtime/main.cpp b/spark-webapp-runtime/main.cpp index f1503a2..4eba78b 100644 --- a/spark-webapp-runtime/main.cpp +++ b/spark-webapp-runtime/main.cpp @@ -1,30 +1,28 @@ -/* - * +/** + * Spark WebApp Runtime * 星火网页应用运行环境 -*/ + */ +#include "application.h" #include "mainwindow.h" +#include "globaldefine.h" +#include "httpd.h" -#include <DApplication> -#include <DPlatformWindowHandle> +#include <DSysInfo> #include <QCommandLineParser> #include <QCommandLineOption> #include <QFileInfo> #include <QSettings> -#include <QVector> -#include "globaldefine.h" -#include "httpd.h" - -DWIDGET_USE_NAMESPACE +#include <unistd.h> int main(int argc, char *argv[]) { if (!QString(qgetenv("XDG_CURRENT_DESKTOP")).toLower().startsWith("deepin")) { - setenv("XDG_CURRENT_DESKTOP", "Deepin", 1); + qputenv("XDG_CURRENT_DESKTOP", "Deepin"); } - // 龙芯机器配置,使得DApplication能正确加载QTWEBENGINE + // 龙芯机器配置,使得 DApplication 能正确加载 QTWEBENGINE qputenv("DTK_FORCE_RASTER_WIDGETS", "FALSE"); // qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-features=UseModernMediaControls"); // qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-web-security"); @@ -32,60 +30,27 @@ int main(int argc, char *argv[]) qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox"); #endif + if (!Dtk::Core::DSysInfo::isDDE()) { #ifndef DSTORE_NO_DXCBs - Dtk::Widget::DApplication::loadDXcbPlugin(); + DApplication::loadDXcbPlugin(); #endif - - // 强制使用DTK平台插件 - QVector<char *> fakeArgv(argc + 2); - fakeArgv[0] = argv[0]; - fakeArgv[1] = "-platformtheme"; - fakeArgv[2] = "deepin"; - for(int i = 1; i < argc; i++) fakeArgv[i + 2] = argv[i]; - int fakeArgc = argc + 2; - DApplication a(fakeArgc, fakeArgv.data()); - - a.loadTranslator(); - a.setAttribute(Qt::AA_UseHighDpiPixmaps); - if (!Dtk::Widget::DPlatformWindowHandle::pluginVersion().isEmpty()) { - a.setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true); } - a.setApplicationVersion(QString(CURRENT_VER)); - a.setOrganizationName(ORGANIZATION_NAME); // 添加组织名称,和商店主体的文件夹同在 ~/.local/share/spark-union 文件夹下 - a.setApplicationName(APPLICATION_NAME); // 这里不要翻译,否则 ~/.local/share 中文件夹名也会被翻译 - a.setProductName(DEFAULT_TITLE); - a.setApplicationDisplayName(DEFAULT_TITLE); + // 开启 HiDPI 缩放支持 + DApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - // Customized DAboutDialog (Can't work on other distro like Ubuntu...) - DAboutDialog *dialog = new DAboutDialog; - a.setAboutDialog(dialog); - - // WindowIcon - dialog->setWindowIcon(QIcon(":/images/spark-webapp-runtime.svg")); - // ProductIcon - dialog->setProductIcon(QIcon(":/images/spark-webapp-runtime.svg")); - // ProductName - dialog->setProductName(QString("<span>%1</span>").arg(DEFAULT_TITLE)); - // Version - dialog->setVersion(QString("%1 %2").arg(QObject::tr("Version:")).arg(CURRENT_VER)); - // CompanyLogo - dialog->setCompanyLogo(QPixmap(":/images/Logo-Spark.png")); - // Description - - QString szDefaultDesc = QString("<a href='https://gitee.com/deepin-community-store/spark-web-app-runtime'><span style='font-size:12pt;font-weight:500;'>%1</span></a><br/>" - "<span style='font-size:12pt;'>%2</span>") - .arg(DEFAULT_TITLE) - .arg(QObject::tr("Presented By Spark developers # HadesStudio")); - - dialog->setDescription(szDefaultDesc); - // WebsiteName - dialog->setWebsiteName("Spark Project"); - // WebsiteLink - dialog->setWebsiteLink("https://gitee.com/deepin-community-store/"); - // License - dialog->setLicense(QObject::tr("Published under GPLv3")); + // 强制使用 DTK 平台插件 + int fakeArgc = argc + 2; + QVector<char *> fakeArgv(fakeArgc); + fakeArgv[0] = argv[0]; + fakeArgv[1] = QString("-platformtheme").toUtf8().data(); + fakeArgv[2] = QString("deepin").toUtf8().data(); + for (int i = 1; i < argc; i++) { + fakeArgv[i + 2] = argv[i]; + } + Application a(fakeArgc, fakeArgv.data()); + // 解析命令行启动参数 QCommandLineParser parser; parser.setApplicationDescription(QObject::tr("Description: %1").arg(DEFAULT_TITLE)); @@ -174,15 +139,14 @@ int main(int argc, char *argv[]) << "port", QObject::tr("The port number of the program web service."), "port", - DEFAULT_PORT); + QString::number(DEFAULT_PORT)); parser.addOption(optPort); - QCommandLineOption useGPU(QStringList() << "G" << "GPU", - QObject::tr("To use GPU instead of CPU to decoding. Default True."), - "GPU", - QString::number(DEFAULT_GPU)); + QObject::tr("To use GPU instead of CPU to decoding. Default True."), + "GPU", + QString::number(DEFAULT_GPU)); parser.addOption(useGPU); #if SSL_SERVER @@ -212,7 +176,12 @@ int main(int argc, char *argv[]) #if SSL_SERVER quint16 u16sslPort = 0; #endif + QString szDefaultDesc = QString("<a href='https://gitee.com/deepin-community-store/spark-web-app-runtime'><span style='font-size:12pt;font-weight:500;'>%1</span></a><br/>" + "<span style='font-size:12pt;'>%2</span>") + .arg(DEFAULT_TITLE) + .arg(QObject::tr("Presented By Spark developers # HadesStudio")); + // 解析可能存在的配置文件 QString szCfgFile = DEFAULT_CFG; if (parser.isSet(optCfgFile)) { szCfgFile = parser.value(optCfgFile); @@ -232,7 +201,7 @@ int main(int argc, char *argv[]) .arg(settings.value("SparkWebAppRuntime/Desc", QString()).toString()) .arg(szDefaultDesc); szRootPath = settings.value("SparkWebAppRuntime/RootPath", QString()).toString(); - u16Port = settings.value("SparkWebAppRuntime/Port", 0).toUInt(); + u16Port = static_cast<quint16>(settings.value("SparkWebAppRuntime/Port", 0).toUInt()); #if SSL_SERVER u16sslPort = settings.value("SparkWebAppRuntime/SSLPort", 0).toUInt(); #endif @@ -267,6 +236,9 @@ int main(int argc, char *argv[]) hideButtons = true; } + if (parser.isSet(optIcon)) { + szIcon = parser.value(optIcon); + } if (parser.isSet(optDesc)) { szDesc = QString("%1<br/><br/>%2").arg(parser.value(optDesc)).arg(szDefaultDesc); } @@ -276,17 +248,17 @@ int main(int argc, char *argv[]) } if (parser.isSet(optPort)) { - u16Port = parser.value(optPort).toUInt(); + u16Port = static_cast<quint16>(parser.value(optPort).toUInt()); } if (parser.isSet(useGPU)) { toUseGPU = parser.value(useGPU).toUInt(); } - if (toUseGPU == true){ - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--ignore-gpu-blocklist --enable-gpu-rasterization --enable-native-gpu-memory-buffers --enable-accelerated-video-decode"); - #ifdef __sw_64__ - qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--ignore-gpu-blocklist --enable-gpu-rasterization --enable-native-gpu-memory-buffers --enable-accelerated-video-decode --no-sandbox"); - #endif + if (toUseGPU == true) { + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--ignore-gpu-blocklist --enable-gpu-rasterization --enable-native-gpu-memory-buffers --enable-accelerated-video-decode --blink-settings=darkMode=4,darkModeImagePolicy=2"); +#ifdef __sw_64__ + qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--ignore-gpu-blocklist --enable-gpu-rasterization --enable-native-gpu-memory-buffers --enable-accelerated-video-decode --blink-settings=darkMode=4,darkModeImagePolicy=2 --no-sandbox"); +#endif qDebug() << "Setting GPU to True."; } @@ -335,7 +307,7 @@ int main(int argc, char *argv[]) szRootPath = QString(argv[11]); } if (argc > 12) { - u16Port = QString(argv[12]).toUInt(); + u16Port = static_cast<quint16>(QString(argv[12]).toUInt()); } #if SSL_SERVER if (argc > 13) { @@ -348,10 +320,13 @@ int main(int argc, char *argv[]) fullScreen = false; // 固定窗口大小时禁用全屏模式,避免标题栏按钮 BUG } + // DApplication 单例运行(标题名称_当前登录用户 id) + if (!a.setSingleInstance(szTitle + "_" + QString::number(getuid()))) { + qInfo() << "Another instance has already started, now exit..."; + exit(0); + } a.setQuitOnLastWindowClosed(!tray); // 启用托盘时,退出程序后服务不终止 - MainWindow w(szTitle, szUrl, width, height, tray, fullScreen, fixSize, hideButtons, dialog); - #if SSL_SERVER if (!szRootPath.isEmpty() && u16Port > 0 && u16sslPort > 0) { HttpD httpd(szRootPath, u16Port, u16sslPort); @@ -360,24 +335,23 @@ int main(int argc, char *argv[]) #else if (!szRootPath.isEmpty() && u16Port > 0) { static HttpD httpd(szRootPath, u16Port); - QObject::connect(&w, &MainWindow::sigQuit, &httpd, &HttpD::stop); + QObject::connect(&a, &Application::sigQuit, &httpd, &HttpD::stop); httpd.start(); } #endif - if (parser.isSet(optIcon)) { - szIcon = parser.value(optIcon); - } + MainWindow w(szTitle, szUrl, width, height, tray, fullScreen, fixSize, hideButtons); + QObject::connect(&a, &Application::newInstanceStarted, &w, &MainWindow::slotNewInstanceStarted); + QObject::connect(&w, &MainWindow::sigClose, &a, &Application::slotMainWindowClose); if (!szIcon.isEmpty()) { - dialog->setWindowIcon(QIcon(szIcon)); - dialog->setProductIcon(QIcon(szIcon)); w.setIcon(szIcon); } if (!szDesc.isEmpty()) { - dialog->setDescription(szDesc); + w.setDescription(szDesc); } w.show(); + return a.exec(); } diff --git a/spark-webapp-runtime/mainwindow.cpp b/spark-webapp-runtime/mainwindow.cpp index c46d810..883620a 100644 --- a/spark-webapp-runtime/mainwindow.cpp +++ b/spark-webapp-runtime/mainwindow.cpp @@ -1,16 +1,19 @@ #include "mainwindow.h" +#include "application.h" +#include "webengineview.h" +#include "webenginepage.h" #include <DWidgetUtil> #include <DTitlebar> #include <DMessageManager> #include <DDesktopServices> +#include <QKeyEvent> +#include <QWebEngineProfile> #include <QFileInfo> #include <QFileDialog> #include <QDir> -#include <QCloseEvent> - -#include "webengineview.h" +#include <QStandardPaths> MainWindow::MainWindow(QString szTitle, QString szUrl, @@ -20,7 +23,6 @@ MainWindow::MainWindow(QString szTitle, bool nFullScreen, bool nFixSize, bool nHideButtons, - QDialog *dialog, QWidget *parent) : DMainWindow(parent) , m_title(szTitle) @@ -32,27 +34,26 @@ MainWindow::MainWindow(QString szTitle, , m_isFixedSize(nFixSize) , m_isHideButton(nHideButtons) , m_widget(new Widget(m_url, this)) - , m_dialog(dynamic_cast<DAboutDialog *>(dialog)) , m_tray(new QSystemTrayIcon(this)) , btnBack(new DToolButton(titlebar())) , btnForward(new DToolButton(titlebar())) , btnRefresh(new DToolButton(titlebar())) , m_menu(new QMenu(titlebar())) - , m_fullScreen(new QAction(tr("Full Screen"), this)) - , m_fixSize(new QAction(tr("Fix Size"), this)) - , m_hideButtons(new QAction(tr("Hide Buttons"), this)) - , m_clearCache(new QAction(tr("Clear Cache"), this)) + , m_fullScreen(new QAction(QObject::tr("Full Screen"), this)) + , m_fixSize(new QAction(QObject::tr("Fix Size"), this)) + , m_hideButtons(new QAction(QObject::tr("Hide Buttons"), this)) + , m_clearCache(new QAction(QObject::tr("Clear Cache"), this)) , t_menu(new QMenu(this)) - , t_show(new QAction(tr("Show MainWindow"), this)) - , t_about(new QAction(tr("About"), this)) - , t_exit(new QAction(tr("Exit"), this)) + , t_show(new QAction(QObject::tr("Show MainWindow"), this)) + , t_about(new QAction(qApp->translate("TitleBarMenu", "About"), this)) + , t_exit(new QAction(qApp->translate("TitleBarMenu", "Exit"), this)) , downloadMessage(new DFloatingMessage(DFloatingMessage::ResidentType, this)) , downloadProgressWidget(new QWidget(downloadMessage)) , progressBarLayout(new QHBoxLayout(downloadProgressWidget)) , downloadProgressBar(new DProgressBar(downloadProgressWidget)) - , btnPause(new DPushButton(tr("Pause"), downloadProgressWidget)) - , btnResume(new DPushButton(tr("Resume"), downloadProgressWidget)) - , btnCancel(new DPushButton(tr("Cancel"), downloadProgressWidget)) + , btnPause(new DPushButton(QObject::tr("Pause"), downloadProgressWidget)) + , btnResume(new DPushButton(QObject::tr("Resume"), downloadProgressWidget)) + , btnCancel(new DPushButton(QObject::tr("Cancel"), downloadProgressWidget)) , isCanceled(false) { initUI(); @@ -62,16 +63,30 @@ MainWindow::MainWindow(QString szTitle, MainWindow::~MainWindow() { - emit sigQuit(); - delete m_dialog; } void MainWindow::setIcon(QString szIconPath) { - if (QFileInfo(szIconPath).exists()) { - titlebar()->setIcon(QIcon(szIconPath)); - setWindowIcon(QIcon(szIconPath)); - m_tray->setIcon(QIcon(szIconPath)); + if (!QFileInfo(szIconPath).exists()) { + return; + } + + titlebar()->setIcon(QIcon(szIconPath)); + setWindowIcon(QIcon(szIconPath)); + m_tray->setIcon(QIcon(szIconPath)); + + DAboutDialog *aboutDialog = qobject_cast<Application *>(qApp)->aboutDialog(); + if (aboutDialog) { + aboutDialog->setWindowIcon(QIcon::fromTheme(szIconPath)); + aboutDialog->setProductIcon(QIcon::fromTheme(szIconPath)); + } +} + +void MainWindow::setDescription(const QString &desc) +{ + DAboutDialog *aboutDialog = qobject_cast<Application *>(qApp)->aboutDialog(); + if (aboutDialog) { + aboutDialog->setDescription(desc); } } @@ -85,7 +100,8 @@ void MainWindow::keyPressEvent(QKeyEvent *event) m_menu->update(); } } - event->accept(); + + DMainWindow::keyPressEvent(event); } void MainWindow::resizeEvent(QResizeEvent *event) @@ -98,15 +114,17 @@ void MainWindow::resizeEvent(QResizeEvent *event) m_fixSize->setEnabled(true); // 命令行参数没有固定窗口大小时,窗口模式下允许手动选择固定窗口大小 } } + DMainWindow::resizeEvent(event); } void MainWindow::closeEvent(QCloseEvent *event) { if (!m_isTrayEnabled) { - m_dialog->close(); // 不启用托盘时,关闭主窗口则关闭关于窗口 + emit sigClose(); // 不启用托盘时,关闭主窗口则关闭关于窗口 } - event->accept(); + + DMainWindow::closeEvent(event); } void MainWindow::initUI() @@ -240,8 +258,7 @@ void MainWindow::initConnections() fixSize(); }); connect(t_about, &QAction::triggered, this, [=]() { - m_dialog->activateWindow(); - m_dialog->show(); + qobject_cast<Application *>(qApp)->handleAboutAction(); }); connect(t_exit, &QAction::triggered, this, [=]() { exit(0); @@ -262,14 +279,14 @@ void MainWindow::fullScreen() m_fixSize->setDisabled(true); m_menu->update(); showFullScreen(); - // DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-information").pixmap(64, 64), QString(tr("%1Fullscreen Mode")).arg(" ")); + // DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-information").pixmap(64, 64), QString(QObject::tr("%1Fullscreen Mode")).arg(" ")); } else { if (!m_isFixedSize) { m_fixSize->setDisabled(false); // 命令行参数没有固定窗口大小时,窗口模式下允许手动选择固定窗口大小 } m_menu->update(); showNormal(); - // DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-information").pixmap(64, 64), QString(tr("%1Windowed Mode")).arg(" ")); + // DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-information").pixmap(64, 64), QString(QObject::tr("%1Windowed Mode")).arg(" ")); } } @@ -307,7 +324,7 @@ void MainWindow::hideButtons() void MainWindow::clearCache() { // 清除缓存文件夹并刷新页面 - QDir dir(QDir::homePath() + "/.local/share/" + ORGANIZATION_NAME + "/" + APPLICATION_NAME); + QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); if (dir.exists()) { dir.removeRecursively(); } @@ -317,7 +334,9 @@ void MainWindow::clearCache() QString MainWindow::saveAs(QString fileName) { - QString saveFile = QFileDialog::getSaveFileName(this, tr("Save As"), QDir::homePath() + "/Downloads/" + fileName); + QString saveFile = QFileDialog::getSaveFileName(this, + QObject::tr("Save As"), + QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileName); if (!saveFile.isEmpty()) { // 判断上层目录是否可写入 if (QFileInfo(QFileInfo(saveFile).absolutePath()).isWritable()) { @@ -329,11 +348,19 @@ QString MainWindow::saveAs(QString fileName) return nullptr; } +void MainWindow::slotNewInstanceStarted() +{ + this->setWindowState(Qt::WindowActive); + this->activateWindow(); + this->show(); +} + void MainWindow::on_trayIconActivated(QSystemTrayIcon::ActivationReason reason) { switch (reason) { /* 响应托盘点击事件 */ case QSystemTrayIcon::Trigger: + this->setWindowState(Qt::WindowActive); this->activateWindow(); fixSize(); break; @@ -372,7 +399,7 @@ void MainWindow::on_downloadStart(QWebEngineDownloadItem *item) DFloatingMessage *message = new DFloatingMessage(DFloatingMessage::TransientType); message->setIcon(QIcon::fromTheme("dialog-information").pixmap(64, 64)); - message->setMessage(QString(tr("%1Start downloading %2")).arg(" ").arg(fileName)); + message->setMessage(QString(QObject::tr("%1Start downloading %2")).arg(" ").arg(fileName)); DMessageManager::instance()->sendMessage(this, message); item->accept(); @@ -383,7 +410,7 @@ void MainWindow::on_downloadStart(QWebEngineDownloadItem *item) btnPause->show(); this->downloadMessage->show(); // 上一次下载完成后隐藏了进度条,这里要重新显示 } else { - DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-cancel").pixmap(64, 64), QString(tr("%1Wait for previous download to complete!")).arg(" ")); + DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-cancel").pixmap(64, 64), QString(QObject::tr("%1Wait for previous download to complete!")).arg(" ")); } } @@ -405,17 +432,18 @@ void MainWindow::on_downloadFinish(QString filePath) // 下载完成显示提示信息 if (!isCanceled) { - DPushButton *button = new DPushButton(tr("Open")); + DPushButton *button = new DPushButton(QObject::tr("Open")); DFloatingMessage *message = new DFloatingMessage(DFloatingMessage::ResidentType); message->setIcon(QIcon::fromTheme("dialog-ok").pixmap(64, 64)); - message->setMessage(QString(" %1 %2 %3").arg(QFileInfo(filePath).fileName()).arg(tr("download finished.")).arg(tr("Show in file manager?"))); + message->setMessage(QString(" %1 %2 %3").arg(QFileInfo(filePath).fileName()).arg(QObject::tr("download finished.")).arg(QObject::tr("Show in file manager?"))); message->setWidget(button); DMessageManager::instance()->sendMessage(this, message); connect(button, &DPushButton::clicked, this, [=]() { DDesktopServices::showFileItem(filePath); message->hide(); + message->deleteLater(); }); } } @@ -446,5 +474,5 @@ void MainWindow::on_downloadCancel(QWebEngineDownloadItem *item) mutex.unlock(); downloadMessage->hide(); - DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-error").pixmap(64, 64), QString(tr("%1Download canceled!")).arg(" ")); + DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-error").pixmap(64, 64), QString(QObject::tr("%1Download canceled!")).arg(" ")); } diff --git a/spark-webapp-runtime/mainwindow.h b/spark-webapp-runtime/mainwindow.h index c060638..935cfa9 100644 --- a/spark-webapp-runtime/mainwindow.h +++ b/spark-webapp-runtime/mainwindow.h @@ -30,11 +30,11 @@ public: bool nFullScreen = false, bool nFixSize = false, bool nHideButtons = false, - QDialog *dialog = nullptr, QWidget *parent = nullptr); ~MainWindow(); void setIcon(QString szIconPath); + void setDescription(const QString &desc); protected: void keyPressEvent(QKeyEvent *event); @@ -56,7 +56,10 @@ private: QString saveAs(QString fileName); signals: - void sigQuit(); + void sigClose(); + +public slots: + void slotNewInstanceStarted(); private slots: void on_trayIconActivated(QSystemTrayIcon::ActivationReason reason); @@ -74,7 +77,6 @@ private: bool m_isTrayEnabled, m_isFullScreen, m_isFixedSize, m_isHideButton; Widget *m_widget; - DAboutDialog *m_dialog; QSystemTrayIcon *m_tray; DToolButton *btnBack; diff --git a/spark-webapp-runtime/spark-webapp-runtime.pro b/spark-webapp-runtime/spark-webapp-runtime.pro index a76bd94..87016fa 100644 --- a/spark-webapp-runtime/spark-webapp-runtime.pro +++ b/spark-webapp-runtime/spark-webapp-runtime.pro @@ -7,7 +7,7 @@ TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS CONFIG += c++11 link_pkgconfig -PKGCONFIG += dtkwidget +PKGCONFIG += dtkcore dtkgui dtkwidget HEADERS += \ mainwindow.h \ @@ -15,14 +15,20 @@ HEADERS += \ httpd.h \ httplib.h \ widget.h \ - webengineview.h + webengineview.h \ + webenginepage.h \ + application.h \ + webengineurlrequestinterceptor.h SOURCES += \ main.cpp \ httpd.cpp \ mainwindow.cpp \ widget.cpp \ - webengineview.cpp + webengineview.cpp \ + webenginepage.cpp \ + application.cpp \ + webengineurlrequestinterceptor.cpp RESOURCES += \ imgs.qrc diff --git a/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.qm b/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.qm index b87ba60..aabae4c 100644 Binary files a/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.qm and b/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.qm differ diff --git a/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.ts b/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.ts index 8849e49..96a00f5 100644 --- a/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.ts +++ b/spark-webapp-runtime/translations/spark-webapp-runtime_zh_CN.ts @@ -2,200 +2,108 @@ <!DOCTYPE TS> <TS version="2.1" language="zh_CN"> <context> - <name>MainWindow</name> + <name>DAboutDialog</name> <message> - <location filename="../mainwindow.cpp" line="41"/> - <source>Full Screen</source> - <translation>全屏显示</translation> + <location filename="../application.cpp" line="49"/> + <source>Version: %1</source> + <translation>版本:%1</translation> </message> <message> - <location filename="../mainwindow.cpp" line="42"/> - <source>Fix Size</source> - <translation>固定大小</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="43"/> - <source>Hide Buttons</source> - <translation>隐藏按钮</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="44"/> - <source>Clear Cache</source> - <translation>清理缓存</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="46"/> - <source>Show MainWindow</source> - <translation>显示主界面</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="47"/> - <source>About</source> - <translation>关于</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="48"/> - <source>Exit</source> - <translation>退出</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="53"/> - <source>Pause</source> - <translation>暂停</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="54"/> - <source>Resume</source> - <translation>继续</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="55"/> - <source>Cancel</source> - <translation>取消</translation> - </message> - <message> - <source>%1Fullscreen Mode</source> - <translation type="vanished">%1全屏模式</translation> - </message> - <message> - <source>%1Windowed Mode</source> - <translation type="vanished">%1窗口模式</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="321"/> - <source>Save As</source> - <translation>另存为</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="376"/> - <source>%1Start downloading %2</source> - <translation>%1开始下载 %2</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="387"/> - <source>%1Wait for previous download to complete!</source> - <translation>%1请等待上一个下载任务完成!</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="409"/> - <source>Open</source> - <translation>打开</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="413"/> - <source>download finished.</source> - <translation>下载完成。</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="413"/> - <source>Show in file manager?</source> - <translation>是否在文件管理器中显示?</translation> - </message> - <message> - <location filename="../mainwindow.cpp" line="450"/> - <source>%1Download canceled!</source> - <translation>%1下载取消!</translation> + <location filename="../application.cpp" line="65"/> + <source>%1 is released under %2</source> + <translation>%1遵循%2协议发布</translation> </message> </context> <context> <name>QObject</name> <message> - <location filename="../main.cpp" line="71"/> + <location filename="../application.cpp" line="57"/> + <location filename="../main.cpp" line="182"/> <source>Presented By Spark developers # HadesStudio</source> <translation>由 星火开发者联盟 @ 花心胡萝卜 提供</translation> </message> <message> - <location filename="../main.cpp" line="63"/> - <source>Version:</source> - <translation>版本:</translation> - </message> - <message> - <location filename="../main.cpp" line="79"/> - <source>Published under GPLv3</source> - <translation>遵循 GPLv3 协议发布</translation> - </message> - <message> - <location filename="../main.cpp" line="83"/> + <location filename="../main.cpp" line="56"/> <source>Description: %1</source> <translation>描述:%1</translation> </message> <message> - <location filename="../main.cpp" line="89"/> + <location filename="../main.cpp" line="62"/> <source>Enable CommandLineParser. Default is false.</source> <translation>启用参数解析方式。默认顺序解析方式。</translation> </message> <message> - <location filename="../main.cpp" line="94"/> + <location filename="../main.cpp" line="67"/> <source>The Title of Application. Default is %1.</source> <translation>设置程序的运行标题。默认是 %1。</translation> </message> <message> - <location filename="../main.cpp" line="101"/> + <location filename="../main.cpp" line="74"/> <source>The target URL. Default is Blank.</source> <translation>设置要打开的目标 URL。默认是空。</translation> </message> <message> - <location filename="../main.cpp" line="108"/> + <location filename="../main.cpp" line="81"/> <source>The Width of Application. Default is %1.</source> <translation>设置应用的窗口宽度。默认是 %1。</translation> </message> <message> - <location filename="../main.cpp" line="115"/> + <location filename="../main.cpp" line="88"/> <source>The Height of Application. Default is %1.</source> <translation>设置应用的窗口高度。默认是 %1。</translation> </message> <message> - <location filename="../main.cpp" line="122"/> + <location filename="../main.cpp" line="95"/> <source>Enable Tray Icon. Default is false.</source> <translation>启用托盘图标。默认不启用。</translation> </message> <message> - <location filename="../main.cpp" line="126"/> + <location filename="../main.cpp" line="99"/> <source>Run in Fullscreen Mode. Default is false.</source> <translation>以全屏模式运行。默认关闭该功能。</translation> </message> <message> - <location filename="../main.cpp" line="130"/> + <location filename="../main.cpp" line="103"/> <source>Fix Window Size. Default is false.</source> <translation>固定窗口大小。默认关闭该功能。</translation> </message> <message> - <location filename="../main.cpp" line="134"/> + <location filename="../main.cpp" line="107"/> <source>Hide Control Buttons. Default is false.</source> <translation>隐藏控制按钮。默认关闭该此功能。</translation> </message> <message> - <location filename="../main.cpp" line="147"/> + <location filename="../main.cpp" line="112"/> <source>The ICON of Application.</source> <translation>设置应用的图标。</translation> </message> <message> - <location filename="../main.cpp" line="154"/> + <location filename="../main.cpp" line="119"/> <source>The Description of Application.</source> <translation>设置应用的描述信息。</translation> </message> <message> - <location filename="../main.cpp" line="161"/> + <location filename="../main.cpp" line="126"/> <source>The Configuration file of Application.</source> <translation>设置应用的配置文件。</translation> </message> <message> - <location filename="../main.cpp" line="168"/> + <location filename="../main.cpp" line="133"/> <source>The root path of the program web service.</source> <translation>设置内置 WebServer 的根路径。</translation> </message> <message> - <location filename="../main.cpp" line="175"/> + <location filename="../main.cpp" line="140"/> <source>The port number of the program web service.</source> <translation>设置内置 WebServer 的监听端口号。</translation> </message> <message> - <location filename="../main.cpp" line="183"/> + <location filename="../main.cpp" line="147"/> <source>To use GPU instead of CPU to decoding. Default True.</source> <translation>启用GPU渲染,默认开启。</translation> </message> <message> - <location filename="../main.cpp" line="191"/> + <location filename="../main.cpp" line="155"/> <source>The ssl port number of the program web service.</source> <translation>设置内置 WebServer 的 SSL 协议的监听端口号。</translation> </message> @@ -204,5 +112,93 @@ <source>SparkWebAppRuntime</source> <translation>星火网页应用运行环境</translation> </message> + <message> + <location filename="../mainwindow.cpp" line="43"/> + <source>Full Screen</source> + <translation>全屏显示</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="44"/> + <source>Fix Size</source> + <translation>固定大小</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="45"/> + <source>Hide Buttons</source> + <translation>隐藏按钮</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="46"/> + <source>Clear Cache</source> + <translation>清理缓存</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="48"/> + <source>Show MainWindow</source> + <translation>显示主界面</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="55"/> + <source>Pause</source> + <translation>暂停</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="56"/> + <source>Resume</source> + <translation>继续</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="57"/> + <source>Cancel</source> + <translation>取消</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="339"/> + <source>Save As</source> + <translation>另存为</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="403"/> + <source>%1Start downloading %2</source> + <translation>%1开始下载 %2</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="414"/> + <source>%1Wait for previous download to complete!</source> + <translation>%1请等待上一个下载任务完成!</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="436"/> + <source>Open</source> + <translation>打开</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="440"/> + <source>download finished.</source> + <translation>下载完成。</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="440"/> + <source>Show in file manager?</source> + <translation>是否在文件管理器中显示?</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="478"/> + <source>%1Download canceled!</source> + <translation>%1下载取消!</translation> + </message> +</context> +<context> + <name>TitleBarMenu</name> + <message> + <location filename="../mainwindow.cpp" line="49"/> + <source>About</source> + <translation>关于</translation> + </message> + <message> + <location filename="../mainwindow.cpp" line="50"/> + <source>Exit</source> + <translation>退出</translation> + </message> </context> </TS> diff --git a/spark-webapp-runtime/webenginepage.cpp b/spark-webapp-runtime/webenginepage.cpp new file mode 100644 index 0000000..756758e --- /dev/null +++ b/spark-webapp-runtime/webenginepage.cpp @@ -0,0 +1,44 @@ +#include "webenginepage.h" + +#include <QDesktopServices> + +WebEnginePage::WebEnginePage(QObject *parent) + : QWebEnginePage(parent) +{ +} + +WebEnginePage::~WebEnginePage() +{ +} + +void WebEnginePage::setUrl(const QUrl &url) +{ + if (m_currentUrl == url) { + return; + } + + m_currentUrl = url; + QWebEnginePage::setUrl(url); +} + +QWebEnginePage *WebEnginePage::createWindow(QWebEnginePage::WebWindowType type) +{ + qDebug() << Q_FUNC_INFO << type; + + WebEnginePage *page = new WebEnginePage(parent()); + connect(page, &WebEnginePage::urlChanged, this, &WebEnginePage::slotUrlChanged); + return page; +} + +void WebEnginePage::slotUrlChanged(const QUrl &url) +{ + if (m_currentUrl == url) { + sender()->deleteLater(); + return; + } + + qDebug() << Q_FUNC_INFO << m_currentUrl << url; + + QDesktopServices::openUrl(url); + sender()->deleteLater(); +} diff --git a/spark-webapp-runtime/webenginepage.h b/spark-webapp-runtime/webenginepage.h new file mode 100644 index 0000000..6ebfb19 --- /dev/null +++ b/spark-webapp-runtime/webenginepage.h @@ -0,0 +1,26 @@ +#ifndef WEBENGINEPAGE_H +#define WEBENGINEPAGE_H + +#include <QWebEnginePage> + +class WebEnginePage : public QWebEnginePage +{ + Q_OBJECT + +public: + explicit WebEnginePage(QObject *parent = nullptr); + ~WebEnginePage() override; + + void setUrl(const QUrl &url); + +protected: + QWebEnginePage *createWindow(WebWindowType type) override; + +private slots: + void slotUrlChanged(const QUrl &url); + +private: + QUrl m_currentUrl; +}; + +#endif // WEBENGINEPAGE_H diff --git a/spark-webapp-runtime/webengineurlrequestinterceptor.cpp b/spark-webapp-runtime/webengineurlrequestinterceptor.cpp new file mode 100644 index 0000000..b87a05d --- /dev/null +++ b/spark-webapp-runtime/webengineurlrequestinterceptor.cpp @@ -0,0 +1,13 @@ +#include "webengineurlrequestinterceptor.h" + +#include <QLocale> + +WebEngineUrlRequestInterceptor::WebEngineUrlRequestInterceptor(QObject *parent) + : QWebEngineUrlRequestInterceptor(parent) +{ +} + +void WebEngineUrlRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo &info) +{ + info.setHttpHeader("Accept-Language", QLocale::system().name().toUtf8()); +} diff --git a/spark-webapp-runtime/webengineurlrequestinterceptor.h b/spark-webapp-runtime/webengineurlrequestinterceptor.h new file mode 100644 index 0000000..73d3a92 --- /dev/null +++ b/spark-webapp-runtime/webengineurlrequestinterceptor.h @@ -0,0 +1,15 @@ +#ifndef WEBENGINEURLREQUESTINTERCEPTOR_H +#define WEBENGINEURLREQUESTINTERCEPTOR_H + +#include <QWebEngineUrlRequestInterceptor> + +class WebEngineUrlRequestInterceptor : public QWebEngineUrlRequestInterceptor +{ + Q_OBJECT + +public: + explicit WebEngineUrlRequestInterceptor(QObject *parent = nullptr); + void interceptRequest(QWebEngineUrlRequestInfo &info) override; +}; + +#endif // WEBENGINEURLREQUESTINTERCEPTOR_H diff --git a/spark-webapp-runtime/webengineview.cpp b/spark-webapp-runtime/webengineview.cpp index acae105..dc25354 100644 --- a/spark-webapp-runtime/webengineview.cpp +++ b/spark-webapp-runtime/webengineview.cpp @@ -1,22 +1,20 @@ #include "webengineview.h" +//#include "webengineurlrequestinterceptor.h" + +#include <QWebEngineSettings> +#include <QWebEngineProfile> +#include <QLocale> WebEngineView::WebEngineView(QWidget *parent) : QWebEngineView(parent) +// , interceptor(new WebEngineUrlRequestInterceptor(this)) { -} - -QWebEngineView *WebEngineView::createWindow(QWebEnginePage::WebWindowType type) -{ - Q_UNUSED(type) - - QWebEngineView *view = new QWebEngineView; - connect(view, &QWebEngineView::urlChanged, this, &WebEngineView::on_urlChanged); - - return view; -} - -void WebEngineView::on_urlChanged(QUrl url) -{ - setUrl(url); - sender()->deleteLater(); + // page()->profile()->setHttpUserAgent(page()->profile()->httpUserAgent() + " MediaFeature/prefers-color-scheme:dark"); + + connect(this, &WebEngineView::urlChanged, this, [=]() { + // page()->setUrlRequestInterceptor(interceptor); + // page()->settings()->setAttribute(QWebEngineSettings::WebAttribute::LocalContentCanAccessRemoteUrls, true); + page()->profile()->setHttpAcceptLanguage(QLocale::system().name()); + // qInfo() << "User Agent:" << page()->profile()->httpUserAgent(); + }); } diff --git a/spark-webapp-runtime/webengineview.h b/spark-webapp-runtime/webengineview.h index 8102441..4ca48b2 100644 --- a/spark-webapp-runtime/webengineview.h +++ b/spark-webapp-runtime/webengineview.h @@ -3,6 +3,7 @@ #include <QWebEngineView> +// class WebEngineUrlRequestInterceptor; class WebEngineView : public QWebEngineView { Q_OBJECT @@ -10,12 +11,8 @@ class WebEngineView : public QWebEngineView public: explicit WebEngineView(QWidget *parent = nullptr); -protected: - QWebEngineView *createWindow(QWebEnginePage::WebWindowType type) override; - -private slots: - void on_urlChanged(QUrl url); - +private: + // WebEngineUrlRequestInterceptor *interceptor = nullptr; }; #endif // WEBENGINEVIEW_H diff --git a/spark-webapp-runtime/widget.cpp b/spark-webapp-runtime/widget.cpp index b6e1dd8..482d457 100644 --- a/spark-webapp-runtime/widget.cpp +++ b/spark-webapp-runtime/widget.cpp @@ -1,4 +1,10 @@ #include "widget.h" +#include "webengineview.h" +#include "webenginepage.h" + +#include <DApplication> + +DWIDGET_USE_NAMESPACE Widget::Widget(QString szUrl, QWidget *parent) : QWidget(parent) @@ -12,14 +18,16 @@ Widget::Widget(QString szUrl, QWidget *parent) m_webEngineView->setObjectName(QStringLiteral("webEngineView")); m_webEngineView->setEnabled(true); m_webEngineView->setAutoFillBackground(false); - m_webEngineView->setZoomFactor(1.0); - QWebEnginePage *page = new QWebEnginePage(m_webEngineView); + DApplication *dApp = qobject_cast<DApplication *>(qApp); + m_webEngineView->setZoomFactor(dApp->devicePixelRatio()); + + WebEnginePage *page = new WebEnginePage(m_webEngineView); m_webEngineView->setPage(page); - m_webEngineView->setUrl(QUrl(nullptr)); + page->setUrl(QUrl()); if (!m_szUrl.isEmpty()) { - m_webEngineView->setUrl(QUrl(m_szUrl)); + page->setUrl(QUrl(m_szUrl)); } QWidget *spinnerWidget = new QWidget(this); @@ -40,8 +48,6 @@ Widget::Widget(QString szUrl, QWidget *parent) Widget::~Widget() { - delete m_webEngineView; - delete m_spinner; } QWebEnginePage *Widget::getPage() diff --git a/spark-webapp-runtime/widget.h b/spark-webapp-runtime/widget.h index ec2c2f9..c128dbc 100644 --- a/spark-webapp-runtime/widget.h +++ b/spark-webapp-runtime/widget.h @@ -1,18 +1,15 @@ #ifndef WIDGET_H #define WIDGET_H -#include <QWidget> -#include <QWebEngineProfile> -#include <QWebEngineView> -#include <QLayout> -#include <QStackedLayout> - #include <DSpinner> -#include "webengineview.h" +#include <QWidget> +#include <QWebEnginePage> +#include <QStackedLayout> DWIDGET_USE_NAMESPACE +class WebEngineView; class Widget : public QWidget { Q_OBJECT @@ -26,16 +23,16 @@ public: void goForward(); void refresh(); -private: - WebEngineView *m_webEngineView; - DSpinner *m_spinner; - QStackedLayout *mainLayout; - - QString m_szUrl; - private slots: void on_loadStarted(); void on_loadFinished(); + +private: + WebEngineView *m_webEngineView = nullptr; + DSpinner *m_spinner = nullptr; + QStackedLayout *mainLayout = nullptr; + + QString m_szUrl; }; #endif // WIDGET_H