Qv2ray/src/main.cpp

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
}