feat: adapt to Qt6

adapt to Qt6

Log: adapt to Qt6; bump version to 1.7.1
This commit is contained in:
zty199
2024-06-26 16:15:21 +08:00
parent a95e7e1beb
commit 4dd7f7fcec
41 changed files with 357 additions and 359 deletions
+36
View File
@@ -0,0 +1,36 @@
find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets Concurrent WebEngineWidgets)
find_package(Dtk6 REQUIRED COMPONENTS Core Gui Widget)
include(src.cmake)
add_executable(${PROJECT_NAME} ${HEADERS} ${SOURCES} ${QRC_FILE})
target_include_directories(${PROJECT_NAME} PUBLIC
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Concurrent
Qt6::WebEngineWidgets
Dtk6::Core
Dtk6::Gui
Dtk6::Widget
)
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt6::Core
Qt6::Gui
Qt6::Widgets
Qt6::Concurrent
Qt6::WebEngineWidgets
Dtk6::Core
Dtk6::Gui
Dtk6::Widget
)
install(TARGETS ${PROJECT_NAME} DESTINATION /opt/durapps/${PROJECT_NAME}/bin)
file(CREATE_LINK /opt/durapps/${PROJECT_NAME}/bin/${PROJECT_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.link SYMBOLIC)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.link DESTINATION ${CMAKE_INSTALL_BINDIR} RENAME ${PROJECT_NAME})
file(CREATE_LINK /opt/durapps/${PROJECT_NAME}/share/${PROJECT_NAME} ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_datadir.link SYMBOLIC)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_datadir.link DESTINATION ${CMAKE_INSTALL_DATADIR} RENAME ${PROJECT_NAME})
+84
View File
@@ -0,0 +1,84 @@
#include "application.h"
#include "globaldefine.h"
#include <DPlatformWindowHandle>
#include <DAboutDialog>
const QString websiteLinkTemplate = "<a href='%1' style='text-decoration: none; color: rgba(0,129,255,0.9);'>%2</a>";
Application::Application(int &argc, char **argv)
: DApplication(argc, argv)
{
saveLaunchParams(argc, argv);
loadTranslator();
if (!DPlatformWindowHandle::pluginVersion().isEmpty()) {
setAttribute(Qt::AA_DontCreateNativeWidgetSiblings, true);
}
setOrganizationName(ORGANIZATION_NAME); // 添加组织名称,和商店主体的文件夹同在 ~/.local/share/spark-union 文件夹下
setApplicationName(PROJECT_NAME); // 这里不要翻译,否则 ~/.local/share 中文件夹名也会被翻译
setApplicationVersion(VERSION);
setApplicationDisplayName(DEFAULT_TITLE);
setWindowIcon(QIcon(":/images/spark-webapp-runtime.svg"));
setProductIcon(QIcon(":/images/spark-webapp-runtime.svg"));
setProductName(websiteLinkTemplate.arg("https://gitee.com/deepin-community-store/spark-web-app-runtime", DEFAULT_TITLE));
setApplicationDescription(QObject::tr("Presented By Spark developers # HadesStudio"));
setApplicationLicense(websiteLinkTemplate.arg("https://gitee.com/spark-store-project/spark-web-app-runtime/blob/master/LICENSE", "GPLv3"));
}
void Application::triggerAboutAction()
{
handleAboutAction();
}
QStringList Application::launchParams() const
{
return m_argv;
}
void Application::setMainWindow(MainWindow *window)
{
m_mainWindow = window;
}
MainWindow *Application::mainWindow()
{
return m_mainWindow;
}
void Application::handleAboutAction()
{
DApplication::handleAboutAction();
DAboutDialog *dialog = aboutDialog();
if (dialog) {
// CompanyLogo
dialog->setCompanyLogo(QPixmap(":/images/Logo-Spark.png"));
// WebsiteName
dialog->setWebsiteName("Spark Project");
// WebsiteLink
dialog->setWebsiteLink("https://gitee.com/deepin-community-store/");
}
}
void Application::saveLaunchParams(int &argc, char **argv)
{
m_argc = argc;
m_argv.clear();
for (int i = 0; i < m_argc; i++) {
m_argv.append(argv[i]);
}
qDebug() << Q_FUNC_INFO << m_argc << m_argv;
}
void Application::slotMainWindowClose()
{
if (aboutDialog()) {
aboutDialog()->close();
}
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef APPLICATION_H
#define APPLICATION_H
#include "mainwindow.h"
#include <DApplication>
DWIDGET_USE_NAMESPACE
class Application : public DApplication
{
Q_OBJECT
public:
Application(int &argc, char **argv);
void triggerAboutAction();
QStringList launchParams() const;
void setMainWindow(MainWindow *window);
MainWindow *mainWindow();
protected:
void handleAboutAction() override;
private:
void saveLaunchParams(int &argc, char **argv);
signals:
void sigQuit();
public slots:
void slotMainWindowClose();
private:
MainWindow *m_mainWindow = nullptr;
int m_argc;
QStringList m_argv;
};
#endif // APPLICATION_H
+20
View File
@@ -0,0 +1,20 @@
#ifndef GLOBALDEFINE_H
#define GLOBALDEFINE_H
#include <QObject>
#define DEFAULT_TITLE QObject::tr("SparkWebAppRuntime")
#define ORGANIZATION_NAME QString("spark-union")
#define DEFAULT_URL QString("qrc:/help/help.html")
#define DEFAULT_WIDTH (1024)
#define DEFAULT_HEIGHT (768)
#define DEFAULT_ICON QString()
#define DEFAULT_DESC QString()
#define DEFAULT_CFG QString()
#define DEFAULT_ROOT QString()
#define DEFAULT_PORT 0
#define DEFAULT_GPU 1
#endif // GLOBALDEFINE_H
+94
View File
@@ -0,0 +1,94 @@
#include "httpd.h"
#include "httplib.h"
#include <QtConcurrent>
#include <QDebug>
using namespace httplib;
#if SSL_SERVER
HttpD::HttpD(QString szRootPath, quint16 u16Port, quint16 u16sslPort, QObject *parent)
#else
HttpD::HttpD(QString szRootPath, quint16 u16Port, QObject *parent)
#endif
: QObject(parent)
, m_szRootPath(szRootPath)
#if SSL_SERVER
, m_httpsServer(new SSLServer(g_szClientCert.toStdString().c_str(), g_szClientCertKey.toStdString().c_str()))
, m_u16HttpsServerBindPort(u16sslPort)
#endif
, m_httpServer(new Server())
, m_u16HttpServerBindPort(u16Port)
{
#if SSL_SERVER
m_httpsServer->set_idle_interval(0, 500 * 1000);
m_httpsServer->set_read_timeout(1);
m_httpsServer->set_write_timeout(1);
#endif
m_httpServer->set_idle_interval(0, 500 * 1000);
m_httpServer->set_read_timeout(1);
m_httpServer->set_write_timeout(1);
#if SSL_SERVER
auto ret = m_httpsServer->set_mount_point("/", m_szRootPath.toStdString().c_str());
if (!ret)
{
qInfo("HTTP Server Mount root directory Fail!");
}
m_httpsServer->set_error_handler([](const Request &, Response & res)
{
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
#endif
auto ret = m_httpServer->set_mount_point("/", m_szRootPath.toStdString().c_str());
if (!ret)
{
qInfo("HTTP Server Mount root directory Fail!");
}
m_httpServer->set_error_handler([](const Request &, Response & res)
{
auto fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
char buf[BUFSIZ];
snprintf(buf, sizeof(buf), fmt, res.status);
res.set_content(buf, "text/html");
});
}
void HttpD::start()
{
#if SSL_SERVER
QtConcurrent::run([&]
{
if (m_u16HttpsServerBindPort > 0)
{
CommonTools::outputLog(QString("HTTPS Server Startup with [%1] listen to %2 ...")
.arg(m_szRootPath)
.arg(m_u16HttpServerBindPort);
m_httpsServer->listen("0.0.0.0", m_u16HttpsServerBindPort);
}
});
#endif
QtConcurrent::run([&]
{
if (m_u16HttpServerBindPort > 0)
{
qInfo() << QString("HTTP Server Startup with [%1] listen to %2 ...")
.arg(m_szRootPath)
.arg(m_u16HttpServerBindPort);
m_httpServer->listen("0.0.0.0", m_u16HttpServerBindPort);
}
});
}
void HttpD::stop()
{
#if SSL_SERVER
m_httpsServer->stop();
#endif
m_httpServer->stop();
}
+45
View File
@@ -0,0 +1,45 @@
#ifndef HTTPSD_H
#define HTTPSD_H
#include <QObject>
#ifdef CPPHTTPLIB_OPENSSL_SUPPORT
#define SSL_SERVER 1
#else
#define SSL_SERVER 0
#endif
namespace httplib
{
class Server;
#if SSL_SERVER
class SSLServer;
#endif
}
class HttpD : public QObject
{
Q_OBJECT
public:
#if SSL_SERVER
explicit HttpD(QString szRootPath, quint16 u16Port, quint16 u16sslPort, QObject *parent = nullptr);
#else
explicit HttpD(QString szRootPath, quint16 u16Port, QObject *parent = nullptr);
#endif
public slots:
void start();
void stop();
private:
QString m_szRootPath;
#if SSL_SERVER
httplib::SSLServer *m_httpsServer;
quint16 m_u16HttpsServerBindPort;
#endif
httplib::Server *m_httpServer;
quint16 m_u16HttpServerBindPort;
};
#endif // HTTPSD_H
+6590
View File
File diff suppressed because it is too large Load Diff
+345
View File
@@ -0,0 +1,345 @@
/**
* Spark WebApp Runtime
* 星火网页应用运行环境
*/
#include "application.h"
#include "mainwindow.h"
#include "webengineview.h"
#include "httpd.h"
#include <DSysInfo>
#include <QCommandLineParser>
#include <QCommandLineOption>
#include <QFileInfo>
#include <QSettings>
#include <unistd.h>
int main(int argc, char *argv[])
{
// 龙芯机器配置,使得 DApplication 能正确加载 QTWEBENGINE
qputenv("DTK_FORCE_RASTER_WIDGETS", "FALSE");
// qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-features=UseModernMediaControls");
// qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--disable-web-security");
#if defined __sw_64__ || __loongarch__
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", "--no-sandbox");
#endif
// 强制使用 DTK 平台插件
int fakeArgc = argc + 2;
QVector<char *> fakeArgv(fakeArgc);
fakeArgv[0] = argv[0];
fakeArgv[1] = const_cast<char *>("-platformtheme");
fakeArgv[2] = const_cast<char *>("deepin");
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));
parser.addHelpOption();
parser.addVersionOption();
QCommandLineOption optParser(QStringList() << "p"
<< "parser",
QObject::tr("Enable CommandLineParser. Default is false."));
parser.addOption(optParser);
QCommandLineOption optTitle(QStringList() << "t"
<< "title",
QObject::tr("The Title of Application. Default is %1.").arg(DEFAULT_TITLE),
"title",
DEFAULT_TITLE);
parser.addOption(optTitle);
QCommandLineOption optUrl(QStringList() << "u"
<< "url",
QObject::tr("The target URL. Default is Blank."),
"url",
DEFAULT_URL);
parser.addOption(optUrl);
QCommandLineOption optWidth(QStringList() << "w"
<< "width",
QObject::tr("The Width of Application. Default is %1.").arg(DEFAULT_WIDTH),
"width",
QString::number(DEFAULT_WIDTH));
parser.addOption(optWidth);
QCommandLineOption optHeight(QStringList() << "H"
<< "height",
QObject::tr("The Height of Application. Default is %1.").arg(DEFAULT_HEIGHT),
"height",
QString::number(DEFAULT_HEIGHT));
parser.addOption(optHeight);
QCommandLineOption optTray(QStringList() << "T"
<< "tray",
QObject::tr("Enable Tray Icon. Default is false."));
parser.addOption(optTray);
QCommandLineOption optFullScreen("full-screen",
QObject::tr("Run in Fullscreen Mode. Default is false."));
parser.addOption(optFullScreen);
QCommandLineOption optFixSize("fix-size",
QObject::tr("Fix Window Size. Default is false."));
parser.addOption(optFixSize);
QCommandLineOption optHideButtons("hide-buttons",
QObject::tr("Hide Control Buttons. Default is false."));
parser.addOption(optHideButtons);
QCommandLineOption optIcon(QStringList() << "i"
<< "ico",
QObject::tr("The ICON of Application."),
"ico",
DEFAULT_ICON);
parser.addOption(optIcon);
QCommandLineOption optDesc(QStringList() << "d"
<< "desc",
QObject::tr("The Description of Application."),
"desc",
DEFAULT_DESC);
parser.addOption(optDesc);
QCommandLineOption optCfgFile(QStringList() << "c"
<< "cfg",
QObject::tr("The Configuration file of Application."),
"cfg",
DEFAULT_CFG);
parser.addOption(optCfgFile);
QCommandLineOption optRootPath(QStringList() << "r"
<< "root",
QObject::tr("The root path of the program web service."),
"root",
DEFAULT_ROOT);
parser.addOption(optRootPath);
QCommandLineOption optPort(QStringList() << "P"
<< "port",
QObject::tr("The port number of the program web service."),
"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));
parser.addOption(useGPU);
#if SSL_SERVER
QCommandLineOption optSSLPort(QStringList() << "s"
<< "sslport",
QObject::tr("The ssl port number of the program web service."),
"sslport",
DEFAULT_PORT);
parser.addOption(optSSLPort);
#endif
parser.process(a);
QString szTitle = DEFAULT_TITLE;
QString szUrl = DEFAULT_URL;
int width = DEFAULT_WIDTH;
int height = DEFAULT_HEIGHT;
bool tray = false;
bool fullScreen = false;
bool fixSize = false;
bool hideButtons = false;
QString szIcon = DEFAULT_ICON;
QString szDesc = DEFAULT_DESC;
QString szRootPath = DEFAULT_ROOT;
quint16 u16Port = DEFAULT_PORT;
bool toUseGPU = DEFAULT_GPU;
#if SSL_SERVER
quint16 u16sslPort = 0;
#endif
QString szDefaultDesc = QObject::tr("Presented By Spark developers # HadesStudio");
// 解析可能存在的配置文件
QString szCfgFile = DEFAULT_CFG;
if (parser.isSet(optCfgFile)) {
szCfgFile = parser.value(optCfgFile);
if (!szCfgFile.isEmpty()) {
if (QFileInfo(szCfgFile).exists()) {
QSettings settings(szCfgFile, QSettings::IniFormat);
szTitle = settings.value("SparkWebAppRuntime/Title", DEFAULT_TITLE).toString();
szUrl = settings.value("SparkWebAppRuntime/URL", DEFAULT_TITLE).toString();
width = settings.value("SparkWebAppRuntime/Width", DEFAULT_WIDTH).toInt();
height = settings.value("SparkWebAppRuntime/Height", DEFAULT_HEIGHT).toInt();
tray = settings.value("SparkWebAppRunTime/Tray", false).toBool();
fullScreen = settings.value("SparkWebAppRunTime/FullScreen", false).toBool();
fixSize = settings.value("SparkWebAppRunTime/FixSize", false).toBool();
hideButtons = settings.value("SparkWebAppRunTime/HideButtons", false).toBool();
szIcon = settings.value("SparkWebAppRuntime/Ico", DEFAULT_ICON).toString();
szDesc = QString("%1<br/><br/>%2")
.arg(settings.value("SparkWebAppRuntime/Desc", QString()).toString())
.arg(szDefaultDesc);
szRootPath = settings.value("SparkWebAppRuntime/RootPath", QString()).toString();
u16Port = static_cast<quint16>(settings.value("SparkWebAppRuntime/Port", 0).toUInt());
#if SSL_SERVER
u16sslPort = settings.value("SparkWebAppRuntime/SSLPort", 0).toUInt();
#endif
}
}
}
// 命令行级别优先, 覆盖配置文件的设置
if (parser.isSet(optTitle)) {
szTitle = parser.value(optTitle);
}
if (parser.isSet(optUrl)) {
szUrl = parser.value(optUrl);
}
if (parser.isSet(optWidth)) {
width = parser.value(optWidth).toInt();
}
if (parser.isSet(optHeight)) {
height = parser.value(optHeight).toInt();
}
if (parser.isSet(optTray)) {
tray = true;
}
if (parser.isSet(optFullScreen)) {
fullScreen = true;
}
if (parser.isSet(optFixSize)) {
fixSize = true;
}
if (parser.isSet(optHideButtons)) {
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);
}
if (parser.isSet(optRootPath)) {
szRootPath = parser.value(optRootPath);
}
if (parser.isSet(optPort)) {
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
qDebug() << "Setting GPU to True.";
}
// 初始化 QtWebEngine 深色模式环境变量
WebEngineView::handleChromiumFlags();
#if SSL_SERVER
if (parser.isSet(optSSLPort)) {
u16sslPort = parser.value(optSSLPort).toUInt();
}
#endif
// 没设置 -p 并且参数个数 > 1 并且第一个参数不是 - 开始的
if (!parser.isSet(optParser) && argc > 1 && !QString(argv[1]).startsWith("-")) {
// 按照固定顺序级别最优先
if (argc > 1) {
szTitle = argv[1];
}
if (argc > 2) {
szUrl = argv[2];
}
if (argc > 3) {
width = QString(argv[3]).toInt();
}
if (argc > 4) {
height = QString(argv[4]).toInt();
}
if (argc > 5) {
tray = true;
}
if (argc > 6) {
fullScreen = true;
}
if (argc > 7) {
fixSize = true;
}
if (argc > 8) {
hideButtons = true;
}
if (argc > 9) {
szIcon = QString(argv[9]);
}
if (argc > 10) {
szDesc = QString("%1<br/><br/>%2").arg(QString(argv[10])).arg(szDefaultDesc);
}
if (argc > 11) {
szRootPath = QString(argv[11]);
}
if (argc > 12) {
u16Port = static_cast<quint16>(QString(argv[12]).toUInt());
}
#if SSL_SERVER
if (argc > 13) {
u16sslPort = QString(argv[13]).toUInt();
}
#endif
}
if (fixSize) {
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); // 启用托盘时,退出程序后服务不终止
#if SSL_SERVER
if (!szRootPath.isEmpty() && u16Port > 0 && u16sslPort > 0) {
HttpD httpd(szRootPath, u16Port, u16sslPort);
httpd.start();
}
#else
if (!szRootPath.isEmpty() && u16Port > 0) {
static HttpD httpd(szRootPath, u16Port);
QObject::connect(&a, &Application::sigQuit, &httpd, &HttpD::stop);
httpd.start();
}
#endif
MainWindow w(szTitle, szUrl, width, height, tray, fullScreen, fixSize, hideButtons);
a.setMainWindow(&w);
QObject::connect(&a, &Application::newInstanceStarted, &w, &MainWindow::slotNewInstanceStarted);
QObject::connect(&w, &MainWindow::sigClose, &a, &Application::slotMainWindowClose);
if (!szIcon.isEmpty()) {
w.setIcon(szIcon);
}
if (!szDesc.isEmpty()) {
w.setDescription(szDesc);
}
w.show();
return a.exec();
}
+548
View File
@@ -0,0 +1,548 @@
#include "mainwindow.h"
#include "application.h"
#include "webengineview.h"
#include "webenginepage.h"
#include <DPlatformWindowHandle>
#include <DLog>
#include <DWidgetUtil>
#include <DTitlebar>
#include <DMessageManager>
#include <DDesktopServices>
#include <QKeyEvent>
#include <QWebEngineProfile>
#include <QFileInfo>
#include <QFileDialog>
#include <QDir>
#include <QStandardPaths>
#include <unistd.h>
DCORE_USE_NAMESPACE
MainWindow::MainWindow(QString szTitle,
QString szUrl,
int nWidth,
int nHeight,
bool nTray,
bool nFullScreen,
bool nFixSize,
bool nHideButtons,
QWidget *parent)
: DMainWindow(parent)
, m_title(szTitle)
, m_url(szUrl)
, m_width(nWidth)
, m_height(nHeight)
, m_isTrayEnabled(nTray)
, m_isFullScreen(nFullScreen)
, m_isFixedSize(nFixSize)
, m_isHideButton(nHideButtons)
, m_widget(new Widget(m_url, this))
, 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(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(QObject::tr("Show MainWindow"), this))
, t_about(new QAction(qApp->translate("TitleBarMenu", QString("About").toUtf8().data()), this))
, t_exit(new QAction(qApp->translate("TitleBarMenu", QString("Exit").toUtf8().data()), this))
, downloadMessage(new DFloatingMessage(DFloatingMessage::ResidentType, this))
, downloadProgressWidget(new QWidget(downloadMessage))
, progressBarLayout(new QHBoxLayout(downloadProgressWidget))
, downloadProgressBar(new DProgressBar(downloadProgressWidget))
, btnPause(new DPushButton(QObject::tr("Pause"), downloadProgressWidget))
, btnResume(new DPushButton(QObject::tr("Resume"), downloadProgressWidget))
, btnCancel(new DPushButton(QObject::tr("Cancel"), downloadProgressWidget))
, isCanceled(false)
{
initTmpDir();
initLog();
initUI();
initTrayIcon();
initConnections();
}
MainWindow::~MainWindow()
{
}
bool MainWindow::event(QEvent *event)
{
/**
* @bug QWebEngineView in Qt6 will recreate window handle
* @ref https://github.com/linuxdeepin/deepin-deepinid-client/commit/2a305926a9047c699cf4d12e3e64aae17e8c367b
*/
if (event->type() == QEvent::WinIdChange) {
DPlatformWindowHandle handle(this);
}
return DMainWindow::event(event);
}
void MainWindow::setIcon(QString szIconPath)
{
if (!QFile::exists(szIconPath)) {
return;
}
titlebar()->setIcon(QIcon(szIconPath));
setWindowIcon(QIcon(szIconPath));
m_tray->setIcon(QIcon(szIconPath));
qApp->setWindowIcon(QIcon::fromTheme(szIconPath));
qApp->setProductIcon(QIcon::fromTheme(szIconPath));
}
void MainWindow::setDescription(const QString &desc)
{
qApp->setApplicationDescription(desc);
}
QString MainWindow::title() const
{
return m_title;
}
QString MainWindow::tmpDir() const
{
QString orgName = qobject_cast<DApplication *>(qApp)->organizationName();
QString appName = qobject_cast<DApplication *>(qApp)->applicationName();
return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + orgName + "/" + appName + "/" + m_title + "/" + QString::number(getuid());
}
void MainWindow::keyPressEvent(QKeyEvent *event)
{
if (!m_fixSize->isChecked()) // 固定窗口大小时禁止全屏
{
if (event->key() == Qt::Key_F11) // 绑定键盘快捷键 F11
{
m_fullScreen->trigger();
m_menu->update();
}
}
DMainWindow::keyPressEvent(event);
}
void MainWindow::resizeEvent(QResizeEvent *event)
{
if (this->isFullScreen()) {
m_fullScreen->setChecked(true);
} else {
m_fullScreen->setChecked(false);
if (!m_isFixedSize) {
m_fixSize->setEnabled(true); // 命令行参数没有固定窗口大小时,窗口模式下允许手动选择固定窗口大小
}
}
DMainWindow::resizeEvent(event);
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (!m_isTrayEnabled) {
emit sigClose(); // 不启用托盘时,关闭主窗口则关闭关于窗口
}
DMainWindow::closeEvent(event);
}
void MainWindow::initLog()
{
if (!QDir(tmpDir()).exists()) {
return;
}
DLogManager::setlogFilePath(tmpDir() + "/" + "log");
DLogManager::registerFileAppender();
DLogManager::registerConsoleAppender();
DLogManager::registerJournalAppender();
}
void MainWindow::initTmpDir()
{
QDir dir(tmpDir());
dir.removeRecursively();
dir.mkpath(dir.path());
if (!dir.exists()) {
qCritical() << Q_FUNC_INFO << dir.path() << "not exists";
return;
}
qDebug() << Q_FUNC_INFO << dir.path() << "created";
}
void MainWindow::initUI()
{
// 初始化 MainWindow
setCentralWidget(m_widget);
centralWidget()->layout()->setContentsMargins(0, 0, 0, 0);
resize(m_width, m_height);
moveToCenter(this);
setWindowIcon(QIcon(":/images/spark-webapp-runtime.svg"));
initTitleBar();
initDownloadProgressBar();
fixSize();
fullScreen();
}
void MainWindow::initTitleBar()
{
titlebar()->setTitle(m_title);
titlebar()->setIcon(QIcon(":/images/spark-webapp-runtime.svg"));
btnBack->setIcon(QIcon(":/images/go-previous-24.svg"));
btnBack->setIconSize(QSize(36, 36));
btnForward->setIcon(QIcon(":/images/go-next-24.svg"));
btnForward->setIconSize(QSize(36, 36));
btnRefresh->setIcon(QIcon(":/images/view-refresh.svg"));
btnRefresh->setIconSize(QSize(36, 36));
titlebar()->addWidget(btnBack, Qt::AlignLeft);
titlebar()->addWidget(btnForward, Qt::AlignLeft);
titlebar()->addWidget(btnRefresh, Qt::AlignLeft);
m_fullScreen->setCheckable(true);
m_fullScreen->setChecked(m_isFullScreen);
m_fullScreen->setDisabled(m_isFixedSize); // NOTE: 固定窗口大小时禁用全屏模式,避免标题栏按钮显示问题
m_fixSize->setCheckable(true);
m_fixSize->setChecked(m_isFixedSize);
m_fixSize->setDisabled(m_isFixedSize);
m_hideButtons->setCheckable(true);
m_hideButtons->setChecked(m_isHideButton);
m_hideButtons->setDisabled(m_isHideButton);
// 命令行设置参数后 GUI 中隐藏对应选项
if (!m_isFixedSize) {
m_menu->addAction(m_fullScreen);
m_menu->addAction(m_fixSize);
}
if (!m_isHideButton) {
m_menu->addAction(m_hideButtons);
}
if (m_menu->actions().size() > 0) {
m_menu->addSeparator();
m_menu->addAction(m_clearCache);
}
titlebar()->setMenu(m_menu);
titlebar()->setAutoHideOnFullscreen(true);
}
void MainWindow::initDownloadProgressBar()
{
// 初始化 DownloadProgressBar
downloadProgressBar->setFixedSize(250, 8);
btnPause->setFixedSize(80, 32);
btnResume->setFixedSize(80, 32);
btnCancel->setFixedSize(80, 32);
progressBarLayout->setContentsMargins(0, 0, 0, 0);
progressBarLayout->setSpacing(0);
progressBarLayout->setAlignment(Qt::AlignCenter);
progressBarLayout->addWidget(downloadProgressBar);
progressBarLayout->addSpacing(10);
progressBarLayout->addWidget(btnPause);
progressBarLayout->addWidget(btnResume);
progressBarLayout->addWidget(btnCancel);
downloadMessage->setIcon(QIcon::fromTheme("deepin-download"));
downloadMessage->setWidget(downloadProgressWidget);
downloadMessage->hide();
}
void MainWindow::initTrayIcon()
{
// 初始化 TrayIcon
t_menu->addAction(t_show);
t_menu->addAction(t_about);
t_menu->addAction(t_exit);
m_tray->setContextMenu(t_menu);
m_tray->setToolTip(m_title);
m_tray->setIcon(QIcon(":/images/spark-webapp-runtime.svg"));
if (m_isTrayEnabled) {
m_tray->show(); // 启用托盘时显示
}
}
void MainWindow::initConnections()
{
connect(btnBack, &DToolButton::clicked, this, [&]() {
m_widget->goBack();
});
connect(btnForward, &DToolButton::clicked, this, [&]() {
m_widget->goForward();
});
connect(btnRefresh, &DToolButton::clicked, this, [&]() {
m_widget->refresh();
});
connect(m_fullScreen, &QAction::triggered, this, [=]() {
fullScreen();
});
connect(m_fixSize, &QAction::triggered, this, [=]() {
fixSize();
});
connect(m_hideButtons, &QAction::triggered, this, [=]() {
hideButtons();
});
connect(m_clearCache, &QAction::triggered, this, [=]() {
clearCache();
});
connect(t_show, &QAction::triggered, this, [=]() {
this->activateWindow();
fixSize();
});
connect(t_about, &QAction::triggered, this, [=]() {
qobject_cast<Application *>(qApp)->triggerAboutAction();
});
connect(t_exit, &QAction::triggered, this, [=]() {
exit(0);
});
connect(m_tray, &QSystemTrayIcon::activated, this, &MainWindow::on_trayIconActivated);
connect(m_widget, &Widget::sigLoadErrorOccurred, this, &MainWindow::slotLoadErrorOccurred);
connect(m_widget->getPage()->profile(), &QWebEngineProfile::downloadRequested, this, &MainWindow::on_downloadStart);
connect(m_widget->getPage(), &QWebEnginePage::windowCloseRequested, this, [=]() {
this->close();
});
}
void MainWindow::fullScreen()
{
if (m_fullScreen->isChecked()) {
m_fixSize->setChecked(false);
m_fixSize->setDisabled(true);
m_menu->update();
showFullScreen();
// DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-information"), 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"), QString(QObject::tr("%1Windowed Mode")).arg(" "));
}
}
void MainWindow::fixSize()
{
if (m_fixSize->isChecked()) {
m_fullScreen->setChecked(false);
m_fullScreen->setDisabled(true);
m_menu->update();
setFixedSize(this->size());
// BUG: 启用托盘图标后,若手动选择固定窗口大小,并且关闭窗口,再次打开时会丢失最大化按钮,且无法恢复。
} else {
m_fullScreen->setDisabled(false);
m_menu->update();
setMinimumSize(m_width, m_height);
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
}
fullScreen();
}
void MainWindow::hideButtons()
{
if (m_hideButtons->isChecked()) {
btnBack->hide();
btnForward->hide();
btnRefresh->hide();
} else {
btnBack->show();
btnForward->show();
btnRefresh->show();
}
}
void MainWindow::clearCache()
{
// 清除缓存文件夹并刷新页面
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation));
if (dir.exists()) {
dir.removeRecursively();
}
emit btnRefresh->clicked();
}
QString MainWindow::saveAs(QString fileName)
{
QString saveFile = QFileDialog::getSaveFileName(this,
QObject::tr("Save As"),
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) + "/" + fileName);
if (!saveFile.isEmpty()) {
// 判断上层目录是否可写入
if (QFileInfo(QFileInfo(saveFile).absoluteDir().canonicalPath()).isWritable()) {
return saveFile;
} else {
return saveAs(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;
default:
break;
}
}
void MainWindow::on_downloadStart(QWebEngineDownloadRequest *request)
{
// 尝试加锁互斥量,禁止同时下载多个文件
if (mutex.tryLock()) {
QString fileName = request->suggestedFileName();
QString filePath = saveAs(fileName);
if (filePath.isEmpty()) {
mutex.unlock();
return;
}
QFileInfo fileInfo(filePath);
fileName = fileInfo.fileName();
QString dirPath = fileInfo.absoluteDir().canonicalPath();
filePath = QDir(dirPath).absoluteFilePath(fileName);
request->setDownloadDirectory(dirPath);
request->setDownloadFileName(fileName);
connect(request, &QWebEngineDownloadRequest::receivedBytesChanged, this, &MainWindow::on_receivedBytesChanged);
connect(request, &QWebEngineDownloadRequest::isFinishedChanged, this, [=]() {
on_downloadFinish(filePath);
});
connect(btnPause, &DPushButton::clicked, this, [=]() {
on_downloadPause(request);
});
connect(btnResume, &DPushButton::clicked, this, [=]() {
on_downloadResume(request);
});
connect(btnCancel, &DPushButton::clicked, this, [=]() {
on_downloadCancel(request);
});
DFloatingMessage *message = new DFloatingMessage(DFloatingMessage::TransientType);
message->setIcon(QIcon::fromTheme("dialog-information"));
message->setMessage(QObject::tr("%1Start downloading %2").arg(" ", fileName));
DMessageManager::instance()->sendMessage(this, message);
request->accept();
// 重置 DownloadProgressBar 状态
isCanceled = false;
btnResume->hide();
btnPause->show();
this->downloadMessage->show(); // 上一次下载完成后隐藏了进度条,这里要重新显示
} else {
DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-cancel"), QString(QObject::tr("%1Wait for previous download to complete!")).arg(" "));
}
}
void MainWindow::on_receivedBytesChanged()
{
QWebEngineDownloadRequest *request = qobject_cast<QWebEngineDownloadRequest *>(sender());
int value = int(double(request->receivedBytes()) / double(request->totalBytes()) * 100.0);
downloadProgressBar->setValue(qBound(downloadProgressBar->value(), value, 100));
downloadMessage->setMessage(" " + QString::number(value) + "%");
DMessageManager::instance()->sendMessage(this, downloadMessage);
}
void MainWindow::on_downloadFinish(QString filePath)
{
QWebEngineDownloadRequest *request = qobject_cast<QWebEngineDownloadRequest *>(sender());
if (!request->isFinished()) {
return;
}
mutex.unlock(); // 解锁互斥量,允许下载新文件
downloadMessage->hide();
// 下载完成显示提示信息
if (!isCanceled) {
DPushButton *button = new DPushButton(QObject::tr("Open"));
DFloatingMessage *message = new DFloatingMessage(DFloatingMessage::ResidentType);
message->setIcon(QIcon::fromTheme("dialog-ok"));
message->setMessage(QString(" %1 %2 %3").arg(QFileInfo(filePath).fileName(), QObject::tr("download finished."), 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();
});
}
}
void MainWindow::on_downloadPause(QWebEngineDownloadRequest *request)
{
request->pause();
downloadMessage->setIcon(QIcon::fromTheme("package-download-failed"));
btnResume->show();
btnPause->hide();
}
void MainWindow::on_downloadResume(QWebEngineDownloadRequest *request)
{
request->resume();
downloadMessage->setIcon(QIcon::fromTheme("deepin-download"));
btnResume->hide();
btnPause->show();
}
void MainWindow::on_downloadCancel(QWebEngineDownloadRequest *request)
{
isCanceled = true; // 取消下载
request->cancel();
mutex.unlock();
downloadMessage->hide();
DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-error"), QString(QObject::tr("%1Download canceled!")).arg(" "));
}
void MainWindow::slotLoadErrorOccurred()
{
DMessageManager::instance()->sendMessage(this, QIcon::fromTheme("dialog-warning"), QString(QObject::tr("%1Load error occurred!")).arg(" "));
}
+118
View File
@@ -0,0 +1,118 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <DMainWindow>
#include <DAboutDialog>
#include <DToolButton>
#include <DProgressBar>
#include <DPushButton>
#include <DFloatingMessage>
#include <QSystemTrayIcon>
#include <QProcess>
#include <QMutex>
#include "widget.h"
#include "globaldefine.h"
DWIDGET_USE_NAMESPACE
class MainWindow : public DMainWindow
{
Q_OBJECT
public:
MainWindow(QString szTitle = DEFAULT_TITLE,
QString szUrl = DEFAULT_URL,
int nWidth = DEFAULT_WIDTH,
int nHeight = DEFAULT_HEIGHT,
bool nTray = false,
bool nFullScreen = false,
bool nFixSize = false,
bool nHideButtons = false,
QWidget *parent = nullptr);
~MainWindow();
bool event(QEvent *event) override;
void setIcon(QString szIconPath);
void setDescription(const QString &desc);
QString title() const;
QString tmpDir() const;
protected:
void keyPressEvent(QKeyEvent *event);
void resizeEvent(QResizeEvent *event);
void closeEvent(QCloseEvent *event);
private:
void initLog();
void initTmpDir();
void initUI();
void initTitleBar();
void initDownloadProgressBar();
void initTrayIcon();
void initConnections();
void fullScreen();
void fixSize();
void hideButtons();
void clearCache();
QString saveAs(QString fileName);
signals:
void sigClose();
public slots:
void slotNewInstanceStarted();
private slots:
void on_trayIconActivated(QSystemTrayIcon::ActivationReason reason);
void on_downloadStart(QWebEngineDownloadRequest *request);
void on_receivedBytesChanged();
void on_downloadFinish(QString filePath);
void on_downloadPause(QWebEngineDownloadRequest *request);
void on_downloadResume(QWebEngineDownloadRequest *request);
void on_downloadCancel(QWebEngineDownloadRequest *request);
void slotLoadErrorOccurred();
private:
QString m_title, m_url;
int m_width, m_height;
bool m_isTrayEnabled, m_isFullScreen, m_isFixedSize, m_isHideButton;
Widget *m_widget;
QSystemTrayIcon *m_tray;
DToolButton *btnBack;
DToolButton *btnForward;
DToolButton *btnRefresh;
QMenu *m_menu;
QAction *m_fullScreen;
QAction *m_fixSize;
QAction *m_hideButtons;
QAction *m_clearCache;
QMenu *t_menu;
QAction *t_show;
QAction *t_about;
QAction *t_exit;
DFloatingMessage *downloadMessage;
QWidget *downloadProgressWidget;
QHBoxLayout *progressBarLayout;
DProgressBar *downloadProgressBar;
DPushButton *btnPause;
DPushButton *btnResume;
DPushButton *btnCancel;
QMutex mutex; // 通过 Mutex 互斥量禁止同时下载多个文件(使用简单的 bool 变量应该也可以实现该功能?)
bool isCanceled; // 判断是否为取消下载
};
#endif // MAINWINDOW_H
+26
View File
@@ -0,0 +1,26 @@
::-webkit-scrollbar {
width: 12px;
height: 12px;
background-color: transparent
}
::-webkit-scrollbar-thumb {
border: 2px solid transparent;
border-radius: 99px;
background-clip: padding-box;
background-color: rgba(80, 80, 80, 0.3);
-webkit-transition: background 0.2s cubic-bezier(0.34, 0.69, 0.1, 1);
transition: background 0.2s cubic-bezier(0.34, 0.69, 0.1, 1)
}
::-webkit-scrollbar-button {
display: none
}
::-webkit-scrollbar-corner {
display: none
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(80, 80, 80, 0.6)!important
}
+85
View File
@@ -0,0 +1,85 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<p><h1>帮助:</h1></p>
<p>
<h3>第一种用法: </h3><br />
spark-webapp-runtime 应用标题 目标网址 应用窗口宽度 应用窗口高度<br />
<p>
<br />
<p>
<h3>第二种用法: </h3><br />
运行 "spark-webapp-runtime -h" 查看命令行帮助信息<br />
<br />
<xmp>
用法:./spark-webapp-runtime [选项]
描述: 星火网页应用运行环境
选项:
-h, --help 显示这个此帮助。
-v, --version 显示版本信息。
-p, --parser 启用参数解析方式。默认为顺序解析方式。
-t, --title <title> 设置应用的标题。默认为
星火网页应用运行环境。
-u, --url <url> 设置要打开的目标 URL。默认为空。
-w, --width <width> 设置应用的窗口宽度。默认为 1024。
-H, --height <height> 设置应用的窗口高度。默认为 768。
-T, --tray 启用托盘图标。默认不启用。
--full-screen 以全屏模式运行。默认关闭该功能。
--fix-size 固定窗口大小。默认关闭该功能。
--hide-buttons 隐藏控制按钮。默认关闭该功能。
-i, --ico <ico> 设置应用的图标。
-d, --desc <desc> 设置应用的描述信息。
-c, --cfg <cfg> 设置应用的配置文件。
-r, --root <root> 设置内置 WebServer 的根路径。
-P, --port <port> 设置内置 WebServer 的监听端口号。
-G, --GPU <0/1> 设置启用 GPU 渲染加速,默认开启。
</xmp>
<br/>
<span style="color: red">需要注意的是, 命令行会覆盖配置文件信息。</span>
</p>
<hr />
<p><h1>How to Usage:</h1></p>
<p>
<h3>The first usage: </h3><br />
spark-webapp-runtime ApplicationTitle TargetURL ApplicationWidth ApplicationHeight<br />
<p>
<br />
<p>
<h3>The second usage: </h3><br />
Using "spark-webapp-runtime -h" to get help<br />
<br />
<xmp>
Usage: ./spark-webapp-runtime [options]
Description: spark-webapp-runtime
Options:
-h, --help Show this help Message.
-v, --version Show Version info.
-p, --parser Enable CommandLineParser. Default is false.
-t, --title <title> The Title of Application. Default is
SparkWebAppRuntime.
-u, --url <url> The target URL. Default is Blank.
-w, --width <width> The Width of Application. Default is 1024.
-H, --height <height> The Height of Application. Default is 768.
-T, --tray Enable Tray Icon. Default is false.
--full-screen Run in Fullscreen Mode. Default is false.
--fix-size Fix Window Size. Default is false.
--hide-buttons Hide Control Buttons. Default is false.
-i, --ico <ico> The ICON of Application.
-d, --desc <desc> The Description of Application.
-c, --cfg <cfg> The Configuration file of Application.
-r, --root <root> The root path of the program web service.
-P, --port <port> The port number of the program web service.
-G, --GPU <0/1> To use GPU instead of CPU to decoding. Default True.
</xmp>
<br/>
<span style="color: red">It should be noticed that the command line parsers will overwrite the configuration from the config file.</span>
</p>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#536076" d="M14.138807,3.52000025 L14.138,12.5230002 L18.6066017,8.05523757 L19.3137085,8.76234435 L13.6568542,14.4191986 L8,8.76234435 L8.70710678,8.05523757 L13.138,12.4870002 L13.138807,3.52000025 L14.138807,3.52000025 Z" transform="matrix(0 1 1 0 2.139 -2.139)"/>
</svg>

After

Width:  |  Height:  |  Size: 373 B

+3
View File
@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#536076" d="M10.138807,3.52000025 L10.138,12.5230002 L14.6066017,8.05523757 L15.3137085,8.76234435 L9.65685425,14.4191986 L4,8.76234435 L4.70710678,8.05523757 L9.138,12.4870002 L9.138807,3.52000025 L10.138807,3.52000025 Z" transform="rotate(90 9.657 11.518)"/>
</svg>

After

Width:  |  Height:  |  Size: 366 B

@@ -0,0 +1,153 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="512"
height="512"
viewBox="0 0 512 512"
fill="none"
version="1.1"
id="svg36"
sodipodi:docname="spark-webapp-runtime.svg"
inkscape:version="1.0.1 (3bc2e813f5, 2020-09-07)">
<metadata
id="metadata40">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="938"
id="namedview38"
showgrid="false"
inkscape:zoom="1.472973"
inkscape:cx="286.01835"
inkscape:cy="177.53211"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg36" />
<g
id="g49"
transform="matrix(0.7781155,0,0,0.7781155,35.015197,53.890577)">
<circle
cx="277"
cy="256"
r="256"
fill="#545454"
id="circle2" />
<g
filter="url(#filter0_b)"
id="g6">
<circle
cx="58"
cy="98"
r="58"
fill="#e6b403"
fill-opacity="0.65"
id="circle4" />
</g>
<g
filter="url(#filter1_b)"
id="g10">
<circle
cx="452.5"
cy="400.5"
r="117.5"
fill="#f2c213"
fill-opacity="0.83"
id="circle8" />
</g>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 267.656,387.599 c -10.729,1.741 -20.771,6.652 -28.78,14.201 l -3.634,3.877 c -5.152,5.418 -15.069,7.851 -22.122,5.298 0,0 -6.283,-2.24 -20.822,-10.572 -14.539,-8.308 -19.69,-12.642 -19.69,-12.642 -5.753,-4.865 -8.57,-14.641 -6.403,-21.721 l 1.516,-5.057 c 3.243,-13.562 1.141,-27.853 -5.87,-39.906 -7.01,-12.052 -18.39,-20.942 -31.777,-24.824 l -5.152,-1.204 c -7.342,-1.638 -14.419,-8.982 -15.718,-16.327 0,0 -1.204,-6.55 -1.204,-23.214 0,-16.664 1.179,-23.239 1.179,-23.239 1.324,-7.344 8.474,-14.689 15.719,-16.326 l 5.296,-1.18 c 13.363,-3.887 24.721,-12.764 31.724,-24.795 7.003,-12.031 9.115,-26.294 5.9,-39.839 l -1.589,-5.177 c -2.191,-7.153 0.65,-16.857 6.403,-21.673 0,0 5.103,-4.311 19.618,-12.715 14.539,-8.26 20.846,-10.524 20.846,-10.524 3.829,-1.1662 7.899,-1.2903 11.792,-0.3591 3.893,0.9311 7.467,2.8841 10.354,5.6571 l 3.707,3.925 c 10.15,9.592 23.589,14.927 37.551,14.907 13.928,0.059 27.35,-5.224 37.503,-14.762 l 3.804,-4.07 c 5.175,-5.4421 15.068,-7.8743 22.145,-5.322 0,0 6.283,2.312 20.798,10.548 14.539,8.356 19.715,12.691 19.715,12.691 2.906,2.714 5.033,6.159 6.159,9.974 1.126,3.816 1.21,7.864 0.243,11.723 l -1.733,5.129 c -3.277,13.523 -1.189,27.79 5.826,39.806 1.661,2.852 3.566,5.528 5.685,8.003 l -57.779,32.748 c -9.95,-24.511 -34.15,-41.811 -62.366,-41.811 -37.118,0 -67.159,29.861 -67.159,66.705 0,33.864 25.419,61.829 58.315,66.131 z"
fill="#ffffff"
id="path12" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 474.383,225.868 c 8.459,1.893 16.64,10.467 18.17,18.873 0,0 1.364,7.6 1.447,26.918 0,19.263 -1.363,26.862 -1.363,26.862 -1.531,8.49 -9.795,16.98 -18.198,18.873 l -5.955,1.364 c -14.97,4.398 -28.409,14.336 -36.813,28.811 -8.07,13.832 -10.473,30.253 -6.706,45.819 l 1.837,6.068 c 2.504,8.24 -0.779,19.541 -7.402,25.081 0,0 -5.899,5.01 -22.705,14.642 -16.779,9.548 -24.125,12.192 -24.125,12.192 -4.425,1.349 -9.131,1.492 -13.63,0.415 -4.5,-1.076 -8.632,-3.333 -11.969,-6.539 l -4.174,-4.482 c -11.297,-10.689 -26.656,-17.286 -43.546,-17.286 -3.455,-0.007 -6.882,0.269 -10.251,0.815 v -76.219 c 3.345,0.437 6.758,0.663 10.223,0.663 20.519,0.067 40.225,-8.016 54.791,-22.473 14.566,-14.457 22.801,-34.107 22.897,-54.634 0,-10.172 -1.987,-19.884 -5.596,-28.775 L 438.104,205 c 7.845,9.166 18.217,15.949 29.935,19.421 z"
fill="#ffffff"
id="path14" />
</g>
<defs
id="defs34">
<filter
id="filter0_b"
x="-45"
y="-5"
width="206"
height="206"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
id="feFlood16" />
<feGaussianBlur
in="BackgroundImage"
stdDeviation="22.5"
id="feGaussianBlur18" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur"
id="feComposite20" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur"
result="shape"
id="feBlend22" />
</filter>
<filter
id="filter1_b"
x="292"
y="240"
width="321"
height="321"
filterUnits="userSpaceOnUse"
color-interpolation-filters="sRGB">
<feFlood
flood-opacity="0"
result="BackgroundImageFix"
id="feFlood25" />
<feGaussianBlur
in="BackgroundImage"
stdDeviation="21.5"
id="feGaussianBlur27" />
<feComposite
in2="SourceAlpha"
operator="in"
result="effect1_backgroundBlur"
id="feComposite29" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="effect1_backgroundBlur"
result="shape"
id="feBlend31" />
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<g fill="#536076" transform="translate(5 5)">
<path d="M7,14 C3.13400675,14 0,10.8659932 0,7 C0,3.13400675 3.13400675,0 7,0 C8.97567015,0 10.7601747,0.81847727 12.0329334,2.1348516 L12.452,4 L12.1971567,3.99974042 C11.1596915,2.20647946 9.22073992,1 7,1 C3.6862915,1 1,3.6862915 1,7 C1,10.3137085 3.6862915,13 7,13 C9.61218135,13 11.8344797,11.3307123 12.6583461,9.00068588 L13.7101213,9 C12.8495721,11.8914889 10.1710206,14 7,14 Z"/>
<polygon points="12.75 1.982 16.286 5.518 9.214 5.518" transform="rotate(135 12.75 3.75)"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 635 B

+11
View File
@@ -0,0 +1,11 @@
<RCC>
<qresource prefix="/">
<file>css/webkit-scrollbar.css</file>
<file>images/spark-webapp-runtime.svg</file>
<file>images/Logo-Spark.png</file>
<file>images/go-previous-24.svg</file>
<file>images/go-next-24.svg</file>
<file>images/view-refresh.svg</file>
<file>help/help.html</file>
</qresource>
</RCC>
+37
View File
@@ -0,0 +1,37 @@
QT += core gui concurrent webenginewidgets
greaterThan(QT_MAJOR_VERSION, 5): QT += widgets
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++17 link_pkgconfig
PKGCONFIG += dtk6core dtk6gui dtk6widget
HEADERS += \
mainwindow.h \
globaldefine.h \
httpd.h \
httplib.h \
widget.h \
webengineview.h \
webenginepage.h \
application.h \
webengineurlrequestinterceptor.h
SOURCES += \
main.cpp \
httpd.cpp \
mainwindow.cpp \
widget.cpp \
webengineview.cpp \
webenginepage.cpp \
application.cpp \
webengineurlrequestinterceptor.cpp
RESOURCES += \
resources/resources.qrc
TRANSLATIONS += \
../translations/spark-webapp-runtime_zh_CN.ts
+11
View File
@@ -0,0 +1,11 @@
# https://cmake.org/cmake/help/v3.12/command/file.html#glob-recurse
file(GLOB_RECURSE HEADERS CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/*.hpp"
)
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/*.cpp"
)
qt6_add_resources(QRC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/resources/resources.qrc)
+80
View File
@@ -0,0 +1,80 @@
#include "webenginepage.h"
#include <QFile>
#include <QWebEngineScript>
#include <QWebEngineScriptCollection>
#include <QDesktopServices>
WebEnginePage::WebEnginePage(QObject *parent)
: QWebEnginePage(parent)
{
initScrollBarStyle();
connect(this, &QWebEnginePage::featurePermissionRequested, [&](const QUrl &origin, QWebEnginePage::Feature feature) {
if (feature != QWebEnginePage::Notifications) {
return;
}
setFeaturePermission(origin, feature, QWebEnginePage::PermissionGrantedByUser);
});
}
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::initScrollBarStyle()
{
//自定义浏览器滚动条样式
QFile file(":/css/webkit-scrollbar.css");
if (file.open(QFile::ReadOnly)) {
QString scrollbarStyleJS = QString("(function() {"
" css = document.createElement('style');"
" css.type = 'text/css';"
" css.id = '%1';"
" document.head.appendChild(css);"
" css.innerText = '%2';"
"})()\n")
.arg("scrollbarStyle")
.arg(QString::fromUtf8(file.readAll()).simplified());
file.close();
QWebEngineScript script;
script.setWorldId(QWebEngineScript::MainWorld);
script.setSourceCode(scrollbarStyleJS);
scripts().insert(script);
runJavaScript(scrollbarStyleJS, QWebEngineScript::ApplicationWorld);
}
}
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();
}
+29
View File
@@ -0,0 +1,29 @@
#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:
void initScrollBarStyle();
private slots:
void slotUrlChanged(const QUrl &url);
private:
QUrl m_currentUrl;
};
#endif // WEBENGINEPAGE_H
+13
View File
@@ -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());
}
+15
View File
@@ -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
+99
View File
@@ -0,0 +1,99 @@
#include "webengineview.h"
//#include "webengineurlrequestinterceptor.h"
#include "application.h"
#include <DGuiApplicationHelper>
#include <DNotifySender>
#include <QWebEngineSettings>
#include <QWebEngineProfile>
#include <QLocale>
#include <QFileInfo>
DGUI_USE_NAMESPACE
DCORE_USE_NAMESPACE
WebEngineView::WebEngineView(QWidget *parent)
: QWebEngineView(parent)
// , interceptor(new WebEngineUrlRequestInterceptor(this))
{
connect(this, &WebEngineView::urlChanged, this, [=]() {
// page()->setUrlRequestInterceptor(interceptor);
// page()->settings()->setAttribute(QWebEngineSettings::WebAttribute::LocalContentCanAccessRemoteUrls, true);
page()->profile()->setHttpAcceptLanguage(QLocale::system().name());
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
page()->profile()->setNotificationPresenter([&](std::unique_ptr<QWebEngineNotification> notification) {
WebEngineView::present(notification);
});
#endif
});
}
void WebEngineView::handleChromiumFlags()
{
DGuiApplicationHelper::ColorType themeType = DGuiApplicationHelper::instance()->themeType();
QString env = qgetenv("QTWEBENGINE_CHROMIUM_FLAGS");
QStringList flags = env.split(" ", Qt::SkipEmptyParts);
/**
* --blink-settings=preferredColorScheme=0 强制 prefers-color-scheme=dark (>= 5.14)
* --blink-settings=darkModeEnabled=true,darkModeInversionAlgorithm=4 强制反转网页颜色 (>= 5.14 && < 5.15)
* --blink-settings=forceDarkModeEnabled=true,darkModeInversionAlgorithm=4 强制反转网页颜色 (>= 5.15)
* --force-dark-mode 强制 prefers-color-scheme=dark (>= 5.14)
*/
foreach (const QString &flag, flags) {
if (flag == "--force-dark-mode") {
flags.removeAll(flag);
}
if (flag.startsWith("--blink-settings")) {
flags.removeAll(flag);
}
}
if (themeType == DGuiApplicationHelper::DarkType) {
flags.append("--blink-settings=preferredColorScheme=0");
} else {
flags.append("--blink-settings=preferredColorScheme=1");
}
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", flags.join(" ").toUtf8());
qDebug() << Q_FUNC_INFO << "QTWEBENGINE_CHROMIUM_FLAGS=" + qgetenv("QTWEBENGINE_CHROMIUM_FLAGS");
}
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
void WebEngineView::present(std::unique_ptr<QWebEngineNotification> &newNotification)
{
qDebug() << Q_FUNC_INFO << "New notification received:" << newNotification->title() << newNotification->message();
QImage image = newNotification->icon();
QString tmpDir = qobject_cast<Application *>(qApp)->mainWindow()->tmpDir();
QString appIcon = tmpDir + "/" + "icon.png";
if (QFileInfo::exists(appIcon)) {
QFile::remove(appIcon);
}
image.save(appIcon, "PNG");
QString summary = newNotification->title();
QString appName = qobject_cast<Application *>(qApp)->mainWindow()->title();
QString appBody = newNotification->message();
quint32 replaceId = 0;
int timeOut = 3000;
QStringList actions = QStringList() << "view" << QObject::tr("View");
QString launchCmd = qobject_cast<Application *>(qApp)->launchParams().join(",");
qDebug() << launchCmd;
QVariantMap hints;
hints.insert("x-deepin-action-view", launchCmd);
DUtil::DNotifySender(summary)
.appName(appName)
.appIcon(appIcon)
.appBody(appBody)
.replaceId(replaceId)
.timeOut(timeOut)
.actions(actions)
.hints(hints)
.call();
}
#endif
+26
View File
@@ -0,0 +1,26 @@
#ifndef WEBENGINEVIEW_H
#define WEBENGINEVIEW_H
#include <QWebEngineView>
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
#include <QWebEngineNotification>
#endif
// class WebEngineUrlRequestInterceptor;
class WebEngineView : public QWebEngineView
{
Q_OBJECT
public:
explicit WebEngineView(QWidget *parent = nullptr);
static void handleChromiumFlags();
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
static void present(std::unique_ptr<QWebEngineNotification> &newNotification);
#endif
private:
// WebEngineUrlRequestInterceptor *interceptor = nullptr;
};
#endif // WEBENGINEVIEW_H
+139
View File
@@ -0,0 +1,139 @@
#include "widget.h"
#include "webengineview.h"
#include "webenginepage.h"
#include <DApplication>
DWIDGET_USE_NAMESPACE
Widget::Widget(QString szUrl, QWidget *parent)
: QWidget(parent)
, m_webEngineView(new WebEngineView(this))
, m_spinner(new DSpinner(this))
, mainLayout(new QStackedLayout(this))
, m_szUrl(szUrl)
{
initUI();
initConnections();
}
Widget::~Widget()
{
}
QWebEnginePage *Widget::getPage()
{
return this->m_webEngineView->page();
}
void Widget::goBack()
{
m_webEngineView->back();
}
void Widget::goForward()
{
m_webEngineView->forward();
}
void Widget::refresh()
{
m_webEngineView->reload();
}
void Widget::initUI()
{
m_spinner->setFixedSize(96, 96);
DApplication *dApp = qobject_cast<DApplication *>(qApp);
m_webEngineView->setZoomFactor(dApp->devicePixelRatio());
WebEnginePage *page = new WebEnginePage(m_webEngineView);
m_webEngineView->setPage(page);
page->setUrl(QUrl());
if (!m_szUrl.isEmpty()) {
page->setUrl(QUrl(m_szUrl));
}
QWidget *spinnerWidget = new QWidget(this);
QHBoxLayout *spinnerLayout = new QHBoxLayout(spinnerWidget);
spinnerLayout->setContentsMargins(0, 0, 0, 0);
spinnerLayout->setSpacing(0);
spinnerLayout->setAlignment(Qt::AlignCenter);
spinnerLayout->addStretch();
spinnerLayout->addWidget(m_spinner);
spinnerLayout->addStretch();
mainLayout->addWidget(spinnerWidget);
mainLayout->addWidget(m_webEngineView);
}
void Widget::initConnections()
{
connect(m_webEngineView, &QWebEngineView::loadStarted, this, &Widget::slotLoadStarted, Qt::UniqueConnection);
connect(m_webEngineView, &QWebEngineView::loadProgress, this, &Widget::slotLoadProgress, Qt::UniqueConnection);
connect(m_webEngineView, &QWebEngineView::loadFinished, this, &Widget::slotLoadFinished, Qt::UniqueConnection);
// FIXME: DTK 主题切换时,动态修改 QtWebEngine prefers-color-scheme
// connect(DGuiApplicationHelper::instance(), &DGuiApplicationHelper::paletteTypeChanged, this, &Widget::slotPaletteTypeChanged, Qt::UniqueConnection);
}
void Widget::updateLayout()
{
slotLoadStarted();
mainLayout->removeWidget(m_webEngineView);
QUrl url = m_webEngineView->url();
m_webEngineView->deleteLater();
m_webEngineView = new WebEngineView(this);
mainLayout->addWidget(m_webEngineView);
initConnections();
DApplication *dApp = qobject_cast<DApplication *>(qApp);
m_webEngineView->setZoomFactor(dApp->devicePixelRatio());
WebEnginePage *page = new WebEnginePage(m_webEngineView);
m_webEngineView->setPage(page);
page->setUrl(url);
}
void Widget::slotLoadStarted()
{
mainLayout->setCurrentIndex(0);
m_spinner->start();
}
void Widget::slotLoadProgress(int value)
{
if (value == 100) {
slotLoadFinished(-1);
}
}
void Widget::slotLoadFinished(int status)
{
m_spinner->stop();
mainLayout->setCurrentIndex(1);
if (status < 0) {
qDebug() << Q_FUNC_INFO << "Load progress: 100%";
return;
}
if (!status) {
qWarning() << Q_FUNC_INFO << "Load finished, error occurred!";
emit sigLoadErrorOccurred();
}
}
void Widget::slotPaletteTypeChanged(DGuiApplicationHelper::ColorType paletteType)
{
Q_UNUSED(paletteType)
WebEngineView::handleChromiumFlags();
if (m_webEngineView) {
updateLayout();
}
}
+51
View File
@@ -0,0 +1,51 @@
#ifndef WIDGET_H
#define WIDGET_H
#include <DSpinner>
#include <DGuiApplicationHelper>
#include <QWidget>
#include <QWebEnginePage>
#include <QStackedLayout>
DWIDGET_USE_NAMESPACE
DGUI_USE_NAMESPACE
class WebEngineView;
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QString szUrl = nullptr, QWidget *parent = nullptr);
~Widget();
QWebEnginePage *getPage();
void goBack();
void goForward();
void refresh();
private:
void initUI();
void initConnections();
void updateLayout();
signals:
void sigLoadErrorOccurred();
private slots:
void slotLoadStarted();
void slotLoadProgress(int value);
void slotLoadFinished(int status);
void slotPaletteTypeChanged(DGuiApplicationHelper::ColorType paletteType);
private:
WebEngineView *m_webEngineView = nullptr;
DSpinner *m_spinner = nullptr;
QStackedLayout *mainLayout = nullptr;
QString m_szUrl;
};
#endif // WIDGET_H