refactor: fixed SingleApplication crash, refactored

This commit is contained in:
QxQ 2020-10-14 20:24:02 +08:00
parent 94d701ce91
commit 4cd00df503
24 changed files with 710 additions and 758 deletions

@ -1 +1 @@
Subproject commit 9a408690ceb260a220d50f5070af9bcda58cacc2
Subproject commit 0e42d86cecf008e850bc1f7540196de1f6bf182d

View File

@ -6,6 +6,7 @@ set(QV2RAY_BASE_SOURCES
${QV2RAY_BASEDIR_BASE}/Qv2rayBaseApplication.cpp
${QV2RAY_BASEDIR_BASE}/Qv2rayBaseApplication.hpp
# Platform Dependent UI
${CMAKE_SOURCE_DIR}/src/ui/Qv2rayPlatformApplication.cpp
${CMAKE_SOURCE_DIR}/src/ui/Qv2rayPlatformApplication.hpp
#
${QV2RAY_BASEDIR_BASE}/JsonHelpers.hpp

View File

@ -1 +1 @@
6000
6001

View File

@ -47,7 +47,7 @@ using namespace Qv2ray::base::objects::transfer;
#endif
// Get Configured Config Dir Path
#define QV2RAY_CONFIG_DIR (qvApplicationInstance->ConfigPath)
#define QV2RAY_CONFIG_DIR (QvCoreApplication->ConfigPath)
#define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf")
//
#define QV2RAY_CONNECTIONS_DIR (QV2RAY_CONFIG_DIR + "connections/")
@ -67,13 +67,6 @@ using namespace Qv2ray::base::objects::transfer;
#error Both QV2RAY_DEFAULT_VCORE_PATH and QV2RAY_DEFAULT_VASSETS_PATH need to be presented when using manually specify the paths.
#endif
#define QV2RAY_TPROXY_VCORE_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ray" QV2RAY_EXECUTABLE_SUFFIX)
#define QV2RAY_TPROXY_VCTL_PATH (QV2RAY_CONFIG_DIR + "vcore/v2ctl" QV2RAY_EXECUTABLE_SUFFIX)
constexpr auto QV2RAY_VCORE_LOG_DIRNAME = "logs/";
constexpr auto QV2RAY_VCORE_ACCESS_LOG_FILENAME = "access.log";
constexpr auto QV2RAY_VCORE_ERROR_LOG_FILENAME = "error.log";
#ifdef Q_OS_MACOS
#define ACCESS_OPTIONAL_VALUE(obj) (*obj)
#else
@ -85,49 +78,14 @@ constexpr auto QV2RAY_VCORE_ERROR_LOG_FILENAME = "error.log";
#define OUTBOUND_TAG_BLACKHOLE "BLACKHOLE"
#define OUTBOUND_TAG_DIRECT "DIRECT"
#define OUTBOUND_TAG_PROXY "PROXY"
#define OUTBOUND_TAG_FORWARD_PROXY "_QV2RAY_FORWARD_PROXY_"
#define OUTBOUND_TAG_FORWARD_PROXY "QV2RAY_FORWARD_PROXY"
#define API_TAG_DEFAULT "_QV2RAY_API_"
#define API_TAG_INBOUND "_QV2RAY_API_INBOUND_"
#define API_TAG_DEFAULT "QV2RAY_API"
#define API_TAG_INBOUND "QV2RAY_API_INBOUND"
#define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_"
namespace Qv2ray
{
inline QStringList Qv2rayAssetsPaths(const QString &dirName)
{
#define makeAbs(p) QDir(p).absolutePath()
// Configuration Path
QStringList list;
// This is the default behavior on Windows
list << makeAbs(QCoreApplication::applicationDirPath() + "/" + dirName);
list << makeAbs(QV2RAY_CONFIG_DIR + dirName);
list << ":/" + dirName;
//
list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, dirName, QStandardPaths::LocateDirectory);
list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, dirName, QStandardPaths::LocateDirectory);
#ifdef Q_OS_LINUX
// For AppImage?
if (qEnvironmentVariableIsSet("APPIMAGE"))
list << makeAbs(QCoreApplication::applicationDirPath() + "/../share/qv2ray/" + dirName);
// For Snap
if (qEnvironmentVariableIsSet("SNAP"))
list << makeAbs(qEnvironmentVariable("SNAP") + "/usr/share/qv2ray/" + dirName);
// Linux platform directories.
list << makeAbs("/usr/local/lib/qv2ray/" + dirName);
list << makeAbs("/usr/lib/qv2ray/" + dirName);
list << makeAbs("/lib/qv2ray/" + dirName);
//
list << makeAbs("/usr/local/share/qv2ray/" + dirName);
list << makeAbs("/usr/share/qv2ray/" + dirName);
#elif defined(Q_OS_MAC)
// macOS platform directories.
list << QDir(QCoreApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath();
#endif
list.removeDuplicates();
return list;
#undef makeAbs
}
} // namespace Qv2ray

View File

