mirror of
https://gitee.com/spark-store-project/spark-web-app-runtime.git
synced 2026-08-07 23:43:57 +08:00
feat: adapt to Qt6
adapt to Qt6 Log: adapt to Qt6; bump version to 1.7.1
This commit is contained in:
@@ -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})
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+345
@@ -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();
|
||||
}
|
||||
@@ -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(" "));
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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());
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user