mirror of
https://github.com/Qv2ray/Qv2ray.git
synced 2025-05-20 02:40:20 +08:00
394 lines
18 KiB
C++
394 lines
18 KiB
C++
#include <QFileInfo>
|
|
#include <QStandardPaths>
|
|
#include <QTranslator>
|
|
#include <QStyle>
|
|
#include <QLocale>
|
|
#include <QObject>
|
|
#include <QStyleFactory>
|
|
|
|
#include <QApplication>
|
|
#include <singleapplication.h>
|
|
|
|
#include "w_MainWindow.hpp"
|
|
|
|
bool verifyConfigAvaliability(QString path, bool checkExistingConfig)
|
|
{
|
|
// Does not exist.
|
|
if (!QDir(path).exists()) return false;
|
|
|
|
// A temp file used to test file permissions in that folder.
|
|
QFile testFile(path + ".qv2ray_file_write_test_file" + QString::number(QTime::currentTime().msecsSinceStartOfDay()));
|
|
bool opened = testFile.open(QFile::OpenModeFlag::ReadWrite);
|
|
|
|
if (!opened) {
|
|
LOG(MODULE_CONFIG, "Directory at: " + path.toStdString() + " cannot be used as a valid config file path.")
|
|
LOG(MODULE_INIT, "---> Cannot create a new file or openwrite a file.")
|
|
return false;
|
|
} else {
|
|
testFile.write("qv2ray test file, feel free to remove.");
|
|
testFile.flush();
|
|
testFile.close();
|
|
bool removed = testFile.remove();
|
|
|
|
if (!removed) {
|
|
// This is rare, as we can create a file but failed to remove it.
|
|
LOG(MODULE_CONFIG, "Directory at: " + path.toStdString() + " cannot be used as a valid config file path.")
|
|
LOG(MODULE_INIT, "---> Cannot remove a file.")
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (checkExistingConfig) {
|
|
// Check if an existing config is found.
|
|
QFile configFile(path + "Qv2ray.conf");
|
|
|
|
// No such config file.
|
|
if (!configFile.exists()) return false;
|
|
|
|
bool opened2 = configFile.open(QIODevice::ReadWrite);
|
|
|
|
try {
|
|
if (opened2) {
|
|
// Verify if the config can be loaded.
|
|
// Failed to parse if we changed the file structure...
|
|
// Original:
|
|
// -- auto conf = StructFromJsonString<Qv2rayConfig>(configFile.readAll());
|
|
//
|
|
// Verify JSON file format. (only) because this file version may not be upgraded and may contain unsupported structure.
|
|
auto err = VerifyJsonString(StringFromFile(&configFile));
|
|
|
|
if (!err.isEmpty()) {
|
|
LOG(MODULE_INIT, "Json parse returns: " + err.toStdString())
|
|
return false;
|
|
} else {
|
|
// If the file format is valid.
|
|
auto conf = JsonFromString(StringFromFile(&configFile));
|
|
LOG(MODULE_CONFIG, "Path: " + path.toStdString() + " contains a config file, in version " + to_string(conf["config_version"].toInt()))
|
|
configFile.close();
|
|
return true;
|
|
}
|
|
} else {
|
|
LOG(MODULE_CONFIG, "File: " + configFile.fileName().toStdString() + " cannot be opened!")
|
|
return false;
|
|
}
|
|
} catch (...) {
|
|
LOG(MODULE_CONFIG, "Exception raised when checking config: " + configFile.fileName().toStdString())
|
|
//LOG(MODULE_INIT, e->what())
|
|
QvMessageBox(nullptr, QObject::tr("Warning"), QObject::tr("Qv2ray cannot load the config file from here:") + NEWLINE + configFile.fileName());
|
|
return false;
|
|
}
|
|
} else return true;
|
|
}
|
|
|
|
bool initialiseQv2ray()
|
|
{
|
|
// Some built-in search paths for Qv2ray to find configs. Reversed Priority (load the bottom one if possible).
|
|
QStringList configFilePaths;
|
|
configFilePaths << QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
configFilePaths << QDir::homePath() + "/.config/qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
configFilePaths << QDir::currentPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
QString configPath = "";
|
|
//
|
|
bool hasExistingConfig = false;
|
|
|
|
for (auto path : configFilePaths) {
|
|
// Verify the config path, check if the config file exists and in the correct JSON format.
|
|
// True means we check for config existance as well. --| |
|
|
bool isValidConfigPath = verifyConfigAvaliability(path, true);
|
|
|
|
if (isValidConfigPath) {
|
|
DEBUG(MODULE_INIT, "Path: " + path.toStdString() + " is valid.")
|
|
configPath = path;
|
|
hasExistingConfig = true;
|
|
} else {
|
|
DEBUG(MODULE_INIT, "Path: " + path.toStdString() + " does not contain a valid config file.")
|
|
}
|
|
}
|
|
|
|
// If there's no existing config.
|
|
if (hasExistingConfig) {
|
|
// Use the config path found by the checks above
|
|
SetConfigDirPath(&configPath);
|
|
LOG(MODULE_INIT, "Using " + QV2RAY_CONFIG_DIR.toStdString() + " as the config path.")
|
|
} else {
|
|
// Create new config at these dirs, these are default values for each platform.
|
|
#ifdef Q_OS_WIN
|
|
configPath = QDir::currentPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
#elif defined (Q_OS_LINUX)
|
|
configPath = QDir::homePath() + "/.config/qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
#elif defined (__APPLE__)
|
|
configPath = QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
|
|
#else
|
|
LOG(MODULE_INIT, "CANNOT CONTINUE because Qv2ray cannot determine the OS type.")
|
|
static_assert(false, "Qv2ray Cannot understand the enviornment");
|
|
#endif
|
|
bool mkpathResult = QDir().mkpath(configPath);
|
|
|
|
// Check if the dirs are write-able
|
|
if (mkpathResult && verifyConfigAvaliability(configPath, false)) {
|
|
// Found a valid config dir, with write permission, but assume no config is located in it.
|
|
LOG(MODULE_INIT, "Set " + configPath.toStdString() + " as the config path.")
|
|
SetConfigDirPath(&configPath);
|
|
|
|
if (QFile::exists(QV2RAY_CONFIG_FILE)) {
|
|
// As we already tried to load config from every possible dir.
|
|
// This condition branch (!hasExistingConfig check) holds the fact that
|
|
// current config dir, should NOT contain any valid file (at least in the same name)
|
|
// It usually means that QV2RAY_CONFIG_FILE here is corrupted, in JSON format.
|
|
// Otherwise Qv2ray would have loaded this config already instead of notifying to
|
|
// create a new config in this folder.
|
|
LOG(MODULE_INIT, "This should not occur: Qv2ray config exists but failed to load.")
|
|
QvMessageBox(nullptr, QObject::tr("Failed to initialise Qv2ray"),
|
|
QObject::tr("Failed to determine the location of config file.") + NEWLINE +
|
|
QObject::tr("Qv2ray will now exit.") + NEWLINE +
|
|
QObject::tr("Please report if you think it's a bug."));
|
|
return false;
|
|
}
|
|
|
|
Qv2rayConfig conf;
|
|
conf.v2AssetsPath = QV2RAY_DEFAULT_VASSETS_PATH;
|
|
conf.v2CorePath = QV2RAY_DEFAULT_VCORE_PATH;
|
|
conf.logLevel = 3;
|
|
//
|
|
// Save initial config.
|
|
SetGlobalConfig(conf);
|
|
LOG(MODULE_INIT, "Created initial config file.")
|
|
} else {
|
|
// None of the path above can be used as a dir for storing config.
|
|
// Even the last folder failed to pass the check.
|
|
LOG(MODULE_INIT, "FATAL")
|
|
LOG(MODULE_INIT, " ---> CANNOT find a proper place to store Qv2ray config files.")
|
|
QString searchPath = Stringify(configFilePaths, NEWLINE);
|
|
QvMessageBox(nullptr, QObject::tr("Cannot Start Qv2ray"),
|
|
QObject::tr("Cannot find a place to store config files.") + NEWLINE +
|
|
QObject::tr("Qv2ray has searched these paths below:") +
|
|
NEWLINE + NEWLINE + searchPath + NEWLINE +
|
|
QObject::tr("Qv2ray will now exit."));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!QDir(QV2RAY_GENERATED_DIR).exists()) {
|
|
// The dir used to generate final config file, for v2ray interaction.
|
|
QDir().mkdir(QV2RAY_GENERATED_DIR);
|
|
LOG(MODULE_INIT, "Created config generation dir at: " + QV2RAY_GENERATED_DIR.toStdString())
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
LOG(MODULE_INIT, "Qv2ray " QV2RAY_VERSION_STRING " running on " + QSysInfo::prettyProductName().toStdString() + " " + QSysInfo::currentCpuArchitecture().toStdString() + NEWLINE)
|
|
//
|
|
// This line must be called before any other ones, since we are using these values to identify instances.
|
|
SingleApplication::setApplicationName("Qv2ray");
|
|
SingleApplication::setApplicationVersion(QV2RAY_VERSION_STRING);
|
|
SingleApplication::setApplicationDisplayName("Qv2ray");
|
|
//
|
|
//
|
|
#ifdef QT_DEBUG
|
|
// ----------------------------> For debug build...
|
|
SingleApplication::setApplicationName("Qv2ray - DEBUG");
|
|
#endif
|
|
//
|
|
SingleApplication _qApp(argc, argv, false, SingleApplication::Mode::ExcludeAppPath | SingleApplication::Mode::ExcludeAppVersion);
|
|
// Early initialisation
|
|
//
|
|
//
|
|
// Install a default translater. From the OS/DE
|
|
auto _lang = QLocale::system().name().replace("_", "-");
|
|
auto _sysTranslator = getTranslator(_lang);
|
|
|
|
if (_lang != "en-US") {
|
|
// Do not install en-US as it's the default language.
|
|
bool _result_ = qApp->installTranslator(_sysTranslator);
|
|
LOG(MODULE_UI, "Installing a tranlator from OS: " + _lang.toStdString() + " -- " + (_result_ ? "OK" : "Failed"))
|
|
}
|
|
|
|
LOG("LICENCE", NEWLINE "This program comes with ABSOLUTELY NO WARRANTY." NEWLINE
|
|
"This is free software, and you are welcome to redistribute it" NEWLINE
|
|
"under certain conditions." NEWLINE NEWLINE
|
|
"Copyright (C) 2019 Leroy.H.Y (@lhy0403): Qv2ray Current Developer" NEWLINE
|
|
"Copyright (C) 2019 Hork (@aliyuchang33): Hv2ray Initial Designs & gRPC implementation " NEWLINE
|
|
"Copyright (C) 2019 SOneWinstone (@SoneWinstone): Hv2ray/Qv2ray HTTP Request Helper" NEWLINE
|
|
"Qv2ray ArtWork Done By ArielAxionL (@axionl)" NEWLINE
|
|
"TheBadGateway (@thebadgateway): Qv2ray Russian Translations" NEWLINE
|
|
"Riko (@rikakomoe): Qv2ray patch 8a8c1a/PR115"
|
|
NEWLINE NEWLINE
|
|
"Libraries that have been used in Qv2ray are listed below (Sorted by date added):" NEWLINE
|
|
"Copyright (c) 2019 dridk (@dridk): X2Struct (Apache)" NEWLINE
|
|
"Copyright (c) 2011 SCHUTZ Sacha (@dridk): QJsonModel (MIT)" NEWLINE
|
|
"Copyright (c) 2019 Nikolaos Ftylitakis (@ftylitak): QZXing (Apache2)" NEWLINE
|
|
"Copyright (c) 2016 Singein (@Singein): ScreenShot (MIT)" NEWLINE
|
|
"Copyright (c) 2016 Nikhil Marathe (@nikhilm): QHttpServer (MIT)" NEWLINE
|
|
"Copyright (c) 2019 Itay Grudev (@itay-grudev): SingleApplication (MIT)" NEWLINE
|
|
"Copyright (c) 2019 paceholder (@paceholder): nodeeditor (QNodeEditor modified by lhy0403) (BSD-3-Clause)" NEWLINE
|
|
"Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)" NEWLINE
|
|
NEWLINE)
|
|
//
|
|
LOG(MODULE_INIT, "Qv2ray Start Time: " + QString::number(QTime::currentTime().msecsSinceStartOfDay()).toStdString())
|
|
DEBUG("DEBUG", "WARNING: ============================== This is a debug build, many features are not stable enough. ==============================")
|
|
//
|
|
// Load the language translation list.
|
|
auto langs = GetFileList(QDir(":/translations"));
|
|
|
|
if (langs.empty()) {
|
|
LOG(MODULE_INIT, "FAILED to find any translations. THIS IS A BUILD ERROR.")
|
|
QvMessageBox(nullptr, QObject::tr("Cannot load languages"), QObject::tr("Qv2ray will continue running, but you cannot change the UI language."));
|
|
} else {
|
|
for (auto lang : langs) {
|
|
LOG(MODULE_INIT, "Found Translator: " + lang.toStdString())
|
|
}
|
|
}
|
|
|
|
// Qv2ray Initialize, find possible config paths and verify them.
|
|
if (!initialiseQv2ray()) {
|
|
return -1;
|
|
}
|
|
|
|
// Load the config for upgrade, but do not parse it to the struct.
|
|
auto conf = JsonFromString(StringFromFile(new QFile(QV2RAY_CONFIG_FILE)));
|
|
//
|
|
auto confVersion = conf["config_version"].toVariant().toString().toInt();
|
|
|
|
if (confVersion > QV2RAY_CONFIG_VERSION) {
|
|
// Config version is larger than the current version...
|
|
// This is rare but it may happen....
|
|
QvMessageBox(nullptr, QObject::tr("Qv2ray Cannot Continue"),
|
|
QObject::tr("You are running a lower version of Qv2ray compared to the current config file.") + NEWLINE +
|
|
QObject::tr("Please check if there's an issue explaining about it.") + NEWLINE +
|
|
QObject::tr("Or submit a new issue if you think this is an error.") + NEWLINE + NEWLINE +
|
|
QObject::tr("Qv2ray will now exit."));
|
|
return -3;
|
|
}
|
|
|
|
if (confVersion < QV2RAY_CONFIG_VERSION) {
|
|
// That is, config file needs to be upgraded.
|
|
conf = Qv2ray::UpgradeConfig(confVersion, QV2RAY_CONFIG_VERSION, conf);
|
|
}
|
|
|
|
// Load config object from upgraded config QJsonObject
|
|
auto confObject = StructFromJsonString<Qv2rayConfig>(JsonToString(conf));
|
|
// Remove system translator, for loading custom translations.
|
|
qApp->removeTranslator(_sysTranslator);
|
|
LOG(MODULE_INIT, "Removing system translations")
|
|
|
|
if (confObject.uiConfig.language.isEmpty()) {
|
|
// Prevent empty.
|
|
LOG(MODULE_UI, "Setting default UI language to en-US")
|
|
confObject.uiConfig.language = "en-US";
|
|
}
|
|
|
|
if (qApp->installTranslator(getTranslator(confObject.uiConfig.language))) {
|
|
LOG(MODULE_INIT, "Successfully installed a translator for " + confObject.uiConfig.language.toStdString())
|
|
} else {
|
|
// Do not translate these.....
|
|
// If a translator fails to load, pop up a message.
|
|
QvMessageBox(
|
|
nullptr, "Translation Failed",
|
|
"Cannot load translation for " + confObject.uiConfig.language + ", English is now used.\r\n\r\n"
|
|
"Please go to Preferences Window to change or Report a Bug at: \r\n"
|
|
"https://github.com/lhy0403/Qv2ray/issues/new");
|
|
}
|
|
|
|
// Let's save the config.
|
|
SetGlobalConfig(confObject);
|
|
//
|
|
// Check OpenSSL version for auto-update and subscriptions
|
|
auto osslReqVersion = QSslSocket::sslLibraryBuildVersionString();
|
|
auto osslCurVersion = QSslSocket::sslLibraryVersionString();
|
|
LOG(MODULE_NETWORK, "Current OpenSSL version: " + osslCurVersion.toStdString())
|
|
|
|
if (!QSslSocket::supportsSsl()) {
|
|
LOG(MODULE_NETWORK, "Required OpenSSL version: " + osslReqVersion.toStdString())
|
|
LOG(MODULE_NETWORK, "OpenSSL library MISSING, Quitting.")
|
|
QvMessageBox(nullptr, QObject::tr("DependencyMissing"),
|
|
QObject::tr("Cannot find openssl libs") + "\r\n" +
|
|
QObject::tr("This could be caused by a missing of `openssl` package in your system. Or an AppImage issue.") + "\r\n" +
|
|
QObject::tr("If you are using AppImage, please report a bug.") + "\r\n\r\n" +
|
|
QObject::tr("Please refer to Github Issue #65 to check for solutions.") + "\r\n" +
|
|
QObject::tr("Github Issue Link: ") + "https://github.com/lhy0403/Qv2ray/issues/65" + "\r\n\r\n" +
|
|
QObject::tr("Technical Details") + "\r\n" +
|
|
"OSsl.Rq.V=" + osslReqVersion + "\r\n" +
|
|
"OSsl.Cr.V=" + osslCurVersion);
|
|
return -2;
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
// Set special font in Windows
|
|
QFont font;
|
|
font.setPointSize(9);
|
|
font.setFamily("微软雅黑");
|
|
_qApp.setFont(font);
|
|
#endif
|
|
#ifdef QV2RAY_USE_BUILTIN_DARKTHEME
|
|
LOG(MODULE_UI, "Using built-in theme.")
|
|
|
|
if (confObject.uiConfig.useDarkTheme) {
|
|
LOG(MODULE_UI, " --> Using built-in dark theme.")
|
|
// From https://forum.qt.io/topic/101391/windows-10-dark-theme/4
|
|
_qApp.setStyle("Fusion");
|
|
QPalette darkPalette;
|
|
QColor darkColor = QColor(45, 45, 45);
|
|
QColor disabledColor = QColor(127, 127, 127);
|
|
// See Qv2rayBase.hpp MACRO --> BLACK(obj)
|
|
QColor defaultTextColor = QColor(210, 210, 210);
|
|
darkPalette.setColor(QPalette::Window, darkColor);
|
|
darkPalette.setColor(QPalette::WindowText, defaultTextColor);
|
|
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, disabledColor);
|
|
darkPalette.setColor(QPalette::Base, QColor(18, 18, 18));
|
|
darkPalette.setColor(QPalette::AlternateBase, darkColor);
|
|
darkPalette.setColor(QPalette::ToolTipBase, defaultTextColor);
|
|
darkPalette.setColor(QPalette::ToolTipText, defaultTextColor);
|
|
darkPalette.setColor(QPalette::Text, defaultTextColor);
|
|
darkPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor);
|
|
darkPalette.setColor(QPalette::Button, darkColor);
|
|
darkPalette.setColor(QPalette::ButtonText, defaultTextColor);
|
|
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor);
|
|
darkPalette.setColor(QPalette::BrightText, Qt::red);
|
|
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
|
|
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
|
|
darkPalette.setColor(QPalette::HighlightedText, Qt::black);
|
|
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor);
|
|
qApp->setPalette(darkPalette);
|
|
qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }");
|
|
}
|
|
|
|
#else
|
|
// Set custom themes.
|
|
QStringList themes = QStyleFactory::keys();
|
|
//_qApp.setDesktopFileName("qv2ray.desktop");
|
|
|
|
if (themes.contains(confObject.uiConfig.theme)) {
|
|
_qApp.setStyle(confObject.uiConfig.theme);
|
|
LOG(MODULE_INIT " " MODULE_UI, "Setting Qv2ray UI themes: " + confObject.uiConfig.theme.toStdString())
|
|
}
|
|
|
|
#endif
|
|
// Show MainWindow
|
|
MainWindow w;
|
|
#ifndef QT_DEBUG
|
|
|
|
try {
|
|
#endif
|
|
QObject::connect(&_qApp, &SingleApplication::instanceStarted, [&w]() {
|
|
// When a second instance is connected, show the mainwindow.
|
|
w.show();
|
|
w.raise();
|
|
w.activateWindow();
|
|
});
|
|
auto rcode = _qApp.exec();
|
|
LOG(MODULE_INIT, "Quitting normally")
|
|
return rcode;
|
|
#ifndef QT_DEBUG
|
|
} catch (...) {
|
|
QvMessageBox(nullptr, "ERROR", "There's something wrong happened and Qv2ray will quit now.");
|
|
LOG(MODULE_INIT, "EXCEPTION THROWN: " __FILE__)
|
|
return -9;
|
|
}
|
|
|
|
#endif
|
|
}
|