@ -4,349 +4,68 @@
#include "core/settings/SettingsBackend.hpp"
#include "utils/QvHelpers.hpp"
#ifdef QT_DEBUG
const static inline QString QV2RAY_URL_SCHEME = "qv2ray-debug";
#else
const static inline QString QV2RAY_URL_SCHEME = "qv2ray";
#endif
#define QV_MODULE_NAME "BaseApplication"
constexpr auto QV2RAY_CONFIG_PATH_ENV_NAME = "QV2RAY_CONFIG_PATH";
inline QString makeAbs(const QString &p)
{
return QDir(p).absolutePath();
}
Qv2rayApplicationManager::Qv2rayApplicationManager()
Qv2rayApplicationInterface::Qv2rayApplicationInterface()
{
ConfigObject = new Qv2rayConfigObject;
qvApplicationInstance = this;
LOG("Qv2ray " QV2RAY_VERSION_STRING " on " + QSysInfo::prettyProductName() + " " + QSysInfo::currentCpuArchitecture());
DEBUG("Qv2ray Start Time: " + QSTRN(QTime::currentTime().msecsSinceStartOfDay()));
DEBUG(std::string{ "QV2RAY_BUILD_INFO" }, QV2RAY_BUILD_INFO);
DEBUG(std::string{ "QV2RAY_BUILD_EXTRA_INFO" }, QV2RAY_BUILD_EXTRA_INFO);
DEBUG(std::string{ "QV2RAY_BUILD_NUMBER" }, QSTRN(QV2RAY_VERSION_BUILD));
QvCoreApplication = this;
LOG("Qv2ray", QV2RAY_VERSION_STRING, "on", QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
DEBUG("Qv2ray Start Time: ", QTime::currentTime().msecsSinceStartOfDay());
DEBUG("QV2RAY_BUILD_INFO", QV2RAY_BUILD_INFO);
DEBUG("QV2RAY_BUILD_EXTRA_INFO", QV2RAY_BUILD_EXTRA_INFO);
DEBUG("QV2RAY_BUILD_NUMBER", QSTRN(QV2RAY_VERSION_BUILD));
QStringList licenseList;
licenseList << "This program comes with ABSOLUTELY NO WARRANTY.";
licenseList << "This is free software, and you are welcome to redistribute it";
licenseList << "under certain conditions.";
licenseList << "Copyright (c) 2019-2020 Qv2ray Development Group.";
licenseList << "Third-party libraries that have been used in this program can be found in the About page.";
LOG(licenseList.join(NEWLINE));
}
Qv2rayApplicationManager::~Qv2rayApplicationManager()
Qv2rayApplicationInterface::~Qv2rayApplicationInterface()
{
delete ConfigObject;
qvApplicationInstance = nullptr;
QvCoreApplication = nullptr;
}
bool Qv2rayApplicationManager::LocateConfiguration()
QStringList Qv2rayApplicationInterface::GetAssetsPaths(const QString &dirName) const
{
LOG("Application exec path: " + qApp->applicationDirPath());
// Non-standard paths needs special handing for "_debug"
const auto currentPathConfig = qApp->applicationDirPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX;
const auto homeQv2ray = QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
//
// Standard paths already handles the "_debug" suffix for us.
const auto configQv2ray = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
//
//
// Some built-in search paths for Qv2ray to find configs. (load the first one if possible).
const auto useManualConfigPath = qEnvironmentVariableIsSet(QV2RAY_CONFIG_PATH_ENV_NAME);
const auto manualConfigPath = qEnvironmentVariable(QV2RAY_CONFIG_PATH_ENV_NAME);
//
QStringList configFilePaths;
if (useManualConfigPath)
{
LOG("Using config path from env: " + manualConfigPath);
configFilePaths << manualConfigPath;
}
else
{
configFilePaths << currentPathConfig;
configFilePaths << configQv2ray;
configFilePaths << homeQv2ray;
}
// Configuration Path
QStringList list;
QString configPath = "";
bool hasExistingConfig = false;
for (const 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 existence as
// well. ---------------------------------------------|HERE|
bool isValidConfigPath = CheckSettingsPathAvailability(path, true);
// Default behavior on Windows
list << makeAbs(QCoreApplication::applicationDirPath() + "/" + dirName);
list << makeAbs(QV2RAY_CONFIG_DIR + dirName);
list << ":/" + dirName;
// If we already found a valid config file. just simply load it...
if (hasExistingConfig)
break;
list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, dirName, QStandardPaths::LocateDirectory);
list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, dirName, QStandardPaths::LocateDirectory);
if (isValidConfigPath)
{
DEBUG("Path:", path, " is valid.");
configPath = path;
hasExistingConfig = true;
}
else
{
LOG("Path:", path, "does not contain a valid config file.");
}
}
#ifdef Q_OS_UNIX
if (qEnvironmentVariableIsSet("APPIMAGE"))
list << makeAbs(QCoreApplication::applicationDirPath() + "/../share/qv2ray/" + dirName);
if (hasExistingConfig)
{
// Use the config path found by the checks above
SetConfigDirPath(configPath);
LOG("Using ", QV2RAY_CONFIG_DIR, " as the config path.");
}
else
{
// If there's no existing config.
//
// Create new config at these dirs, these are default values for each platform.
if (useManualConfigPath)
{
configPath = manualConfigPath;
}
else
{
#if defined(Q_OS_WIN) && !defined(QV2RAY_NO_ASIDECONFIG)
configPath = currentPathConfig;
#else
configPath = configQv2ray;
if (qEnvironmentVariableIsSet("SNAP"))
list << makeAbs(qEnvironmentVariable("SNAP") + "/usr/share/qv2ray/" + dirName);
list << makeAbs("/usr/local/share/qv2ray/" + dirName);
list << makeAbs("/usr/local/lib/qv2ray/" + dirName);
list << makeAbs("/usr/share/qv2ray/" + dirName);
list << makeAbs("/usr/lib/qv2ray/" + dirName);
list << makeAbs("/lib/qv2ray/" + dirName);
#endif
}
bool hasPossibleNewLocation = QDir().mkpath(configPath) && CheckSettingsPathAvailability(configPath, false);
// Check if the dirs are write-able
if (!hasPossibleNewLocation)
{
// None of the path above can be used as a dir for storing config.
// Even the last folder failed to pass the check.
LOG("FATAL");
LOG(" ---> CANNOT find a proper place to store Qv2ray config files.");
QvMessageBoxWarn(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 + //
configFilePaths.join(NEWLINE) + NEWLINE + //
QObject::tr("It usually means you don't have the write permission to all of those locations.") + NEWLINE + //
QObject::tr("Qv2ray will now exit.")); //
return false;
}
// Found a valid config dir, with write permission, but assume no config is located in it.
LOG("Set " + configPath + " 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 has a corrupted JSON format.
//
// Otherwise Qv2ray would have loaded this config already instead of notifying to create a new config in this folder.
//
LOG("This should not occur: Qv2ray config exists but failed to load.");
QvMessageBoxWarn(nullptr, QObject::tr("Failed to initialise Qv2ray"),
QObject::tr("Failed to determine the location of config file:") + NEWLINE + //
QObject::tr("Qv2ray has found a config file, but it failed to be loaded due to some errors.") + NEWLINE + //
QObject::tr("A workaround is to remove the this file and restart Qv2ray:") + NEWLINE + //
QV2RAY_CONFIG_FILE + NEWLINE + //
QObject::tr("Qv2ray will now exit.") + NEWLINE + //
QObject::tr("Please report if you think it's a bug.")); //
return false;
}
GlobalConfig.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH));
GlobalConfig.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH));
GlobalConfig.logLevel = 3;
GlobalConfig.uiConfig.language = QLocale::system().name();
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "1.1.1.1" });
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "8.8.8.8" });
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "8.8.4.4" });
// Save initial config.
SaveGlobalSettings();
LOG("Created initial config file.");
}
if (!QDir(QV2RAY_GENERATED_DIR).exists())
{
// The dir used to generate final config file, for V2Ray interaction.
QDir().mkdir(QV2RAY_GENERATED_DIR);
LOG("Created config generation dir at: " + QV2RAY_GENERATED_DIR);
}
//
// BEGIN LOAD CONFIGURATIONS
//
{
// Load the config for upgrade, but do not parse it to the struct.
auto conf = JsonFromString(StringFromFile(QV2RAY_CONFIG_FILE));
const auto configVersion = conf["config_version"].toInt();
if (configVersion > QV2RAY_CONFIG_VERSION)
{
// Config version is larger than the current version...
// This is rare but it may happen....
QvMessageBoxWarn(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 false;
}
else if (configVersion < QV2RAY_CONFIG_VERSION)
{
// That is the config file needs to be upgraded.
conf = Qv2ray::UpgradeSettingsVersion(configVersion, QV2RAY_CONFIG_VERSION, conf);
}
// Let's save the config.
GlobalConfig.loadJson(conf);
const auto allTranslations = Qv2rayTranslator->GetAvailableLanguages();
const auto osLanguage = QLocale::system().name();
if (!allTranslations.contains(GlobalConfig.uiConfig.language))
{
// If we need to reset the language.
if (allTranslations.contains(osLanguage))
{
GlobalConfig.uiConfig.language = osLanguage;
}
else if (!allTranslations.isEmpty())
{
GlobalConfig.uiConfig.language = allTranslations.first();
}
}
if (!Qv2rayTranslator->InstallTranslation(GlobalConfig.uiConfig.language))
{
QvMessageBoxWarn(nullptr, "Translation Failed",
"Cannot load translation for " + GlobalConfig.uiConfig.language + NEWLINE + //
"English is now used." + NEWLINE + NEWLINE + //
"Please go to Preferences Window to change language or open an Issue");
GlobalConfig.uiConfig.language = "en_US";
}
SaveGlobalSettings();
return true;
}
}
Qv2rayPreInitResult Qv2rayApplicationManager::StaticPreInitialize(int argc, char **argv)
{
QString errorMessage;
QCoreApplication coreApp(argc, argv);
const auto &args = coreApp.arguments();
Qv2rayProcessArgument.version = QV2RAY_VERSION_STRING;
Qv2rayProcessArgument.fullArgs = args;
auto result = ParseCommandLine(&errorMessage, args);
LOG("Qv2ray PreInitialization: " + errorMessage);
if (result != PRE_INIT_RESULT_CONTINUE)
return result;
#ifdef Q_OS_WIN
const auto appPath = QDir::toNativeSeparators(coreApp.applicationFilePath());
const auto regPath = "HKEY_CURRENT_USER\\Software\\Classes\\" + QV2RAY_URL_SCHEME;
QSettings reg(regPath, QSettings::NativeFormat);
reg.setValue("Default", "Qv2ray");
reg.setValue("URL Protocol", "");
reg.beginGroup("DefaultIcon");
reg.setValue("Default", QString("%1,1").arg(appPath));
reg.endGroup();
reg.beginGroup("shell");
reg.beginGroup("open");
reg.beginGroup("command");
reg.setValue("Default", appPath + " %1");
#ifdef Q_OS_MAC
// macOS platform directories.
list << QDir(QCoreApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath();
#endif
return result;
}
Qv2rayPreInitResult Qv2rayApplicationManager::ParseCommandLine(QString *errorMessage, const QStringList &_argx_)
{
QStringList filteredArgs;
for (const auto &arg : _argx_)
{
#ifdef Q_OS_MACOS
if (arg.contains("-psn"))
continue;
#endif
filteredArgs << arg;
}
QCommandLineParser parser;
//
QCommandLineOption noAPIOption("noAPI", QObject::tr("Disable gRPC API subsystem"));
QCommandLineOption noPluginsOption("noPlugin", QObject::tr("Disable plugins feature"));
QCommandLineOption noScaleFactorOption("noScaleFactor", QObject::tr("Disable Qt UI scale factor"));
QCommandLineOption debugLogOption("debug", QObject::tr("Enable debug output"));
QCommandLineOption noAutoConnectionOption("noAutoConnection", QObject::tr("Do not automatically connect"));
QCommandLineOption disconnectOption("disconnect", QObject::tr("Stop current connection"));
QCommandLineOption reconnectOption("reconnect", QObject::tr("Reconnect last connection"));
QCommandLineOption exitOption("exit", QObject::tr("Exit Qv2ray"));
//
parser.setApplicationDescription(QObject::tr("Qv2ray - A cross-platform Qt frontend for V2Ray."));
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
//
parser.addOption(noAPIOption);
parser.addOption(noPluginsOption);
parser.addOption(noScaleFactorOption);
parser.addOption(debugLogOption);
parser.addOption(noAutoConnectionOption);
parser.addOption(disconnectOption);
parser.addOption(reconnectOption);
parser.addOption(exitOption);
//
auto helpOption = parser.addHelpOption();
auto versionOption = parser.addVersionOption();
if (!parser.parse(filteredArgs))
{
*errorMessage = parser.errorText();
return PRE_INIT_RESULT_CONTINUE;
}
if (parser.isSet(versionOption))
{
parser.showVersion();
return PRE_INIT_RESULT_QUIT;
}
if (parser.isSet(helpOption))
{
parser.showHelp();
return PRE_INIT_RESULT_QUIT;
}
for (const auto &arg : parser.positionalArguments())
{
if (arg.startsWith(QV2RAY_URL_SCHEME + "://"))
{
Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::QV2RAY_LINK;
Qv2rayProcessArgument.links << arg;
}
}
if (parser.isSet(exitOption))
{
DEBUG("disconnectOption is set.");
Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::EXIT;
}
if (parser.isSet(disconnectOption))
{
DEBUG("disconnectOption is set.");
Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::DISCONNECT;
}
if (parser.isSet(reconnectOption))
{
DEBUG("reconnectOption is set.");
Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::RECONNECT;
}
#define ProcessExtraStartupOptions(option) \
DEBUG("Startup Options:" A(parser.isSet(option##Option))); \
StartupOption.option = parser.isSet(option##Option);
ProcessExtraStartupOptions(noAPI);
ProcessExtraStartupOptions(debugLog);
ProcessExtraStartupOptions(noScaleFactor);
ProcessExtraStartupOptions(noAutoConnection);
ProcessExtraStartupOptions(noPlugins);
*errorMessage = "OK";
return PRE_INIT_RESULT_CONTINUE;
list.removeDuplicates();
return list;
}

View File

@ -7,17 +7,6 @@
namespace Qv2ray
{
enum Qv2rayExitCode
{
QVEXIT_NORMAL = 0,
QVEXIT_SECONDARY_INSTANCE = 0,
QVEXIT_PRE_INITIALIZE_FAIL = -1,
QVEXIT_EARLY_SETUP_FAIL = -2,
QVEXIT_CONFIG_FILE_FAIL = -3,
QVEXIT_SSL_FAIL = -4,
QVEXIT_NEW_VERSION = -5
};
enum MessageOpt
{
OK,
@ -27,21 +16,17 @@ namespace Qv2ray
Ignore
};
enum Qv2rayPreInitResult
enum Qv2rayExitReason
{
PRE_INIT_RESULT_ERROR,
PRE_INIT_RESULT_QUIT,
PRE_INIT_RESULT_CONTINUE
EXIT_NORMAL = 0,
EXIT_NEW_VERSION_TRIGGER = EXIT_NORMAL,
EXIT_SECONDARY_INSTANCE = EXIT_NORMAL,
EXIT_PREINITIALIZATION_FAILED = -1,
EXIT_PRECONDITION_FAILED = -2,
EXIT_CRASHED = -9,
};
enum Qv2raySetupStatus
{
NORMAL,
SINGLE_APPLICATION,
FAILED
};
struct Qv2rayProcessArguments
struct Qv2rayStartupArguments
{
enum Argument
{
@ -53,40 +38,45 @@ namespace Qv2ray
};
QList<Argument> arguments;
QString version;
int buildVersion;
QString data;
QList<QString> links;
QList<QString> fullArgs;
//
bool noAPI;
bool noAutoConnection;
bool debugLog;
bool noPlugins;
bool exitQv2ray;
//
QString _qvNewVersionPath;
JSONSTRUCT_REGISTER(Qv2rayProcessArguments, F(arguments, version, data, links, fullArgs))
JSONSTRUCT_REGISTER(Qv2rayStartupArguments, F(arguments, data, version, links, fullArgs, buildVersion))
};
inline Qv2rayProcessArguments Qv2rayProcessArgument;
class Qv2rayApplicationManager
class Qv2rayApplicationInterface
{
public:
Qv2ray::base::config::Qv2rayConfigObject *ConfigObject;
QString ConfigPath;
Qv2rayExitReason ExitReason;
Qv2rayStartupArguments StartupArguments;
public:
static Qv2rayPreInitResult StaticPreInitialize(int argc, char **argv);
explicit Qv2rayApplicationManager();
~Qv2rayApplicationManager();
virtual bool LocateConfiguration() final;
explicit Qv2rayApplicationInterface();
~Qv2rayApplicationInterface();
public:
virtual QStringList GetAssetsPaths(const QString &dirName) const final;
//
virtual Qv2raySetupStatus Initialize() = 0;
virtual Qv2rayExitCode RunQv2ray() = 0;
virtual bool Initialize() = 0;
virtual Qv2rayExitReason RunQv2ray() = 0;
//
virtual void MessageBoxWarn(QWidget *parent, const QString &title, const QString &text, MessageOpt button) = 0;
virtual void MessageBoxInfo(QWidget *parent, const QString &title, const QString &text, MessageOpt button) = 0;
virtual MessageOpt MessageBoxAsk(QWidget *parent, const QString &title, const QString &text, const QList<MessageOpt> &buttons) = 0;
virtual void OpenURL(const QString &url) = 0;
private:
static Qv2rayPreInitResult ParseCommandLine(QString *errorMessage, const QStringList &_argx_);
};
inline Qv2rayApplicationManager *qvApplicationInstance = nullptr;
inline Qv2rayApplicationInterface *QvCoreApplication = nullptr;
} // namespace Qv2ray
#define QvCoreApplication static_cast<Qv2rayApplicationManager *>(qvApplicationInstance)
#define GlobalConfig (*Qv2ray::qvApplicationInstance->ConfigObject)
#define GlobalConfig (*Qv2ray::QvCoreApplication->ConfigObject)

View File

@ -27,8 +27,8 @@
#define _LOG_ARG_(...) QV2RAY_LOG_PREPEND_CONTENT "[" QV_MODULE_NAME "]", __VA_ARGS__
#define LOG(...) Qv2ray::base::log_concat<QV2RAY_LOG_NORMAL>(_LOG_ARG_(__VA_ARGS__))
#define DEBUG(...) Qv2ray::base::log_concat<QV2RAY_LOG_DEBUG>(_LOG_ARG_(__VA_ARGS__))
#define LOG(...) Qv2ray::base::log_internal<QV2RAY_LOG_NORMAL>(_LOG_ARG_(__VA_ARGS__))
#define DEBUG(...) Qv2ray::base::log_internal<QV2RAY_LOG_DEBUG>(_LOG_ARG_(__VA_ARGS__))
enum QvLogType
{
@ -51,7 +51,7 @@ namespace Qv2ray::base
}
template<QvLogType t, typename... T>
inline void log_concat(T... v)
inline void log_internal(T... v)
{
((logStream << v << " "), ...);
((tempStream << v << " "), ...);

View File

@ -80,7 +80,7 @@ namespace Qv2ray::base::objects::complex
return meta;
}
explicit OutboundObjectMeta() : metaType(METAOUTBOUND_ORIGINAL){};
JSONSTRUCT_REGISTER(OutboundObjectMeta, F(metaType, displayName, connectionId, outboundTags))
JSONSTRUCT_REGISTER(OutboundObjectMeta, F(metaType, displayName, connectionId, outboundTags, chainPortAllocation))
};
inline OutboundObjectMeta make_chained_outbound(const QList<QString> &chain, const QString &tag)

View File

@ -1,27 +1 @@
#pragma once
namespace Qv2ray::base
{
struct QvStartupOptions
{
/// No API subsystem
bool noAPI;
/// Do not automatically connect to a server
bool noAutoConnection;
/// Enable Debug Log.
bool debugLog;
/// Disable Qt scale factors support.
bool noScaleFactor;
/// Disable all plugin features.
bool noPlugins;
/// Exit existing Qv2ray instance
bool exitQv2ray;
};
} // namespace Qv2ray::base
inline Qv2ray::base::QvStartupOptions StartupOption = Qv2ray::base::QvStartupOptions();

View File

@ -12,7 +12,7 @@ namespace Qv2ray::components::plugins
{
QvPluginHost::QvPluginHost(QObject *parent) : QObject(parent)
{
if (!StartupOption.noPlugins)
if (!QvCoreApplication->StartupArguments.noPlugins)
{
if (auto dir = QDir(QV2RAY_PLUGIN_SETTINGS_DIR); !dir.exists())
{
@ -30,7 +30,7 @@ namespace Qv2ray::components::plugins
{
clearPlugins();
LOG("Reloading plugin list");
for (const auto &pluginDirPath : Qv2rayAssetsPaths("plugins"))
for (const auto &pluginDirPath : QvCoreApplication->GetAssetsPaths("plugins"))
{
const QStringList entries = QDir(pluginDirPath).entryList(QDir::Files);
for (const auto &fileName : entries)

View File

@ -11,7 +11,7 @@ using namespace Qv2ray::base;
QStringList getLanguageSearchPaths()
{
// Configuration Path
QStringList list = Qv2rayAssetsPaths("lang");
QStringList list = QvCoreApplication->GetAssetsPaths("lang");
#ifdef QV2RAY_EMBED_TRANSLATIONS
// If the translations have been embedded.
list << QString(":/translations/");

View File

@ -19,18 +19,6 @@ namespace Qv2ray::core::kernel
{ API_OUTBOUND_DIRECT, { "freedom" } },
{ API_OUTBOUND_BLACKHOLE, { "blackhole" } } };
// To all contributors:
//
// You may feel it difficult to understand this part of API backend.
// It's been expected that you will take hours to fully understand the tricks and hacks lying deeply in this class.
//
// The API Worker runs as a daemon together with Qv2ray, on a single thread.
// They use a flag, running, to indicate if the API worker should go and fetch the statistics from V2Ray Core.
//
// The flag, running, will be set to true, immediately after the V2Ray core reported that it's been started.
// and will be set to false right before we stopping V2Ray Core.
//
APIWorker::APIWorker()
{
workThread = new QThread();

View File

@ -286,12 +286,11 @@ namespace Qv2ray::core::kernel
if (ValidateConfig(filePath))
{
QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
auto env = QProcessEnvironment::systemEnvironment();
env.insert("V2RAY_LOCATION_ASSET", GlobalConfig.kernelConfig.AssetsPath());
vProcess->setProcessEnvironment(env);
vProcess->start(GlobalConfig.kernelConfig.KernelPath(), { "-config", filePath }, QIODevice::ReadWrite | QIODevice::Text);
vProcess->waitForStarted();
DEBUG("V2Ray core started.");
KernelStarted = true;
QMap<bool, QMap<QString, QString>> tagProtocolMap;
@ -312,7 +311,7 @@ namespace Qv2ray::core::kernel
}
apiEnabled = false;
if (StartupOption.noAPI)
if (QvCoreApplication->StartupArguments.noAPI)
{
LOG("API has been disabled by the command line arguments");
}

View File

@ -1,8 +1,10 @@
#include "SettingsBackend.hpp"
#include "base/Qv2rayLog.hpp"
#include "utils/QvHelpers.hpp"
#define QV_MODULE_NAME "SettingsBackend"
constexpr auto QV2RAY_CONFIG_PATH_ENV_NAME = "QV2RAY_CONFIG_PATH";
namespace Qv2ray::core::config
{
@ -14,11 +16,11 @@ namespace Qv2ray::core::config
void SetConfigDirPath(const QString &path)
{
qvApplicationInstance->ConfigPath = path;
QvCoreApplication->ConfigPath = path;
if (!path.endsWith("/"))
{
qvApplicationInstance->ConfigPath += "/";
QvCoreApplication->ConfigPath += "/";
}
}
@ -90,6 +92,177 @@ namespace Qv2ray::core::config
return true;
}
bool LocateConfiguration()
{
LOG("Application exec path: " + qApp->applicationDirPath());
// Non-standard paths needs special handing for "_debug"
const auto currentPathConfig = qApp->applicationDirPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX;
const auto homeQv2ray = QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX;
//
// Standard paths already handles the "_debug" suffix for us.
const auto configQv2ray = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
//
//
// Some built-in search paths for Qv2ray to find configs. (load the first one if possible).
const auto useManualConfigPath = qEnvironmentVariableIsSet(QV2RAY_CONFIG_PATH_ENV_NAME);
const auto manualConfigPath = qEnvironmentVariable(QV2RAY_CONFIG_PATH_ENV_NAME);
//
QStringList configFilePaths;
if (useManualConfigPath)
{
LOG("Using config path from env: " + manualConfigPath);
configFilePaths << manualConfigPath;
}
else
{
configFilePaths << currentPathConfig;
configFilePaths << configQv2ray;
configFilePaths << homeQv2ray;
}
QString configPath = "";
bool hasExistingConfig = false;
for (const 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 existence as
// well. ---------------------------------------------|HERE|
bool isValidConfigPath = CheckSettingsPathAvailability(path, true);
// If we already found a valid config file. just simply load it...
if (hasExistingConfig)
break;
if (isValidConfigPath)
{
DEBUG("Path:", path, " is valid.");
configPath = path;
hasExistingConfig = true;
}
else
{
LOG("Path:", path, "does not contain a valid config file.");
}
}
if (hasExistingConfig)
{
// Use the config path found by the checks above
SetConfigDirPath(configPath);
LOG("Using ", QV2RAY_CONFIG_DIR, " as the config path.");
}
else
{
// If there's no existing config.
//
// Create new config at these dirs, these are default values for each platform.
if (useManualConfigPath)
{
configPath = manualConfigPath;
}
else
{
#if defined(Q_OS_WIN) && !defined(QV2RAY_NO_ASIDECONFIG)
configPath = currentPathConfig;
#else
configPath = configQv2ray;
#endif
}
bool hasPossibleNewLocation = QDir().mkpath(configPath) && CheckSettingsPathAvailability(configPath, false);
// Check if the dirs are write-able
if (!hasPossibleNewLocation)
{
// None of the path above can be used as a dir for storing config.
// Even the last folder failed to pass the check.
LOG("FATAL");
LOG(" ---> CANNOT find a proper place to store Qv2ray config files.");
QvMessageBoxWarn(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 + //
configFilePaths.join(NEWLINE) + NEWLINE + //
QObject::tr("It usually means you don't have the write permission to all of those locations.") + NEWLINE + //
QObject::tr("Qv2ray will now exit.")); //
return false;
}
// Found a valid config dir, with write permission, but assume no config is located in it.
LOG("Set " + configPath + " 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 has a corrupted JSON format.
//
// Otherwise Qv2ray would have loaded this config already instead of notifying to create a new config in this folder.
//
LOG("This should not occur: Qv2ray config exists but failed to load.");
QvMessageBoxWarn(nullptr, QObject::tr("Failed to initialise Qv2ray"),
QObject::tr("Failed to determine the location of config file:") + NEWLINE + //
QObject::tr("Qv2ray has found a config file, but it failed to be loaded due to some errors.") + NEWLINE + //
QObject::tr("A workaround is to remove the this file and restart Qv2ray:") + NEWLINE + //
QV2RAY_CONFIG_FILE + NEWLINE + //
QObject::tr("Qv2ray will now exit.") + NEWLINE + //
QObject::tr("Please report if you think it's a bug.")); //
return false;
}
GlobalConfig.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH));
GlobalConfig.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH));
GlobalConfig.logLevel = 3;
GlobalConfig.uiConfig.language = QLocale::system().name();
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "1.1.1.1" });
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "8.8.8.8" });
GlobalConfig.defaultRouteConfig.dnsConfig.servers.append({ "8.8.4.4" });
// Save initial config.
SaveGlobalSettings();
LOG("Created initial config file.");
}
if (!QDir(QV2RAY_GENERATED_DIR).exists())
{
// The dir used to generate final config file, for V2Ray interaction.
QDir().mkdir(QV2RAY_GENERATED_DIR);
LOG("Created config generation dir at: " + QV2RAY_GENERATED_DIR);
}
//
// BEGIN LOAD CONFIGURATIONS
//
{
// Load the config for upgrade, but do not parse it to the struct.
auto conf = JsonFromString(StringFromFile(QV2RAY_CONFIG_FILE));
const auto configVersion = conf["config_version"].toInt();
if (configVersion > QV2RAY_CONFIG_VERSION)
{
// Config version is larger than the current version...
// This is rare but it may happen....
QvMessageBoxWarn(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 false;
}
else if (configVersion < QV2RAY_CONFIG_VERSION)
{
// That is the config file needs to be upgraded.
conf = Qv2ray::UpgradeSettingsVersion(configVersion, QV2RAY_CONFIG_VERSION, conf);
}
// Let's save the config.
GlobalConfig.loadJson(conf);
SaveGlobalSettings();
return true;
}
}
} // namespace Qv2ray::core::config
using namespace Qv2ray::core::config;

View File

@ -4,6 +4,7 @@
namespace Qv2ray::core::config
{
void SaveGlobalSettings();
bool LocateConfiguration();
void SetConfigDirPath(const QString &path);
bool CheckSettingsPathAvailability(const QString &_path, bool checkExistingConfig);
} // namespace Qv2ray::core::config

View File

@ -24,6 +24,9 @@
#define QV_MODULE_NAME "Init"
#define EXITCODE_ERROR 1;
#define EXITCODE_NORMAL 0
int globalArgc;
char **globalArgv;
@ -177,6 +180,30 @@ LONG WINAPI TopLevelExceptionHandler(PEXCEPTION_POINTERS)
}
#endif
void ProcessExitReason(Qv2rayExitReason reason)
{ // app.MessageBoxWarn(nullptr, app.tr("Cannot start Qv2ray"), app.tr("Qv2ray early initialization failed."));
// return QVEXIT_EARLY_SETUP_FAIL;
switch (reason)
{
case EXIT_CRASHED:
{
break;
}
case EXIT_NORMAL:
{
break;
}
case EXIT_PREINITIALIZATION_FAILED:
{
break;
}
case EXIT_PRECONDITION_FAILED:
{
break;
}
}
}
int main(int argc, char *argv[])
{
globalArgc = argc;
@ -195,44 +222,20 @@ int main(int argc, char *argv[])
// This line must be called before any other ones, since we are using these
// values to identify instances.
QCoreApplication::setApplicationVersion(QV2RAY_VERSION_STRING);
//
#ifdef QT_DEBUG
QCoreApplication::setApplicationName("qv2ray_debug");
// QApplication::setApplicationDisplayName("Qv2ray - " + QObject::tr("Debug version"));
#else
QCoreApplication::setApplicationName("qv2ray");
#ifdef QV2RAY_GUI
QApplication::setApplicationDisplayName("Qv2ray");
#endif
#endif
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-2020 Qv2ray Development Group." NEWLINE //
"Third-party libraries that have been used in Qv2ray can be found in the About page." NEWLINE);
#ifdef QT_DEBUG
std::cerr << "WARNING: ================ This is a debug build, many features are not stable enough. ================" << std::endl;
#endif
// parse the command line before starting as a Qt application
switch (Qv2rayApplicationManager::StaticPreInitialize(argc, argv))
{
case PRE_INIT_RESULT_QUIT: return QVEXIT_NORMAL;
case PRE_INIT_RESULT_CONTINUE: break;
case PRE_INIT_RESULT_ERROR:
{
BootstrapMessageBox("Cannot Start Qv2ray!", "Early initialization failed!");
return QVEXIT_PRE_INITIALIZE_FAIL;
}
default: Q_UNREACHABLE();
}
#ifndef QV2RAY_QT6
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
#endif
// noScaleFactors = disable HiDPI
if (StartupOption.noScaleFactor)
if (qEnvironmentVariableIsSet("QV2RAY_NO_SCALE_FACTORS"))
{
LOG("Force set QT_SCALE_FACTOR to 1.");
DEBUG("UI", "Original QT_SCALE_FACTOR was:", qEnvironmentVariable("QT_SCALE_FACTOR"));
@ -251,6 +254,10 @@ int main(int argc, char *argv[])
#endif
}
#ifndef QV2RAY_QT6
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps, true);
#endif
// Check OpenSSL version for auto-update and subscriptions
auto osslReqVersion = QSslSocket::sslLibraryBuildVersionString();
auto osslCurVersion = QSslSocket::sslLibraryVersionString();
@ -268,28 +275,14 @@ int main(int argc, char *argv[])
"OSsl.Rq.V=" + osslReqVersion + NEWLINE + //
"OSsl.Cr.V=" + osslCurVersion);
BootstrapMessageBox(QObject::tr("Cannot start Qv2ray"), QObject::tr("Cannot start Qv2ray without OpenSSL"));
return QVEXIT_SSL_FAIL;
return -1;
}
Qv2rayApplication app(argc, argv);
switch (app.Initialize())
if (!app.Initialize())
{
case NORMAL: break;
case SINGLE_APPLICATION: return QVEXIT_SECONDARY_INSTANCE;
case FAILED:
{
app.MessageBoxWarn(nullptr, app.tr("Cannot start Qv2ray"), app.tr("Qv2ray early initialization failed."));
return QVEXIT_EARLY_SETUP_FAIL;
}
}
//
// Qv2ray Initialize, find possible config paths and verify them.
if (!app.LocateConfiguration())
{
LOG("Cannot load or create initial configuration file.");
app.MessageBoxWarn(nullptr, app.tr("Cannot start Qv2ray"), app.tr("Cannot load config file."));
return QVEXIT_CONFIG_FILE_FAIL;
LOG("Qv2ray initialization failed:", app.ExitReason);
ProcessExitReason(app.ExitReason);
}
#ifndef Q_OS_WIN
@ -297,11 +290,11 @@ int main(int argc, char *argv[])
signal(SIGUSR2, [](int) { ConnectionManager->StopConnection(); });
#endif
const auto rcode = app.RunQv2ray();
if (rcode == QVEXIT_NEW_VERSION)
app.RunQv2ray();
if (app.ExitReason == EXIT_NEW_VERSION_TRIGGER)
{
LOG("Starting new version of Qv2ray: " + Qv2rayProcessArgument._qvNewVersionPath);
QProcess::startDetached(Qv2rayProcessArgument._qvNewVersionPath, {});
LOG("Starting new version of Qv2ray: " + app.StartupArguments._qvNewVersionPath);
QProcess::startDetached(app.StartupArguments._qvNewVersionPath, {});
}
return rcode;
return app.ExitReason;
}

