FreeKill/src/main.cpp

387 lines
11 KiB
C++
Raw Normal View History

// SPDX-License-Identifier: GPL-3.0-or-later
#include "client.h"
#include "util.h"
using namespace fkShell;
#include "packman.h"
#ifndef Q_OS_WASM
2023-04-12 12:51:09 +00:00
#include "server.h"
#else
#include <emscripten.h>
#endif
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include "shell.h"
#endif
2022-12-18 07:08:01 +00:00
#if defined(Q_OS_WIN32)
#include "applink.c"
#endif
#ifndef FK_SERVER_ONLY
#include <QFileDialog>
2023-04-12 12:51:09 +00:00
#include <QScreen>
#include <QSplashScreen>
#ifndef Q_OS_ANDROID
#include <QQuickStyle>
#endif
#include "qmlbackend.h"
#endif
#if defined(Q_OS_ANDROID) || defined(Q_OS_WASM)
2023-04-12 12:51:09 +00:00
static bool copyPath(const QString &srcFilePath, const QString &tgtFilePath) {
QFileInfo srcFileInfo(srcFilePath);
if (srcFileInfo.isDir()) {
QDir targetDir(tgtFilePath);
if (!targetDir.exists()) {
targetDir.cdUp();
if (!targetDir.mkdir(QFileInfo(tgtFilePath).fileName()))
return false;
}
QDir sourceDir(srcFilePath);
2023-04-12 12:51:09 +00:00
QStringList fileNames =
sourceDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot |
QDir::Hidden | QDir::System);
foreach (const QString &fileName, fileNames) {
2023-04-12 12:51:09 +00:00
const QString newSrcFilePath = srcFilePath + QLatin1Char('/') + fileName;
const QString newTgtFilePath = tgtFilePath + QLatin1Char('/') + fileName;
if (!copyPath(newSrcFilePath, newTgtFilePath))
return false;
}
} else {
QFile::remove(tgtFilePath);
if (!QFile::copy(srcFilePath, tgtFilePath))
return false;
}
return true;
}
#endif
static void installFkAssets(const QString &src, const QString &dest) {
2023-03-09 05:32:09 +00:00
QFile f(dest + "/fk_ver");
if (f.exists() && f.open(QIODevice::ReadOnly)) {
2023-03-09 05:32:09 +00:00
auto ver = f.readLine().simplified();
if (ver == FK_VERSION) {
return;
}
}
#ifdef Q_OS_ANDROID
copyPath(src, dest);
#elif defined(Q_OS_LINUX)
2023-05-20 08:11:16 +00:00
system(QString("cp -r %1 %2/..").arg(src).arg(dest).toUtf8());
#endif
}
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
#include <stdlib.h>
#include <unistd.h>
static void prepareForLinux() {
2023-04-12 12:51:09 +00:00
// 如果用户执行的是 /usr/bin/FreeKill那么这意味着 freekill 是被包管理器安装
// 的,所以我们就需要把资源文件都复制到 ~/.local 中,并且切换当前目录
// TODO: AppImage
char buf[256] = {0};
int len = readlink("/proc/self/exe", buf, 256);
const char *home = getenv("HOME");
if (!strcmp(buf, "/usr/bin/FreeKill")) {
system("mkdir -p ~/.local/share/FreeKill");
installFkAssets("/usr/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
chdir(home);
chdir(".local/share/FreeKill");
} else if (!strcmp(buf, "/usr/local/bin/FreeKill")) {
system("mkdir -p ~/.local/share/FreeKill");
installFkAssets("/usr/local/share/FreeKill", QString("%1/.local/share/FreeKill").arg(home));
chdir(home);
chdir(".local/share/FreeKill");
}
}
#endif
static FILE *info_log = nullptr;
static FILE *err_log = nullptr;
2023-04-12 12:51:09 +00:00
void fkMsgHandler(QtMsgType type, const QMessageLogContext &context,
const QString &msg) {
auto date = QDate::currentDate();
FILE *file;
switch (type) {
case QtDebugMsg:
case QtInfoMsg:
file = info_log;
break;
case QtWarningMsg:
case QtCriticalMsg:
case QtFatalMsg:
file = err_log;
break;
}
fprintf(stderr, "\r%02d/%02d ", date.month(), date.day());
fprintf(stderr, "%s ",
2023-04-12 12:51:09 +00:00
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
fprintf(file, "\r%02d/%02d ", date.month(), date.day());
fprintf(file, "%s ",
QTime::currentTime().toString("hh:mm:ss").toLatin1().constData());
auto localMsg = msg.toUtf8();
auto threadName = QThread::currentThread()->objectName().toLatin1();
switch (type) {
case QtDebugMsg:
fprintf(stderr, "%s[D] %s\n", threadName.constData(),
2023-04-12 12:51:09 +00:00
localMsg.constData());
fprintf(file, "%s[D] %s\n", threadName.constData(),
localMsg.constData());
break;
case QtInfoMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("I", Green).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"I", localMsg.constData());
break;
case QtWarningMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("W", Yellow, Bold).toUtf8().constData(),
2023-04-12 12:51:09 +00:00
localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"W", localMsg.constData());
break;
case QtCriticalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("C", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"C", localMsg.constData());
#ifndef FK_SERVER_ONLY
if (Backend != nullptr) {
2023-04-12 12:51:09 +00:00
Backend->notifyUI(
"ErrorDialog",
QString("⛔ %1/Error occured!\n %2").arg(threadName).arg(localMsg));
}
#endif
break;
case QtFatalMsg:
fprintf(stderr, "%s[%s] %s\n", threadName.constData(),
Color("E", Red, Bold).toUtf8().constData(), localMsg.constData());
fprintf(file, "%s[%s] %s\n", threadName.constData(),
"E", localMsg.constData());
break;
}
}
2023-04-12 12:51:09 +00:00
// FreeKill 的程序主入口。整个程序就是从这里开始执行的。
int main(int argc, char *argv[]) {
// 初始化一下各种杂项信息
QThread::currentThread()->setObjectName("Main");
if (!info_log) {
2023-07-16 11:27:45 +00:00
info_log = fopen("freekill.server.info.log", "w+");
if (!info_log) {
qFatal("Cannot open info.log");
}
}
if (!err_log) {
2023-07-16 11:27:45 +00:00
err_log = fopen("freekill.server.error.log", "w+");
if (!err_log) {
qFatal("Cannot open error.log");
}
}
qInstallMessageHandler(fkMsgHandler);
QCoreApplication *app;
QCoreApplication::setApplicationName("FreeKill");
QCoreApplication::setApplicationVersion(FK_VERSION);
2023-08-12 17:39:45 +00:00
QLocale l = QLocale::system();
auto localeName = l.name();
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
prepareForLinux();
#endif
#ifndef FK_CLIENT_ONLY
2023-04-12 12:51:09 +00:00
// 分析命令行,如果有 -s 或者 --server 就在命令行直接开服务器
QCommandLineParser parser;
parser.setApplicationDescription("FreeKill server");
parser.addVersionOption();
parser.addOption({{"s", "server"}, "start server at <port>", "port"});
parser.addOption({{"h", "help"}, "display help information"});
QStringList cliOptions;
for (int i = 0; i < argc; i++)
cliOptions << argv[i];
parser.parse(cliOptions);
if (parser.isSet("version")) {
parser.showVersion();
return 0;
} else if (parser.isSet("help")) {
parser.showHelp();
return 0;
}
bool startServer = parser.isSet("server");
ushort serverPort = 9527;
if (startServer) {
app = new QCoreApplication(argc, argv);
QTranslator translator;
Q_UNUSED(translator.load("zh_CN.qm"));
QCoreApplication::installTranslator(&translator);
bool ok = false;
if (parser.value("server").toInt(&ok) && ok)
serverPort = parser.value("server").toInt();
2023-08-27 13:54:25 +00:00
Pacman = new PackMan;
Server *server = new Server;
if (!server->listen(QHostAddress::Any, serverPort)) {
qFatal("cannot listen on port %d!\n", serverPort);
app->exit(1);
} else {
qInfo("Server is listening on port %d", serverPort);
2023-04-12 12:51:09 +00:00
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
// Linux 服务器的话可以启用一个 Shell 来操作服务器。
auto shell = new Shell;
shell->start();
2023-04-12 12:51:09 +00:00
#endif
}
return app->exec();
}
#endif
#ifdef FK_SERVER_ONLY
2023-04-12 12:51:09 +00:00
// 根本没编译 GUI 相关的功能,直接在此退出
qFatal("This is server-only build and have no GUI support.\n\
Please use ./FreeKill -s to start a server in command line.");
#else
2023-04-12 12:51:09 +00:00
#ifdef Q_OS_WASM
EM_ASM (
FS.mkdir('/assets');
FS.mount(IDBFS, {}, '/assets');
FS.chdir('/assets');
FS.syncfs(true, function(err) {
});
);
copyPath(":/", QDir::currentPath());
2023-04-12 12:51:09 +00:00
#endif
app = new QApplication(argc, argv);
2023-04-12 12:51:09 +00:00
#ifdef DESKTOP_BUILD
((QApplication *)app)->setWindowIcon(QIcon("image/icon.png"));
2023-04-12 12:51:09 +00:00
#endif
2023-04-12 12:51:09 +00:00
#define SHOW_SPLASH_MSG(msg) \
splash.showMessage(msg, Qt::AlignHCenter | Qt::AlignBottom);
2023-04-12 12:51:09 +00:00
#ifdef Q_OS_ANDROID
// 安卓:先切换到我们安装程序的那个外部存储目录去
QJniObject::callStaticMethod<void>("org/notify/FreeKill/Helper", "InitView",
"()V");
QDir::setCurrent(
"/storage/emulated/0/Android/data/org.notify.FreeKill/files");
2023-03-04 22:39:03 +00:00
2023-04-12 12:51:09 +00:00
// 然后显示欢迎界面,并在需要时复制资源素材等
QScreen *screen = qobject_cast<QApplication *>(app)->primaryScreen();
QRect screenGeometry = screen->geometry();
int screenWidth = screenGeometry.width();
int screenHeight = screenGeometry.height();
2023-04-12 12:51:09 +00:00
QSplashScreen splash(QPixmap("assets:/res/image/splash.jpg")
.scaled(screenWidth, screenHeight));
splash.showFullScreen();
SHOW_SPLASH_MSG("Copying resources...");
installFkAssets("assets:/res", QDir::currentPath());
2023-04-12 12:51:09 +00:00
#else
// 不是安卓,那么直接启动欢迎界面,也就是不复制东西了
QSplashScreen splash(QPixmap("image/splash.jpg"));
splash.show();
2023-04-12 12:51:09 +00:00
#endif
SHOW_SPLASH_MSG("Loading qml files...");
QQmlApplicationEngine *engine = new QQmlApplicationEngine;
2023-04-12 12:51:09 +00:00
#ifndef Q_OS_ANDROID
2023-01-16 13:57:05 +00:00
QQuickStyle::setStyle("Material");
2023-04-12 12:51:09 +00:00
#endif
QTranslator translator;
2023-11-07 04:49:31 +00:00
if (localeName.startsWith("zh_")) {
Q_UNUSED(translator.load("zh_CN.qm"));
} else {
Q_UNUSED(translator.load("en_US.qm"));
}
QCoreApplication::installTranslator(&translator);
2023-02-26 08:51:29 +00:00
QmlBackend backend;
backend.setEngine(engine);
2023-08-27 13:54:25 +00:00
Pacman = new PackMan;
2023-04-12 12:51:09 +00:00
// 向 Qml 中先定义几个全局变量
engine->rootContext()->setContextProperty("FkVersion", FK_VERSION);
engine->rootContext()->setContextProperty("Backend", &backend);
engine->rootContext()->setContextProperty("ModBackend", nullptr);
engine->rootContext()->setContextProperty("Pacman", Pacman);
2023-08-12 17:39:45 +00:00
engine->rootContext()->setContextProperty("SysLocale", localeName);
2023-04-12 12:51:09 +00:00
#ifdef QT_DEBUG
bool debugging = true;
2023-04-12 12:51:09 +00:00
#else
bool debugging = false;
2023-04-12 12:51:09 +00:00
#endif
engine->rootContext()->setContextProperty("Debugging", debugging);
QString system;
2023-04-12 12:51:09 +00:00
#if defined(Q_OS_ANDROID)
system = "Android";
2023-04-12 12:51:09 +00:00
#elif defined(Q_OS_WASM)
system = "Web";
engine->rootContext()->setContextProperty("ServerAddr", "127.0.0.1:9527");
2023-04-12 12:51:09 +00:00
#elif defined(Q_OS_WIN32)
system = "Win";
::system("chcp 65001");
2023-04-12 12:51:09 +00:00
#elif defined(Q_OS_LINUX)
system = "Linux";
2023-04-12 12:51:09 +00:00
#else
system = "Other";
2023-04-12 12:51:09 +00:00
#endif
engine->rootContext()->setContextProperty("OS", system);
2023-04-12 12:51:09 +00:00
engine->rootContext()->setContextProperty(
"AppPath", QUrl::fromLocalFile(QDir::currentPath()));
engine->addImportPath(QDir::currentPath());
2023-04-12 12:51:09 +00:00
// 加载完全局变量后,就再去加载 main.qml此时UI界面正式显示
engine->load("Fk/main.qml");
2023-04-12 12:51:09 +00:00
// qml 报错了就直接退出吧
if (engine->rootObjects().isEmpty())
return -1;
2023-04-12 12:51:09 +00:00
// 关闭欢迎界面然后进入Qt主循环
splash.close();
int ret = app->exec();
2022-03-27 12:00:29 +00:00
2023-04-12 12:51:09 +00:00
// 先删除 engine
// 防止报一堆错 "TypeError: Cannot read property 'xxx' of null"
delete engine;
delete Pacman;
2022-03-27 12:00:29 +00:00
#ifdef Q_OS_WASM
EM_ASM (
FS.syncfs(function(err) {});
);
#endif
if (info_log) {
fclose(info_log);
info_log = nullptr;
}
if (err_log) {
fclose(err_log);
info_log = nullptr;
}
return ret;
#endif
}