View File

@ -0,0 +1,221 @@
#include "Qv2rayPlatformApplication.hpp"
#include "core/settings/SettingsBackend.hpp"
#define QV_MODULE_NAME "PlatformApplication"
#ifdef QT_DEBUG
const static inline QString QV2RAY_URL_SCHEME = "qv2ray-debug";
#else
const static inline QString QV2RAY_URL_SCHEME = "qv2ray";
#endif
bool Qv2rayPlatformApplication::initializeInternal()
{
QString errorMessage;
bool canContinue;
auto hasError = parseCommandLine(&errorMessage, &canContinue);
if (hasError && !canContinue)
return false;
#ifdef Q_OS_WIN
const auto appPath = QDir::toNativeSeparators(applicationFilePath());
const auto regPath = "HKEY_CURRENT_USER\\Software\\Classes\\" + QV2RAY_URL_SCHEME;
QSettings reg(regPath, QSettings::NativeFormat);
reg.setValue("Default", "Qv2ray");
reg.setValue("URL Protocol", "");
reg.beginGroup("DefaultIcon");
reg.setValue("Default", QString("%1,1").arg(appPath));
reg.endGroup();
reg.beginGroup("shell");
reg.beginGroup("open");
reg.beginGroup("command");
reg.setValue("Default", appPath + " %1");
#endif
connect(this, &Qv2rayPlatformApplication::aboutToQuit, this, &Qv2rayPlatformApplication::quitInternal);
#ifndef QV2RAY_NO_SINGLEAPPLICATON
connect(this, &SingleApplication::receivedMessage, this, &Qv2rayPlatformApplication::onMessageReceived, Qt::QueuedConnection);
if (isSecondary())
{
StartupArguments.version = QV2RAY_VERSION_STRING;
StartupArguments.fullArgs = arguments();
if (StartupArguments.arguments.isEmpty())
StartupArguments.arguments << Qv2rayStartupArguments::NORMAL;
bool status = sendMessage(JsonToString(StartupArguments.toJson(), QJsonDocument::Compact).toUtf8());
if (!status)
LOG("Cannot send message.");
return true;
}
#endif
#ifdef QV2RAY_GUI
#ifdef Q_OS_LINUX
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
setFallbackSessionManagementEnabled(false);
#endif
connect(this, &QGuiApplication::commitDataRequest, [] {
RouteManager->SaveRoutes();
ConnectionManager->SaveConnectionConfig();
PluginHost->SavePluginSettings();
SaveGlobalSettings();
});
#endif
#ifdef Q_OS_WIN
SetCurrentDirectory(applicationDirPath().toStdWString().c_str());
// Set special font in Windows
QFont font;
font.setPointSize(9);
font.setFamily("Microsoft YaHei");
setFont(font);
#endif
#endif
// Install a default translater. From the OS/DE
Qv2rayTranslator = std::make_unique<QvTranslator>();
Qv2rayTranslator->InstallTranslation(QLocale::system().name());
//
//
LocateConfiguration();
//
const auto allTranslations = Qv2rayTranslator->GetAvailableLanguages();
const auto osLanguage = QLocale::system().name();
if (!allTranslations.contains(GlobalConfig.uiConfig.language))
{
// If we need to reset the language.
if (allTranslations.contains(osLanguage))
{
GlobalConfig.uiConfig.language = osLanguage;
}
else if (!allTranslations.isEmpty())
{
GlobalConfig.uiConfig.language = allTranslations.first();
}
}
if (!Qv2rayTranslator->InstallTranslation(GlobalConfig.uiConfig.language))
{
QvMessageBoxWarn(nullptr, "Translation Failed",
"Cannot load translation for " + GlobalConfig.uiConfig.language + NEWLINE + //
"English is now used." + NEWLINE + NEWLINE + //
"Please go to Preferences Window to change language or open an Issue");
GlobalConfig.uiConfig.language = "en_US";
}
return true;
}
bool Qv2rayPlatformApplication::runInternal()
{
PluginHost = new QvPluginHost();
RouteManager = new RouteHandler();
ConnectionManager = new QvConfigHandler();
return true;
}
void Qv2rayPlatformApplication::quitInternal()
{
// Do not change the order.
ConnectionManager->StopConnection();
RouteManager->SaveRoutes();
ConnectionManager->SaveConnectionConfig();
PluginHost->SavePluginSettings();
SaveGlobalSettings();
TerminateUI();
delete ConnectionManager;
delete RouteManager;
delete PluginHost;
ConnectionManager = nullptr;
RouteManager = nullptr;
PluginHost = nullptr;
}
bool Qv2rayPlatformApplication::parseCommandLine(QString *errorMessage, bool *canContinue)
{
*canContinue = true;
QStringList filteredArgs;
for (const auto &arg : arguments())
{
#ifdef Q_OS_MACOS
if (arg.contains("-psn"))
continue;
#endif
filteredArgs << arg;
}
QCommandLineParser parser;
//
QCommandLineOption noAPIOption("noAPI", QObject::tr("Disable gRPC API subsystem"));
QCommandLineOption noPluginsOption("noPlugin", QObject::tr("Disable plugins feature"));
QCommandLineOption debugLogOption("debug", QObject::tr("Enable debug output"));
QCommandLineOption noAutoConnectionOption("noAutoConnection", QObject::tr("Do not automatically connect"));
QCommandLineOption disconnectOption("disconnect", QObject::tr("Stop current connection"));
QCommandLineOption reconnectOption("reconnect", QObject::tr("Reconnect last connection"));
QCommandLineOption exitOption("exit", QObject::tr("Exit Qv2ray"));
//
parser.setApplicationDescription(QObject::tr("Qv2ray - A cross-platform Qt frontend for V2Ray."));
parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions);
//
parser.addOption(noAPIOption);
parser.addOption(noPluginsOption);
parser.addOption(debugLogOption);
parser.addOption(noAutoConnectionOption);
parser.addOption(disconnectOption);
parser.addOption(reconnectOption);
parser.addOption(exitOption);
//
const auto helpOption = parser.addHelpOption();
const auto versionOption = parser.addVersionOption();
if (!parser.parse(filteredArgs))
{
*canContinue = true;
*errorMessage = parser.errorText();
return false;
}
if (parser.isSet(versionOption))
{
parser.showVersion();
return true;
}
if (parser.isSet(helpOption))
{
parser.showHelp();
return true;
}
for (const auto &arg : parser.positionalArguments())
{
if (arg.startsWith(QV2RAY_URL_SCHEME + "://"))
{
StartupArguments.arguments << Qv2rayStartupArguments::QV2RAY_LINK;
StartupArguments.links << arg;
}
}
if (parser.isSet(exitOption))
{
DEBUG("disconnectOption is set.");
StartupArguments.arguments << Qv2rayStartupArguments::EXIT;
}
if (parser.isSet(disconnectOption))
{
DEBUG("disconnectOption is set.");
StartupArguments.arguments << Qv2rayStartupArguments::DISCONNECT;
}
if (parser.isSet(reconnectOption))
{
DEBUG("reconnectOption is set.");
StartupArguments.arguments << Qv2rayStartupArguments::RECONNECT;
}
#define ProcessExtraStartupOptions(option) \
DEBUG("Startup Options:" A(parser.isSet(option##Option))); \
StartupArguments.option = parser.isSet(option##Option);
ProcessExtraStartupOptions(noAPI);
ProcessExtraStartupOptions(debugLog);
ProcessExtraStartupOptions(noAutoConnection);
ProcessExtraStartupOptions(noPlugins);
return true;
}

View File

@ -23,7 +23,6 @@
#ifdef QV2RAY_GUI
#include <QApplication>
#include <QFont>
#include <QMessageBox>
const static inline QMap<MessageOpt, QMessageBox::StandardButton> MessageBoxButtonMap //
= { { No, QMessageBox::No },
@ -37,97 +36,36 @@ const static inline QMap<MessageOpt, QMessageBox::StandardButton> MessageBoxButt
#ifndef QV2RAY_NO_SINGLEAPPLICATON
#include <SingleApplication>
#define QV2RAY_BASE_APPLICATION_CLASS SingleApplication
#define QV2RAY_BASE_CLASS_CONSTRUCTOR_ARGS argc, argv, true, User | ExcludeAppPath | ExcludeAppVersion
#define QVBASEAPPLICATION SingleApplication
#define QVBASEAPPLICATION_CTORARGS argc, argv, true, User | ExcludeAppPath | ExcludeAppVersion
#else
#define QV2RAY_BASE_APPLICATION_CLASS QAPPLICATION_CLASS
#define QV2RAY_BASE_CLASS_CONSTRUCTOR_ARGS argc, argv
#define QVBASEAPPLICATION QAPPLICATION_CLASS
#define QVBASEAPPLICATION_CTORARGS argc, argv
#endif
class Qv2rayPlatformApplication
: public QV2RAY_BASE_APPLICATION_CLASS
, public Qv2rayApplicationManager
: public QVBASEAPPLICATION
, public Qv2rayApplicationInterface
{
Q_OBJECT
public:
Qv2rayPlatformApplication(int &argc, char *argv[])
: QV2RAY_BASE_APPLICATION_CLASS(QV2RAY_BASE_CLASS_CONSTRUCTOR_ARGS), Qv2rayApplicationManager(){};
Qv2rayPlatformApplication(int &argc, char *argv[]) : QVBASEAPPLICATION(QVBASEAPPLICATION_CTORARGS), Qv2rayApplicationInterface(){};
virtual ~Qv2rayPlatformApplication(){};
void QuitApplication(int retCode = 0)
inline void QuitApplication(int retCode = 0)
{
QCoreApplication::exit(retCode);
}
protected:
Qv2raySetupStatus InitializeInternal()
{
connect(this, &Qv2rayPlatformApplication::aboutToQuit, this, &Qv2rayPlatformApplication::QuitInternal);
#ifndef QV2RAY_NO_SINGLEAPPLICATON
connect(this, &SingleApplication::receivedMessage, this, &Qv2rayPlatformApplication::onMessageReceived, Qt::QueuedConnection);
if (isSecondary())
{
if (Qv2rayProcessArgument.arguments.isEmpty())
Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::NORMAL;
sendMessage(JsonToString(Qv2rayProcessArgument.toJson(), QJsonDocument::Compact).toUtf8());
return SINGLE_APPLICATION;
}
#endif
#ifdef QV2RAY_GUI
#ifdef Q_OS_LINUX
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
setFallbackSessionManagementEnabled(false);
#endif
connect(this, &QGuiApplication::commitDataRequest, [] {
RouteManager->SaveRoutes();
ConnectionManager->SaveConnectionConfig();
PluginHost->SavePluginSettings();
SaveGlobalSettings();
});
#endif
#ifdef Q_OS_WIN
SetCurrentDirectory(applicationDirPath().toStdWString().c_str());
// Set special font in Windows
QFont font;
font.setPointSize(9);
font.setFamily("Microsoft YaHei");
setFont(font);
#endif
#endif
// Install a default translater. From the OS/DE
Qv2rayTranslator = std::make_unique<QvTranslator>();
Qv2rayTranslator->InstallTranslation(QLocale::system().name());
return NORMAL;
}
bool RunInternal()
{
PluginHost = new QvPluginHost();
RouteManager = new RouteHandler();
ConnectionManager = new QvConfigHandler();
return true;
}
void QuitInternal()
{
// Do not change the order.
ConnectionManager->StopConnection();
RouteManager->SaveRoutes();
ConnectionManager->SaveConnectionConfig();
PluginHost->SavePluginSettings();
SaveGlobalSettings();
TerminateUI();
delete ConnectionManager;
delete RouteManager;
delete PluginHost;
ConnectionManager = nullptr;
RouteManager = nullptr;
PluginHost = nullptr;
}
bool initializeInternal();
bool runInternal();
void quitInternal();
virtual void TerminateUI() = 0;
#ifndef QV2RAY_NO_SINGLEAPPLICATON
virtual void onMessageReceived(quint32 clientId, QByteArray msg) = 0;
#endif
private:
bool parseCommandLine(QString *errorMessage, bool *canContinue);
};

View File

@ -1,6 +1,5 @@
#include "Qv2rayWidgetApplication.hpp"
#include "3rdparty/libsemver/version.hpp"
#include "base/Qv2rayBase.hpp"
#include "components/translations/QvTranslator.hpp"
#include "core/settings/SettingsBackend.hpp"
@ -22,195 +21,192 @@
constexpr auto QV2RAY_WIDGETUI_STATE_FILENAME = "UIState.json";
namespace Qv2ray
Qv2rayWidgetApplication::Qv2rayWidgetApplication(int &argc, char *argv[]) : Qv2rayPlatformApplication(argc, argv)
{
Qv2rayWidgetApplication::Qv2rayWidgetApplication(int &argc, char *argv[]) : Qv2rayPlatformApplication(argc, argv)
{
}
}
Qv2raySetupStatus Qv2rayWidgetApplication::Initialize()
{
const auto result = InitializeInternal();
if (result != NORMAL)
return result;
setQuitOnLastWindowClosed(false);
hTray = new QSystemTrayIcon();
return NORMAL;
}
bool Qv2rayWidgetApplication::Initialize()
{
const auto result = initializeInternal();
if (!result)
return false;
setQuitOnLastWindowClosed(false);
hTray = new QSystemTrayIcon();
return true;
}
void Qv2rayWidgetApplication::TerminateUI()
{
delete mainWindow;
delete hTray;
delete StyleManager;
StringToFile(JsonToString(UIStates), QV2RAY_CONFIG_DIR + QV2RAY_WIDGETUI_STATE_FILENAME);
}
void Qv2rayWidgetApplication::TerminateUI()
{
delete mainWindow;
delete hTray;
delete StyleManager;
StringToFile(JsonToString(UIStates), QV2RAY_CONFIG_DIR + QV2RAY_WIDGETUI_STATE_FILENAME);
}
#ifndef QV2RAY_NO_SINGLEAPPLICATON
void Qv2rayWidgetApplication::onMessageReceived(quint32 clientId, QByteArray _msg)
void Qv2rayWidgetApplication::onMessageReceived(quint32 clientId, QByteArray _msg)
{
// Sometimes SingleApplication will send message with clientId == 0, ignore them.
if (clientId == instanceId())
return;
if (!isInitialized)
return;
const auto msg = Qv2rayStartupArguments::fromJson(JsonFromString(_msg));
LOG("Client ID:", clientId, ", message received, version:", msg.buildVersion);
DEBUG(_msg);
//
if (msg.buildVersion > QV2RAY_VERSION_BUILD)
{
// Sometimes SingleApplication will send message with clientId == 0, ignore them.
if (clientId == instanceId())
return;
const auto msg = Qv2rayProcessArguments::fromJson(JsonFromString(_msg));
LOG("Client ID:", clientId, ", message received, version:", msg.version);
DEBUG(_msg);
//
const auto currentVersion = semver::version::from_string(QV2RAY_VERSION_STRING);
const auto newVersionString = msg.version.isEmpty() ? "0.0.0" : msg.version.toStdString();
const auto newVersion = semver::version::from_string(newVersionString);
//
if (newVersion > currentVersion)
{
const auto newPath = msg.fullArgs.first();
QString message;
message += tr("A new version of Qv2ray is starting:") + NEWLINE;
message += NEWLINE;
message += tr("New version information: ") + NEWLINE;
message += tr("Qv2ray version: %1").arg(msg.version) + NEWLINE;
message += tr("Qv2ray path: %1").arg(newPath) + NEWLINE;
message += NEWLINE;
message += tr("Do you want to exit and launch that new version?");
const auto newPath = msg.fullArgs.first();
QString message;
message += tr("A new version of Qv2ray is starting:") + NEWLINE;
message += NEWLINE;
message += tr("New version information: ") + NEWLINE;
message += tr("Version: %1:%2").arg(msg.version).arg(msg.buildVersion) + NEWLINE;
message += tr("Path: %1").arg(newPath) + NEWLINE;
message += NEWLINE;
message += tr("Do you want to exit and launch that new version?");
const auto result = QvMessageBoxAsk(nullptr, tr("New version detected"), message);
if (result == Yes)
{
Qv2rayProcessArgument._qvNewVersionPath = newPath;
QuitApplication(QVEXIT_NEW_VERSION);
}
const auto result = QvMessageBoxAsk(nullptr, tr("New version detected"), message);
if (result == Yes)
{
StartupArguments._qvNewVersionPath = newPath;
QuitApplication(EXIT_NEW_VERSION_TRIGGER);
}
}
for (const auto &argument : msg.arguments)
for (const auto &argument : msg.arguments)
{
switch (argument)
{
switch (argument)
case Qv2rayStartupArguments::EXIT:
{
case Qv2rayProcessArguments::EXIT:
QuitApplication();
break;
}
case Qv2rayStartupArguments::NORMAL:
{
mainWindow->show();
mainWindow->raise();
mainWindow->activateWindow();
break;
}
case Qv2rayStartupArguments::RECONNECT:
{
ConnectionManager->RestartConnection();
break;
}
case Qv2rayStartupArguments::DISCONNECT:
{
ConnectionManager->StopConnection();
break;
}
case Qv2rayStartupArguments::QV2RAY_LINK:
{
for (const auto &link : msg.links)
{
QuitApplication();
break;
}
case Qv2rayProcessArguments::NORMAL:
{
mainWindow->show();
mainWindow->raise();
mainWindow->activateWindow();
break;
}
case Qv2rayProcessArguments::RECONNECT:
{
ConnectionManager->RestartConnection();
break;
}
case Qv2rayProcessArguments::DISCONNECT:
{
ConnectionManager->StopConnection();
break;
}
case Qv2rayProcessArguments::QV2RAY_LINK:
{
for (const auto &link : msg.links)
const auto url = QUrl::fromUserInput(link);
const auto command = url.host();
auto subcommands = url.path().split("/");
subcommands.removeAll("");
QMap<QString, QString> args;
for (const auto &kvp : QUrlQuery(url).queryItems())
{
const auto url = QUrl::fromUserInput(link);
const auto command = url.host();
auto subcommands = url.path().split("/");
subcommands.removeAll("");
QMap<QString, QString> args;
for (const auto &kvp : QUrlQuery(url).queryItems())
{
args.insert(kvp.first, kvp.second);
}
if (command == "open")
{
emit mainWindow->ProcessCommand(command, subcommands, args);
}
args.insert(kvp.first, kvp.second);
}
if (command == "open")
{
emit mainWindow->ProcessCommand(command, subcommands, args);
}
break;
}
break;
}
}
}
}
#endif
Qv2rayExitCode Qv2rayWidgetApplication::RunQv2ray()
Qv2rayExitReason Qv2rayWidgetApplication::RunQv2ray()
{
runInternal();
StyleManager = new QvStyleManager();
StyleManager->ApplyStyle(GlobalConfig.uiConfig.theme);
// Show MainWindow
UIStates = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + QV2RAY_WIDGETUI_STATE_FILENAME));
mainWindow = new MainWindow();
if (StartupArguments.arguments.contains(Qv2rayStartupArguments::QV2RAY_LINK))
{
RunInternal();
StyleManager = new QvStyleManager();
StyleManager->ApplyStyle(GlobalConfig.uiConfig.theme);
// Show MainWindow
UIStates = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + QV2RAY_WIDGETUI_STATE_FILENAME));
mainWindow = new MainWindow();
if (Qv2rayProcessArgument.arguments.contains(Qv2rayProcessArguments::QV2RAY_LINK))
for (const auto &link : StartupArguments.links)
{
for (const auto &link : Qv2rayProcessArgument.links)
const auto url = QUrl::fromUserInput(link);
const auto command = url.host();
auto subcommands = url.path().split("/");
subcommands.removeAll("");
QMap<QString, QString> args;
for (const auto &kvp : QUrlQuery(url).queryItems())
{
const auto url = QUrl::fromUserInput(link);
const auto command = url.host();
auto subcommands = url.path().split("/");
subcommands.removeAll("");
QMap<QString, QString> args;
for (const auto &kvp : QUrlQuery(url).queryItems())
{
args.insert(kvp.first, kvp.second);
}
if (command == "open")
{
emit mainWindow->ProcessCommand(command, subcommands, args);
}
args.insert(kvp.first, kvp.second);
}
if (command == "open")
{
emit mainWindow->ProcessCommand(command, subcommands, args);
}
}
}
#ifdef Q_OS_MACOS
connect(this, &QApplication::applicationStateChanged, [this](Qt::ApplicationState state) {
switch (state)
{
case Qt::ApplicationActive:
{
mainWindow->show();
mainWindow->raise();
mainWindow->activateWindow();
break;
}
case Qt::ApplicationHidden:
case Qt::ApplicationInactive:
case Qt::ApplicationSuspended: break;
}
});
#endif
return (Qv2rayExitCode) exec();
}
void Qv2rayWidgetApplication::OpenURL(const QString &url)
{
QDesktopServices::openUrl(url);
}
void Qv2rayWidgetApplication::MessageBoxWarn(QWidget *parent, const QString &title, const QString &text, MessageOpt button)
{
QMessageBox::warning(parent, title, text, MessageBoxButtonMap[button]);
}
void Qv2rayWidgetApplication::MessageBoxInfo(QWidget *parent, const QString &title, const QString &text, MessageOpt button)
{
QMessageBox::information(parent, title, text, MessageBoxButtonMap[button]);
}
MessageOpt Qv2rayWidgetApplication::MessageBoxAsk(QWidget *parent, const QString &title, const QString &text, const QList<MessageOpt> &buttons)
{
QFlags<QMessageBox::StandardButton> btns;
for (const auto &b : buttons)
connect(this, &QApplication::applicationStateChanged, [this](Qt::ApplicationState state) {
switch (state)
{
btns.setFlag(MessageBoxButtonMap[b]);
case Qt::ApplicationActive:
{
mainWindow->show();
mainWindow->raise();
mainWindow->activateWindow();
break;
}
case Qt::ApplicationHidden:
case Qt::ApplicationInactive:
case Qt::ApplicationSuspended: break;
}
return MessageBoxButtonMap.key(QMessageBox::question(parent, title, text, btns));
}
});
#endif
isInitialized = true;
return (Qv2rayExitReason) exec();
}
void Qv2rayWidgetApplication::ShowTrayMessage(const QString &m, const QIcon &icon, int msecs)
void Qv2rayWidgetApplication::OpenURL(const QString &url)
{
QDesktopServices::openUrl(url);
}
void Qv2rayWidgetApplication::MessageBoxWarn(QWidget *parent, const QString &title, const QString &text, MessageOpt button)
{
QMessageBox::warning(parent, title, text, MessageBoxButtonMap[button]);
}
void Qv2rayWidgetApplication::MessageBoxInfo(QWidget *parent, const QString &title, const QString &text, MessageOpt button)
{
QMessageBox::information(parent, title, text, MessageBoxButtonMap[button]);
}
MessageOpt Qv2rayWidgetApplication::MessageBoxAsk(QWidget *parent, const QString &title, const QString &text, const QList<MessageOpt> &buttons)
{
QFlags<QMessageBox::StandardButton> btns;
for (const auto &b : buttons)
{
hTray->showMessage("Qv2ray", m, icon, msecs);
btns.setFlag(MessageBoxButtonMap[b]);
}
return MessageBoxButtonMap.key(QMessageBox::question(parent, title, text, btns));
}
void Qv2rayWidgetApplication::ShowTrayMessage(const QString &m, QSystemTrayIcon::MessageIcon icon, int msecs)
{
hTray->showMessage("Qv2ray", m, icon, msecs);
}
void Qv2rayWidgetApplication::ShowTrayMessage(const QString &m, const QIcon &icon, int msecs)
{
hTray->showMessage("Qv2ray", m, icon, msecs);
}
} // namespace Qv2ray
void Qv2rayWidgetApplication::ShowTrayMessage(const QString &m, QSystemTrayIcon::MessageIcon icon, int msecs)
{
hTray->showMessage("Qv2ray", m, icon, msecs);
}

View File

@ -13,8 +13,8 @@ namespace Qv2ray
Q_OBJECT
public:
explicit Qv2rayWidgetApplication(int &argc, char *argv[]);
Qv2raySetupStatus Initialize() override;
Qv2rayExitCode RunQv2ray() override;
bool Initialize() override;
Qv2rayExitReason RunQv2ray() override;
QJsonObject UIStates;
public:
@ -31,6 +31,7 @@ namespace Qv2ray
}
private:
bool isInitialized;
void TerminateUI() override;
#ifndef QV2RAY_NO_SINGLEAPPLICATON
void onMessageReceived(quint32 clientID, QByteArray msg) override;

View File

@ -33,7 +33,7 @@ namespace Qv2ray::ui::styles
styles.insert(key, style);
}
for (const auto &styleDir : Qv2rayAssetsPaths("uistyles"))
for (const auto &styleDir : QvCoreApplication->GetAssetsPaths("uistyles"))
{
for (const auto &file : GetFileList(QDir(styleDir)))
{

View File

@ -101,7 +101,7 @@ void MainWindow::MWClearSystemProxy()
bool MainWindow::StartAutoConnectionEntry()
{
if (StartupOption.noAutoConnection)
if (QvCoreApplication->StartupArguments.noAutoConnection)
return false;
switch (GlobalConfig.autoStartBehavior)
{

View File

@ -296,7 +296,7 @@ void PreferencesWindow::on_buttonBox_accepted()
ports << CurrentConfig.inboundConfig.tProxySettings.port;
}
if (!StartupOption.noAPI)
if (!QvCoreApplication->StartupArguments.noAPI)
{
size++;
ports << CurrentConfig.kernelConfig.statsPort;

View File

@ -8,15 +8,15 @@ char *fakeArgv[]{};
class QvTestApplication
: public QCoreApplication
, public Qv2rayApplicationManager
, public Qv2rayApplicationInterface
{
public:
explicit QvTestApplication() : QCoreApplication(fakeArgc, fakeArgv), Qv2rayApplicationManager(){};
virtual Qv2raySetupStatus Initialize() override
explicit QvTestApplication() : QCoreApplication(fakeArgc, fakeArgv), Qv2rayApplicationInterface(){};
virtual bool Initialize() override
{
return {};
return true;
};
virtual Qv2rayExitCode RunQv2ray() override
virtual Qv2rayExitReason RunQv2ray() override
{
return {};
};