diff --git a/.appveyor.yml b/.appveyor.yml index b6d19aa9..d462cdee 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -2,6 +2,7 @@ install: - set QTDIR=C:\Qt\5.10\mingw53_32 - choco install -y InnoSetup - set PATH=%QTDIR%\bin;C:\Qt\Tools\mingw730_32\bin;%PATH%;"C:\Program Files (x86)\Inno Setup 5" + - git submodule update --init build_script: - mkdir python37 && xcopy C:\Python37 python37 /E /H /Q diff --git a/.gitignore b/.gitignore index 55f8ccb3..ba5bbdc4 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ v2ctl.sig build/ python37/ tools/python-3.7.3.exe +./.vscode diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..06267c78 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "3rdparty/jsoncons"] + path = 3rdparty/jsoncons + url = https://github.com/danielaparker/jsoncons +[submodule "3rdparty/x2struct"] + path = 3rdparty/x2struct + url = https://github.com/xyz347/x2struct diff --git a/.travis.yml b/.travis.yml index f2b2dcce..c362f953 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,7 @@ before_script: - if [ "$BADGE" = "osx" ]; then export PATH="/usr/local/opt/qt/bin:$PATH"; fi script: + - git submodule update --init - lrelease ./Hv2ray.pro - mkdir build && cd ./build - QT_SELECT=5 QTDIR=/usr/share/qt5 qmake ../ diff --git a/3rdparty/jsoncons b/3rdparty/jsoncons new file mode 160000 index 00000000..11d0da64 --- /dev/null +++ b/3rdparty/jsoncons @@ -0,0 +1 @@ +Subproject commit 11d0da64cf9e8725aa9963c307c8df4957359eef diff --git a/3rdparty/x2struct b/3rdparty/x2struct new file mode 160000 index 00000000..084c3cd1 --- /dev/null +++ b/3rdparty/x2struct @@ -0,0 +1 @@ +Subproject commit 084c3cd183ae2450473befa53e987a0ed347a6ff diff --git a/Hv2ray.pro b/Hv2ray.pro index 92b630e3..1d110756 100644 --- a/Hv2ray.pro +++ b/Hv2ray.pro @@ -24,109 +24,104 @@ DEFINES += QT_DEPRECATED_WARNINGS CONFIG += c++11 -VPATH += ./src - SOURCES += \ - main.cpp \ - MainWindow.cpp \ - ConnectionEditWindow.cpp \ - ImportConfig.cpp \ - PrefrencesWindow.cpp \ - vinteract.cpp \ - utils.cpp \ - runguard.cpp + src/HUtils.cpp \ + src/w_MainWindow.cpp \ + src/w_ConnectionEditWindow.cpp \ + src/w_ImportConfig.cpp \ + src/w_PrefrencesWindow.cpp \ + src/main.cpp \ + src/vinteract.cpp \ + src/runguard.cpp HEADERS += \ - MainWindow.h \ - ConnectionEditWindow.h \ - ImportConfig.h \ - PrefrencesWindow.h \ - vinteract.h \ - utils.h \ - runguard.h + src/HConfigObjects.hpp \ + src/HUtils.hpp \ + src/V2ConfigObjects.hpp \ + src/runguard.hpp \ + src/vinteract.hpp \ + src/w_MainWindow.h \ + src/w_ConnectionEditWindow.h \ + src/w_ImportConfig.h \ + src/w_PrefrencesWindow.h FORMS += \ - MainWindow.ui \ - ConnectionEditWindow.ui \ - ImportConfig.ui \ - PrefrencesWindow.ui + src/w_MainWindow.ui \ + src/w_ConnectionEditWindow.ui \ + src/w_ImportConfig.ui \ + src/w_PrefrencesWindow.ui RESOURCES += \ - resources.qrc + resources.qrc TRANSLATIONS += \ - ./translations/zh-CN.ts \ - ./translations/en-US.ts + translations/zh-CN.ts \ + translations/en-US.ts RC_ICONS += ./icons/Hv2ray.ico +INCLUDEPATH += 3rdparty/\ + 3rdparty/jsoncons/include + + # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target WITH_PYTHON = no +PYTHONVER = null unix:!macx { - exists( "/usr/include/python3.7m/Python.h" ) { - equals(WITH_PYTHON, "no") { - message("Will build with python lib version 3.7.") - INCLUDEPATH += /usr/include/python3.7m/ - LIBS += -lpython3.7m - WITH_PYTHON = yes - } - } -} - -unix:!macx { - exists( "/usr/include/python3.6m/Python.h" ) { - equals(WITH_PYTHON, "no") { - message("Will build with python lib version 3.6.") - INCLUDEPATH += /usr/include/python3.6m/ - LIBS += -lpython3.6m - WITH_PYTHON = yes - } - } -} - -unix:!macx { + # Python Headers check. exists( "/usr/include/python3.5m/Python.h" ) { - equals(WITH_PYTHON, "no") { - message("Will build with python lib version 3.5.") - INCLUDEPATH += /usr/include/python3.5m/ - LIBS += -lpython3.5m - WITH_PYTHON = yes - } + PYTHONVER = 3.5 + WITH_PYTHON = yes + } + exists( "/usr/include/python3.6m/Python.h" ) { + PYTHONVER = 3.6 + WITH_PYTHON = yes + } + exists( "/usr/include/python3.7m/Python.h" ) { + PYTHONVER = 3.7 + WITH_PYTHON = yes + } + + equals( WITH_PYTHON, "yes" ) { + INCLUDEPATH += /usr/include/python$${PYTHONVER}m/ + LIBS += -lpython$${PYTHONVER}m + message("Will build with python lib version $$PYTHONVER") } } macx { - PYTHON_ROOT=/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions - exists( "$$PYTHON_ROOT/3.7/include/python3.7m/Python.h" ) { - equals(WITH_PYTHON, "no") { - message("Will build with python lib version 3.7.3.") - INCLUDEPATH += $$PYTHON_ROOT/3.7/include/python3.7m/ - LIBS += -L$$PYTHON_ROOT/3.7/lib/python3.7/config-3.7m-darwin/ -lpython3.7m - WITH_PYTHON = yes - } + PYTHON_ROOT=/usr/local/Cellar/python + exists( "$${PYTHON_ROOT}/3.6.5_1/" ) { + PYTHONVER = 3.6 + PYLDPATH=$${PYTHON_ROOT}/3.6.5_1/Frameworks/Python.framework/Versions/$${PYTHONVER} + WITH_PYTHON = yes + } + + exists( "$$PYTHON_ROOT/3.7.3/" ) { + PYTHONVER = 3.7 + PYLDPATH=$${PYTHON_ROOT}/3.7.3/Frameworks/Python.framework/Versions/$${PYTHONVER} + WITH_PYTHON = yes + } + + INCLUDEPATH += $${PYLDPATH}/include/python$${PYTHONVER}m/ + LIBS += -L$${PYLDPATH}/lib/python$${PYTHONVER}/config-$${PYTHONVER}m-darwin/ -lpython$${PYTHONVER}m + message("Will build with python lib version $$PYTHONVER") +} + +win32 { + exists( "$$PWD/python37/libs/libpython37_mingw.a" ) { + LIBS += -L$$PWD/python37/libs/ -lpython37_mingw + INCLUDEPATH += $$PWD/python37/include + WITH_PYTHON = yes } } -macx { - PYTHON_ROOT=/usr/local/Cellar/python/3.6.5_1/Frameworks/Python.framework/Versions - exists( "$$PYTHON_ROOT/3.6/include/python3.6m/Python.h" ) { - equals(WITH_PYTHON, "no") { - message("Will build with python lib version 3.6.5_1.") - INCLUDEPATH += $$PYTHON_ROOT/3.6/include/python3.6m/ - LIBS += -L$$PYTHON_ROOT/3.6/lib/python3.6/config-3.6m-darwin/ -lpython3.6m - WITH_PYTHON = yes - } - } +equals(WITH_PYTHON, "no") { + error("No Python3 libs found, did you install dev packages such as python3-dev ?") } -unix: equals(WITH_PYTHON, "no") { - error("No python libs found, did you install python3 dev package?") -} - -win32: LIBS += -L$$PWD/python37/libs/ -lpython37_mingw -win32: INCLUDEPATH += $$PWD/python37/include diff --git a/src/ConnectionEditWindow.cpp b/src/ConnectionEditWindow.cpp deleted file mode 100644 index c8239c20..00000000 --- a/src/ConnectionEditWindow.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include "ConnectionEditWindow.h" -#include "ui_ConnectionEditWindow.h" -#include -#include "MainWindow.h" -#include -#include -#include - -ConnectionEditWindow::ConnectionEditWindow(QWidget *parent) : - QDialog(parent), - ui(new Ui::ConnectionEditWindow) -{ - ui->setupUi(this); - ui->portLineEdit->setValidator(new QIntValidator()); - ui->alterLineEdit->setValidator(new QIntValidator()); -} - -ConnectionEditWindow::~ConnectionEditWindow() -{ - delete ui; -} -int Hv2Config::save() -{ - return -1; -} - -void Hv2Config::getConfigFromDialog(Ui::ConnectionEditWindow *ui) -{ - this->host = ui->ipLineEdit->text(); - this->port = ui->portLineEdit->text(); - this->alias = ui->aliasLineEdit->text(); - this->uuid = ui->idLineEdit->text(); - this->alterid = ui->alterLineEdit->text(); - this->security = ui->securityCombo->currentText(); - this->isCustom = 0; -} diff --git a/src/ConnectionEditWindow.h b/src/ConnectionEditWindow.h deleted file mode 100644 index d6bce706..00000000 --- a/src/ConnectionEditWindow.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CONFEDIT_H -#define CONFEDIT_H - -#include - -namespace Ui -{ - class ConnectionEditWindow; -} - -class Hv2Config -{ -public: - QString host; - QString port; - QString alias; - QString uuid; - QString alterid; - QString security; - int isCustom; - int save(); - void getConfigFromDialog(Ui::ConnectionEditWindow *ui); -private: - -}; - -class ConnectionEditWindow : public QDialog -{ - Q_OBJECT - -public: - explicit ConnectionEditWindow(QWidget *parent = nullptr); - ~ConnectionEditWindow(); -private: - Ui::ConnectionEditWindow *ui; - -}; - - -#endif // CONFEDIT_H diff --git a/src/HConfigObjects.hpp b/src/HConfigObjects.hpp new file mode 100644 index 00000000..e0e53a5d --- /dev/null +++ b/src/HConfigObjects.hpp @@ -0,0 +1,76 @@ +#ifndef HCONFIGOBJECTS_HPP +#define HCONFIGOBJECTS_HPP +#include +#include + +#include "V2ConfigObjects.hpp" +#include "vinteract.hpp" + +// Macros +#define HV2RAY_CONFIG_DIR_NAME "/.hv2ray/" +using namespace std; +namespace Hv2ray +{ + namespace HConfigModels + { + struct HInbondSetting { + bool enabled; + string ip; + int port; + bool useAuthentication; + string authUsername; + string authPassword; + HInbondSetting(): enabled(), ip(), port(), useAuthentication(), authUsername(), authPassword() {} + HInbondSetting(bool _e, string _ip, int _p): HInbondSetting() + { + enabled = _e; + port = _p; + ip = _ip; + } +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(enabled, ip, port, useAuthentication, authUsername, authPassword)) +#endif + }; + + struct HvConfigList { + string alias; + string fileName; + int index; + HvConfigList(): alias(), fileName(), index() {} + XTOSTRUCT(O(alias, fileName, index)) + }; + + struct Hv2Config { + string language; + bool runAsRoot; + string logLevel; + //Hv2ray::V2ConfigModels::MuxObject muxSetting; + HInbondSetting httpSetting; + HInbondSetting socksSetting; + list configs; + Hv2Config(): language(), runAsRoot(), logLevel(), httpSetting(), socksSetting(), configs() { } + Hv2Config(string lang, string log, HInbondSetting httpIn, HInbondSetting socksIN): Hv2Config() + { + language = lang; + logLevel = log; + httpSetting = httpIn; + socksSetting = socksIN; + runAsRoot = false; + } +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(language, runAsRoot, logLevel, httpSetting, socksSetting, configs)) +#endif + }; + } + + /// ConfigGlobalConfig is platform-independent as it's solved to be in the best + /// place at first in main.cpp + static QDir ConfigDir; +} + + +#if USE_TODO_FEATURES +JSONCONS_MEMBER_TRAITS_DECL(Hv2ray::HConfigModels::Hv2Config, language, runAsRoot, logLevel, httpSetting, socksSetting) +JSONCONS_MEMBER_TRAITS_DECL(Hv2ray::HConfigModels::HInbondSetting, enabled, ip, port, useAuthentication, authUsername, authPassword) +#endif +#endif // HCONFIGOBJECTS_HPP diff --git a/src/HUtils.cpp b/src/HUtils.cpp new file mode 100644 index 00000000..c3fa48d7 --- /dev/null +++ b/src/HUtils.cpp @@ -0,0 +1,61 @@ +#include "HUtils.hpp" +#include + +namespace Hv2ray +{ + namespace Utils + { + static HConfigModels::Hv2Config GlobalConfig; + void SetGlobalConfig(HConfigModels::Hv2Config conf) + { + GlobalConfig = conf; + } + + HConfigModels::Hv2Config GetGlobalConfig() + { + return GlobalConfig; + } + + void SaveConfig(QFile *configFile) + { + configFile->open(QFile::WriteOnly); + QString jsonConfig = StructToJSON(GetGlobalConfig()); + QTextStream stream(configFile); + stream << jsonConfig << endl; + stream.flush(); + configFile->close(); + } + + void LoadConfig(QFile *configFile) + { + using namespace Hv2ray::HConfigModels; + configFile->open(QFile::ReadOnly); + QTextStream stream(configFile); + auto str = stream.readAll(); + auto config = StructFromJSON(str.toStdString()); + SetGlobalConfig(config); + configFile->close(); + } + + QStringList getAllFilesList(QDir *dir) + { + return dir->entryList(QStringList() << "*" << "*.*", QDir::Hidden | QDir::Files); + } + QTranslator *getTranslator(string lang) + { + QTranslator *translator = new QTranslator(); + translator->load(QString::fromStdString(lang + ".qm"), ":/translations"); + return translator; + } + bool hasFile(QDir *dir, QString fileName) + { + return getAllFilesList(dir).indexOf(fileName) >= 0; + } + void showWarnMessageBox(QWidget *parent, QString title, QString text) + { + QMessageBox::warning(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); + } + } +} + + diff --git a/src/HUtils.hpp b/src/HUtils.hpp new file mode 100644 index 00000000..a4e662c8 --- /dev/null +++ b/src/HUtils.hpp @@ -0,0 +1,57 @@ +#ifndef UTILS_H +#define UTILS_H +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "HConfigObjects.hpp" + +namespace Hv2ray +{ + namespace Utils + { + void showWarnMessageBox(QWidget *parent, QString title, QString text); + QTranslator *getTranslator(string lang); + void SetGlobalConfig(HConfigModels::Hv2Config conf); + HConfigModels::Hv2Config GetGlobalConfig(); + void SaveConfig(QFile *configFile); + void LoadConfig(QFile *configFile); + /// Get file list in a Dir + QStringList getAllFilesList(QDir *dir); + bool hasFile(QDir *dir, QString fileName); + + template + QString StructToJSON(const TYPE &t) + { + string s; +#if USE_TODO_FEATURES + encode_json(t, s, indenting::indent); +#else + s = X::tojson(t, "", 4, ' '); +#endif + cout << s << endl; + return QString::fromStdString(s); + } + + template + TYPE StructFromJSON(const string &str) + { + TYPE v; +#if USE_TODO_FEATURES + v = decode_json(str); +#else + X::loadjson(str, v, false); +#endif + return v; + } + } +} +#endif // UTILS_H diff --git a/src/ImportConfig.cpp b/src/ImportConfig.cpp deleted file mode 100644 index 1ee03959..00000000 --- a/src/ImportConfig.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#pragma push_macro("slots") -#undef slots -#include "Python.h" -#pragma pop_macro("slots") - -#include "ConnectionEditWindow.h" -#include "vinteract.h" -#include "utils.h" -#include "ImportConfig.h" -#include "ui_ImportConfig.h" - -ImportConfig::ImportConfig(QWidget *parent) : - QDialog(parent), - ui(new Ui::ImportConfig) -{ - ui->setupUi(this); - connect(this, SIGNAL(updateConfTable()), parentWidget(), SLOT(updateConfTable())); -} - -ImportConfig::~ImportConfig() -{ - delete ui; -} - -void ImportConfig::on_pushButton_clicked() -{ - QString dir = QFileDialog::getOpenFileName(this, tr("OpenConfigFile"), "~/"); - ui->fileLineTxt->setText(dir); -} - -void ImportConfig::savefromFile(QString path, QString alias) -{ - Hv2Config newConfig; - newConfig.alias = alias; - QFile configFile(path); - if(!configFile.open(QIODevice::ReadOnly)) { - showWarnMessageBox(this, tr("ImportConfig"), tr("CannotOpenFile")); - qDebug() << "ImportConfig::CannotOpenFile"; - return; - } - QByteArray allData = configFile.readAll(); - configFile.close(); - QJsonDocument v2conf(QJsonDocument::fromJson(allData)); - QJsonObject rootobj = v2conf.object(); - QJsonObject outbound; - if(rootobj.contains("outbounds")) { - outbound = rootobj.value("outbounds").toArray().first().toObject(); - } else { - outbound = rootobj.value("outbound").toObject(); - } - QJsonObject vnext = switchJsonArrayObject(outbound.value("settings").toObject(), "vnext"); - QJsonObject user = switchJsonArrayObject(vnext, "users"); - newConfig.host = vnext.value("address").toString(); - newConfig.port = QString::number(vnext.value("port").toInt()); - newConfig.alterid = QString::number(user.value("alterId").toInt()); - newConfig.uuid = user.value("id").toString(); - newConfig.security = user.value("security").toString(); - if (newConfig.security.isNull()) { - newConfig.security = "auto"; - } - newConfig.isCustom = 1; - int id = newConfig.save(); - if(id < 0) - { - showWarnMessageBox(this, tr("ImportConfig"), tr("SaveFailed")); - qDebug() << "ImportConfig::SaveFailed"; - return; - } - emit updateConfTable(); - QString newFile = "conf/" + QString::number(id) + ".conf"; - if(!QFile::copy(path, newFile)) { - showWarnMessageBox(this, tr("ImportConfig"), tr("CannotCopyCustomConfig")); - qDebug() << "ImportConfig::CannotCopyCustomConfig"; - } -} - -void ImportConfig::on_buttonBox_accepted() -{ - QString alias = ui->nameTxt->text(); - if(ui->importSourceCombo->currentIndex() == 0) // From File... - { - QString path = ui->fileLineTxt->text(); - bool isValid = validationCheck(path); - if(isValid) { - savefromFile(path, alias); - } - } - else - { - QString vmess = ui->vmessConnectionStringTxt->toPlainText(); - Py_Initialize(); - assert(Py_IsInitialized()); - QString param = "--inbound socks:1080 " + vmess + " -o config.json.tmp"; - PyRun_SimpleString("import sys"); - PyRun_SimpleString("sys.path.append('./utils')"); - PyObject *pModule = PyImport_ImportModule("vmess2json"); - PyObject *pFunc = PyObject_GetAttrString(pModule, "main"); - PyObject *arg = PyTuple_New(1); - PyObject *arg1 = Py_BuildValue("s", param.toStdString().c_str()); - PyTuple_SetItem(arg, 0, arg1); - PyObject_CallObject(pFunc, arg); - Py_Finalize(); - if(QFile::exists(QCoreApplication::applicationDirPath() + "/config.json.tmp")) { - ImportConfig *im = new ImportConfig(this->parentWidget()); - if (validationCheck(QCoreApplication::applicationDirPath() + "/config.json.tmp")) { - im->savefromFile("config.json.tmp", alias); - } - QFile::remove("config.json.tmp"); - } else { - showWarnMessageBox(this, tr("ImportConfig"), tr("CannotGenerateConfig")); - qDebug() << "ImportConfig::CannotGenerateConfig"; - } - } - - if(ui->useCurrentSettingRidBtn->isChecked()) - { - // TODO: Use Current Settings... - } - else - { - // TODO: Override Inbound.... - } -} diff --git a/src/ImportConfig.h b/src/ImportConfig.h deleted file mode 100644 index ea8a97cc..00000000 --- a/src/ImportConfig.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef IMPORTCONF_H -#define IMPORTCONF_H - -#include - -namespace Ui -{ - class ImportConfig; -} - -class ImportConfig : public QDialog -{ - Q_OBJECT - -public: - explicit ImportConfig(QWidget *parent = nullptr); - void savefromFile(QString path, QString alias); - ~ImportConfig(); - -private slots: - void on_pushButton_clicked(); - void on_buttonBox_accepted(); -signals: - void updateConfTable(); - -private: - Ui::ImportConfig *ui; -}; - -#endif // IMPORTCONF_H diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp deleted file mode 100644 index eace8dec..00000000 --- a/src/MainWindow.cpp +++ /dev/null @@ -1,273 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "ui_MainWindow.h" -#include "PrefrencesWindow.h" -#include "MainWindow.h" -#include "ConnectionEditWindow.h" -#include "ImportConfig.h" -#include "vinteract.h" -#include "utils.h" - -void MainWindow::CreateTrayIcon() -{ - hTray = new QSystemTrayIcon(); - hTray->setToolTip(tr("Hv2ray")); - hTray->setIcon(this->windowIcon()); - connect(hTray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(on_activatedTray(QSystemTrayIcon::ActivationReason))); - - QAction *actionShow = new QAction(this); - QAction *actionQuit = new QAction(this); - QAction *actionStart = new QAction(this); - QAction *actionRestart = new QAction(this); - QAction *actionStop = new QAction(this); - - actionShow->setText(tr("#Hide")); - actionQuit->setText(tr("#Quit")); - actionStart->setText(tr("#Start")); - actionStop->setText(tr("#Stop")); - actionRestart->setText(tr("#Restart")); - actionStart->setEnabled(true); - actionStop->setEnabled(false); - actionRestart->setEnabled(false); - - trayMenu->addAction(actionShow); - trayMenu->addSeparator(); - trayMenu->addAction(actionStart); - trayMenu->addAction(actionStop); - trayMenu->addAction(actionRestart); - trayMenu->addSeparator(); - trayMenu->addAction(actionQuit); - - connect(actionShow, SIGNAL(triggered()), this, SLOT(toggleMainWindowVisibility())); - connect(actionStart, SIGNAL(triggered()), this, SLOT(on_startButton_clicked())); - connect(actionStop, SIGNAL(triggered()), this, SLOT(on_stopButton_clicked())); - connect(actionRestart, SIGNAL(triggered()), this, SLOT(on_restartButton_clicked())); - connect(actionQuit, SIGNAL(triggered()), this, SLOT(quit())); - hTray->setContextMenu(trayMenu); - hTray->show(); -} - -MainWindow::MainWindow(QWidget *parent) : - QMainWindow(parent), - ui(new Ui::MainWindow) -{ - this->setWindowIcon(QIcon(":/icons/Hv2ray.ico")); - ui->setupUi(this); - UpdateConfigTable(); -// ui->configTable->setContextMenuPolicy(Qt::CustomContextMenu); -// connect(ui->configTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); - this->v2instance = new v2Instance(); - CreateTrayIcon(); - if(QFileInfo("config.json").exists()) { - v2instance->start(this); - } - -// QAction *select = new QAction("Select", ui->configTable); -// QAction *del = new QAction("Delete", ui->configTable); -// QAction *rename = new QAction("Rename", ui->configTable); -// popMenu->addAction(select); -// popMenu->addAction(del); -// popMenu->addAction(rename); -// connect(select, SIGNAL(triggered()), this, SLOT(select_triggered())); -// connect(del, SIGNAL(triggered()), this, SLOT(delConf())); -// connect(rename, SIGNAL(triggered()), this, SLOT(renameRow())); -// connect(ui->logText, SIGNAL(textChanged()), this, SLOT(scrollToBottom())); -// bar = ui->logText->verticalScrollBar(); -} - -MainWindow::~MainWindow() -{ - hTray->hide(); - delete this->hTray; - delete this->v2instance; - delete ui; -} - -void MainWindow::on_actionEdit_triggered() -{ - ConnectionEditWindow *e = new ConnectionEditWindow(this); - e->setAttribute(Qt::WA_DeleteOnClose); - e->show(); -} - -void MainWindow::on_actionExisting_config_triggered() -{ - ImportConfig *f = new ImportConfig(this); - f->setAttribute(Qt::WA_DeleteOnClose); - f->show(); -} - -void MainWindow::showMenu(QPoint pos) -{ -// if(ui->configTable->indexAt(pos).column() != -1) { -// popMenu->move(cursor().pos()); -// popMenu->show(); -// } -} -void MainWindow::select_triggered() -{ -// int row = ui->configTable->selectionModel()->currentIndex().row(); -// int idIntable = ui->configTable->model()->data(ui->configTable->model()->index(row, 4)).toInt(); -// this->geneConf(idIntable); -// if(this->v2Inst->v2Process->state() == QProcess::Running) { -// this->on_restartButton_clicked(); -// } -} - -void MainWindow::DeleteConfig() -{ - -} -void MainWindow::UpdateConfigTable() -{ - -} -void MainWindow::GenerateConfig(int idIntable) -{ - Hv2Config tmpConf; - emit UpdateConfigTable(); - if (tmpConf.isCustom == 1) { - QString src = "conf/" + QString::number(idIntable) + ".conf"; - overrideInbounds(src); - if (QFile::exists("config.json")) { - QFile::remove("config.json"); - } - QFile::copy(src, "config.json"); - } else { - // TODO: Config generator - } -} -void MainWindow::UpdateLog() -{ - ui->logText->insertPlainText(this->v2instance->vProcess->readAllStandardOutput()); -} - -void MainWindow::on_startButton_clicked() -{ - ui->logText->clear(); - bool startFlag = this->v2instance->start(this); - trayMenu->actions()[2]->setEnabled(!startFlag); - trayMenu->actions()[3]->setEnabled(startFlag); - trayMenu->actions()[4]->setEnabled(startFlag); -} - -void MainWindow::on_stopButton_clicked() -{ - this->v2instance->stop(); - ui->logText->clear(); - trayMenu->actions()[2]->setEnabled(true); - trayMenu->actions()[3]->setEnabled(false); - trayMenu->actions()[4]->setEnabled(false); -} - -void MainWindow::on_restartButton_clicked() -{ - on_stopButton_clicked(); - on_startButton_clicked(); -} - -void MainWindow::on_clbutton_clicked() -{ - ui->logText->clear(); -} - -void MainWindow::on_rtButton_clicked() -{ - emit UpdateConfigTable(); -} - -void MainWindow::closeEvent(QCloseEvent *event) -{ - this->hide(); - event->ignore(); -} - -void MainWindow::on_activatedTray(QSystemTrayIcon::ActivationReason reason) -{ - switch (reason) { - case QSystemTrayIcon::Trigger: - // Toggle Show/Hide -#ifndef __APPLE__ - // Every single click will trigger the Show/Hide toggling. - // So, as a hobby on common MacOS Apps, we 'don't toggle visibility on click'. - toggleMainWindowVisibility(); -#endif - break; - case QSystemTrayIcon::DoubleClick: - if(this->isHidden()) { - this->show(); - } - break; - case QSystemTrayIcon::MiddleClick: - // TODO: Check if an alert message box is present. - // If so, do nothing but please wait for the message box to be closed. - if(this->v2instance->vProcess->state() == QProcess::ProcessState::Running) { - on_stopButton_clicked(); - } else { - on_startButton_clicked(); - } - break; - case QSystemTrayIcon::Unknown: - break; - case QSystemTrayIcon::Context: - break; - } -} - -void MainWindow::toggleMainWindowVisibility() -{ - if(this->isHidden()) { - this->show(); - trayMenu->actions()[0]->setText(tr("#Hide")); - } else { - this->hide(); - trayMenu->actions()[0]->setText(tr("#Show")); - } -} - -void MainWindow::quit() -{ - QCoreApplication::quit(); -} - -void MainWindow::on_actionExit_triggered() -{ - quit(); -} - -void MainWindow::renameRow() -{ -// QString text = QInputDialog::getText(this, "Rename config", "New name:", QLineEdit::Normal); -// int row = ui->configTable->currentIndex().row(); -// int idIntable = ui->configTable->model()->data(ui->configTable->model()->index(row, 4)).toInt(); -// SQLiteDB mydb; -// QString updateString = "update confs set alias = '" + text + "' where id = " + QString::number(idIntable); -// mydb.DoQuery(updateString); -// emit updateConfTable(); -} - -void MainWindow::scrollToBottom() -{ - bar->setValue(bar->maximum()); -} - -void MainWindow::on_actionPreferences_triggered() -{ - PrefrencesWindow *v = new PrefrencesWindow(this); - v->setAttribute(Qt::WA_DeleteOnClose); - v->show(); -} - -void MainWindow::on_pushButton_clicked() -{ - auto confedit = new ConnectionEditWindow(); - confedit->show(); -} diff --git a/src/MainWindow.h b/src/MainWindow.h deleted file mode 100644 index a46dba8f..00000000 --- a/src/MainWindow.h +++ /dev/null @@ -1,63 +0,0 @@ -#ifndef MAINWINDOW_H -#define MAINWINDOW_H -#define confDir "conf/" -#define confDatabase "conf/conf.db" -#include -#include "ConnectionEditWindow.h" -#include -#include -#include - -#include "vinteract.h" - -namespace Ui -{ - class MainWindow; -} - -class MainWindow : public QMainWindow -{ - Q_OBJECT - -public: - explicit MainWindow(QWidget *parent = nullptr); - v2Instance *v2instance; - QSystemTrayIcon *hTray; - QMenu *trayMenu = new QMenu(this); - QMenu *popMenu = new QMenu(this); - QScrollBar *bar; - ~MainWindow(); - -private slots: - void on_restartButton_clicked(); - void on_actionEdit_triggered(); - void on_actionExisting_config_triggered(); - void UpdateConfigTable(); - void DeleteConfig(); - void showMenu(QPoint pos); - void UpdateLog(); - void on_startButton_clicked(); - void on_stopButton_clicked(); - void select_triggered(); - void on_clbutton_clicked(); - void on_rtButton_clicked(); - void GenerateConfig(int idIntable); - void on_activatedTray(QSystemTrayIcon::ActivationReason reason); - void toggleMainWindowVisibility(); - void quit(); - void on_actionExit_triggered(); - void renameRow(); - void scrollToBottom(); - void on_actionPreferences_triggered(); - - void on_pushButton_clicked(); - -private: - Ui::MainWindow *ui; - void closeEvent(QCloseEvent *); - void createTrayAction(); - - void CreateTrayIcon(); -}; - -#endif // MAINWINDOW_H diff --git a/src/PrefrencesWindow.cpp b/src/PrefrencesWindow.cpp deleted file mode 100644 index 6aa155fd..00000000 --- a/src/PrefrencesWindow.cpp +++ /dev/null @@ -1,158 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils.h" -#include "PrefrencesWindow.h" - -PrefrencesWindow::PrefrencesWindow(QWidget *parent) : - QDialog(parent), - ui(new Ui::PrefrencesWindow) -{ - ui->setupUi(this); - rootObj = loadRootObjFromConf(); - QJsonObject http = findValueFromJsonArray(rootObj.value("inbounds").toArray(), "tag", "http-in"); - QJsonObject socks = findValueFromJsonArray(rootObj.value("inbounds").toArray(), "tag", "socks-in"); - if(rootObj.value("v2suidEnabled").toBool()) { - ui->runAsRootCheckBox->setCheckState(Qt::Checked); - } - if(!http.isEmpty()) { - ui->httpPortLE->setText(http.value("port").toString()); - ui->httpCB->setCheckState(Qt::Checked); - } else { - ui->httpPortLE->setDisabled(true); - } - if(!socks.isEmpty()) { - ui->socksPortLE->setText(socks.value("port").toString()); - ui->socksCB->setCheckState(Qt::Checked); - } else { - ui->socksPortLE->setDisabled(true); - } - ui->httpPortLE->setValidator(new QIntValidator()); - ui->socksPortLE->setValidator(new QIntValidator()); - parentMW = parent; -} - -PrefrencesWindow::~PrefrencesWindow() -{ - delete ui; -} - -void PrefrencesWindow::on_buttonBox_accepted() -{ - if(checkVCoreExes()) { - if(ui->httpPortLE->text().toInt() != ui->socksPortLE->text().toInt()) { - QJsonArray inbounds; - QJsonDocument modifiedDoc; - inbounds = rootObj.value("inbounds").toArray(); - int socksId = getIndexByValue(inbounds, "tag", "socks-in"); - if(socksId != -1) { - inbounds.removeAt(socksId); - } - int httpId = getIndexByValue(inbounds, "tag", "http-in"); - if(httpId != -1) { - inbounds.removeAt(httpId); - } - rootObj.remove("inbounds"); - rootObj.remove("v2suidEnabled"); - if(ui->socksCB->isChecked()) { - QJsonObject socks; - QJsonObject settings; - socks.insert("tag", "socks-in"); - socks.insert("port", ui->socksPortLE->text().toInt()); - socks.insert("listen", "127.0.0.1"); - socks.insert("protocol", "socks"); - settings.insert("auth", "noauth"); - settings.insert("udp", true); - settings.insert("ip", "127.0.0.1"); - socks.insert("settings", QJsonValue(settings)); - inbounds.append(socks); - } - if(ui->httpCB->isChecked()) { - QJsonObject http; - QJsonObject settings; - http.insert("tag", "http-in"); - http.insert("port", ui->httpPortLE->text().toInt()); - http.insert("listen", "127.0.0.1"); - http.insert("protocol", "http"); - settings.insert("auth", "noauth"); - settings.insert("udp", true); - settings.insert("ip", "127.0.0.1"); - http.insert("settings", QJsonValue(settings)); - inbounds.append(http); - } - rootObj.insert("inbounds", QJsonValue(inbounds)); -#ifndef _WIN32 - // Set UID and GID in *nix - QFileInfo v2rayCoreExeFile("v2ray"); - if(ui->runAsRootCheckBox->isChecked() && v2rayCoreExeFile.ownerId() != 0) { - QProcess::execute("pkexec", QStringList() << "bash" << "-c" << "chown root:root " + QCoreApplication::applicationDirPath() + "/v2ray" + ";chmod +s " + QCoreApplication::applicationDirPath() + "/v2ray"); - } else if (!ui->runAsRootCheckBox->isChecked() && v2rayCoreExeFile.ownerId() == 0) { - uid_t uid = getuid(); - gid_t gid = getgid(); - QProcess::execute("pkexec", QStringList() << "chown" << QString::number(uid) + ":" + QString::number(gid) << QCoreApplication::applicationDirPath() + "/v2ray"); - } - v2rayCoreExeFile.refresh(); - rootObj.insert("v2suidEnabled", v2rayCoreExeFile.ownerId() == 0); -#else - // No such uid gid thing on windows.... -#endif - modifiedDoc.setObject(rootObj); - QByteArray byteArray = modifiedDoc.toJson(QJsonDocument::Indented); - QFile confFile("conf/Hv2ray.config.json"); - if(!confFile.open(QIODevice::WriteOnly)) { - showWarnMessageBox(this, tr("#Prefrences"), tr("#CannotOpenConfigFile")); - qDebug() << "Cannot open Hv2ray.config.json for modifying"; - } - confFile.write(byteArray); - confFile.close(); - } else { - showWarnMessageBox(this, tr("Prefrences"), tr("PortNumbersCannotBeSame")); - } - } -} - - -void PrefrencesWindow::on_httpCB_stateChanged(int checked) -{ - if(checked != Qt::Checked) { - ui->httpPortLE->setDisabled(true); - } else { - ui->httpPortLE->setEnabled(true); - ui->httpPortLE->setText("6666"); - } -} - -void PrefrencesWindow::on_socksCB_stateChanged(int checked) -{ - if(checked != Qt::Checked) { - ui->socksPortLE->setDisabled(true); - } else { - ui->socksPortLE->setEnabled(true); - ui->socksPortLE->setText("1080"); - } -} - -void PrefrencesWindow::on_httpAuthCB_stateChanged(int checked) -{ - if(checked) - { - - } -} - -void PrefrencesWindow::on_runAsRootCheckBox_stateChanged(int arg1) -{ - Q_UNUSED(arg1); -#ifdef _WIN32 - showWarnMessageBox(this, tr("Prefrences"), tr("RunAsRootNotOnWindows")); -#endif -} diff --git a/src/PrefrencesWindow.h b/src/PrefrencesWindow.h deleted file mode 100644 index de856f08..00000000 --- a/src/PrefrencesWindow.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef HVCONF_H -#define HVCONF_H - -#include -#include - -namespace Ui -{ - class PrefrencesWindow; -} - -class PrefrencesWindow : public QDialog -{ - Q_OBJECT - -public: - explicit PrefrencesWindow(QWidget *parent = nullptr); - ~PrefrencesWindow(); - QJsonObject rootObj; - QWidget *parentMW; - -private slots: - void on_buttonBox_accepted(); - void on_httpCB_stateChanged(int arg1); - void on_socksCB_stateChanged(int arg1); - - void on_httpAuthCB_stateChanged(int arg1); - - void on_runAsRootCheckBox_stateChanged(int arg1); - -private: - Ui::PrefrencesWindow *ui; -}; - -#endif // HVCONF_H diff --git a/src/V2ConfigObjects.hpp b/src/V2ConfigObjects.hpp new file mode 100644 index 00000000..9765fb76 --- /dev/null +++ b/src/V2ConfigObjects.hpp @@ -0,0 +1,638 @@ +#include +#include + +// TODO Features +#define USE_TODO_FEATURES false + +#if USE_TODO_FEATURES +#include +using namespace jsoncons; +#else +#include +using namespace x2struct; +#endif + +#ifndef V2CONFIG_H +#define V2CONFIG_H + +using namespace std; +/*------------------------------------------------------------------------------------------------------------*/ + +namespace Hv2ray +{ + namespace V2ConfigModels + { + // Two struct defining TYPE parameter to be passed into inbound configs and outbound configs. + struct XOutBoundsType { + }; + struct XInBoundsType { + }; + + struct LogObject { + string access; + string error; + string loglevel; + LogObject(): access(), error(), loglevel() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(access, error, loglevel)) +#endif + }; + + struct ApiObject { + string tag; + list services; + ApiObject() : tag(), services() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tag, services)) +#endif + }; + namespace DNSObjects + { + struct ServerObject { + string address; + int port; + list domains; + ServerObject(): address(), port(), domains() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(address, port, domains)) +#endif + }; + } + struct DnsObject { + map hosts; +#if USE_TODO_FEATURES + tuple> servers; +#else + // Currently does not support ServerObject as tuple is.... quite complicated... + list servers; +#endif + DnsObject(): hosts(), servers() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(hosts, servers)) +#endif + }; + namespace ROUTINGObjects + { + struct RuleObject { + string type = "field"; + list domain; + list ip; + string port; + string network; + list source; + list user; + string inboundTag; + string protocol; + string attrs; + RuleObject() : type(), domain(), ip(), port(), network(), source(), user(), inboundTag(), protocol(), attrs() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(type, domain, ip, port, network, source, user, inboundTag, protocol, attrs)) +#endif + }; + struct BalancerObject { + string tag ; + list selector; + BalancerObject() : tag(), selector() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tag, selector)) +#endif + }; + } + + struct RoutingObject { + string domainStrategy; + list rules; + list balancers; + RoutingObject() : domainStrategy(), rules(), balancers() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(domainStrategy, rules, balancers)) +#endif + }; + namespace POLICYObjects + { + struct SystemPolicyObject { + bool statsInboundUplink; + bool statsInboundDownlink; + SystemPolicyObject() : statsInboundUplink(), statsInboundDownlink() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(statsInboundUplink, statsInboundDownlink)) +#endif + }; + + struct LevelPolicyObject { + int handshake; + int connIdle; + int uplinkOnly; + int downlinkOnly; + bool statsUserUplink; + bool statsUserDownlink; + int bufferSize; + LevelPolicyObject(): handshake(), connIdle(), uplinkOnly(), downlinkOnly(), statsUserUplink(), statsUserDownlink(), bufferSize() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize)) +#endif + }; + } + struct PolicyObject { + map level; + list system; + PolicyObject(): level(), system() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(level, system)) +#endif + }; + namespace TRANSFERObjects + { + namespace TRANSFERObjectsInternal + { + + struct HTTPRequestObject { + string version; + string method; + list path; + map> headers; + HTTPRequestObject(): version(), method(), path(), headers() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(version, method, path, headers)) +#endif + }; + + struct HTTPResponseObject { + string version; + string status; + string reason; + map> headers; + HTTPResponseObject(): version(), status(), reason(), headers() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(version, status, reason, headers)) +#endif + }; + struct TCPHeader_M_Object { + string type; + HTTPRequestObject request; + HTTPResponseObject response; + TCPHeader_M_Object(): type(), request(), response() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(type, request, response)) +#endif + }; + struct HeaderObject { + string type; + HeaderObject(): type() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(type)) +#endif + }; + } + + + struct TCPObject { + TRANSFERObjectsInternal:: TCPHeader_M_Object header; + TCPObject(): header() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(header)) +#endif + }; + + + struct KCPObject { + int mtu; + int tti; + int uplinkCapacity; + int downlinkCapacity; + bool congestion; + int readBufferSize; + int writeBufferSize; + TRANSFERObjectsInternal:: HeaderObject header; + KCPObject(): mtu(), tti(), uplinkCapacity(), downlinkCapacity(), congestion(), readBufferSize(), writeBufferSize(), header() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header)) +#endif + }; + + + struct WebSocketObject { + string path; + map headers; + WebSocketObject(): path(), headers() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(path, headers)) +#endif + }; + + struct HttpObject { + list host; + string path; + HttpObject() : host(), path() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(host, path)) +#endif + }; + + struct DomainSocketObject { + string path; + DomainSocketObject(): path() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(path)) +#endif + }; + + struct QuicObject { + string security; + string key; + TRANSFERObjectsInternal::HeaderObject header; + QuicObject(): security(), key(), header() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(security, key, header)) +#endif + }; + + + } + struct TransportObject { + TRANSFERObjects::TCPObject tcpSettings; + TRANSFERObjects::KCPObject kcpSettings; + TRANSFERObjects::WebSocketObject wsSettings; + TRANSFERObjects::HttpObject httpSettings; + TRANSFERObjects::DomainSocketObject dsSettings; + TRANSFERObjects::QuicObject quicSettings; + TransportObject(): tcpSettings(), kcpSettings(), wsSettings(), httpSettings(), dsSettings(), quicSettings() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tcpSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings)) +#endif + }; + namespace INBOUNDObjects + { + + struct SniffingObject { + bool enabled; + string destOverride; + SniffingObject(): enabled(), destOverride() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(enabled, destOverride)) +#endif + }; + + struct AllocateObject { + string strategy; + int refresh; + int concurrency; + AllocateObject(): strategy(), refresh(), concurrency() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(strategy, refresh, concurrency)) +#endif + }; + } + namespace STREAMSETTINGSObjects + { + struct SockoptObject { + int mark; + bool tcpFastOpen; + string tproxy; + SockoptObject(): mark(), tcpFastOpen(), tproxy() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(mark, tcpFastOpen, tproxy)) +#endif + }; + + struct CertificateObject { + string usage; + string certificateFile; + string keyFile; + list certificate; + list key; + CertificateObject(): usage(), certificateFile(), keyFile(), certificate(), key() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(usage, certificateFile, keyFile, certificate, key)) +#endif + }; + + struct TLSObject { + string serverName; + bool allowInsecure; + list alpn; + list certificates; + bool disableSystemRoot; + TLSObject(): serverName(), allowInsecure(), certificates(), disableSystemRoot() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(serverName, allowInsecure, alpn, certificates, disableSystemRoot)) +#endif + }; + } + + struct StreamSettingsObject { + string network; + string security; + STREAMSETTINGSObjects::SockoptObject sockopt; + STREAMSETTINGSObjects::TLSObject tlsSettings; + TRANSFERObjects::TCPObject tcpSettings; + TRANSFERObjects::KCPObject kcpSettings; + TRANSFERObjects::WebSocketObject wsSettings; + TRANSFERObjects::HttpObject httpSettings; + TRANSFERObjects::DomainSocketObject dsSettings; + TRANSFERObjects::QuicObject quicSettings; + StreamSettingsObject(): network(), security(), sockopt(), tlsSettings(), tcpSettings(), kcpSettings(), wsSettings(), httpSettings(), dsSettings(), quicSettings() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(network, security, sockopt, tcpSettings, tlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings)) +#endif + }; + + template + struct InboundObject { + static_assert(std::is_base_of::value, "XINBOUNDSETTINGOBJECT must extend XInBoundsType"); + int port; + string listen; + string protocol; + XINBOUNDSETTINGOBJECT settings; + StreamSettingsObject streamSettings; + string tag; + INBOUNDObjects::SniffingObject sniffing; + INBOUNDObjects::AllocateObject allocate; + InboundObject(): port(), listen(), protocol(), settings(), streamSettings(), tag(), sniffing(), allocate() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(port, listen, protocol, settings, streamSettings, tag, sniffing, allocate)) +#endif + }; + namespace OUTBOUNDObjects + { + + struct ProxySettingsObject { + string tag; + ProxySettingsObject(): tag() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tag)) +#endif + }; + + struct MuxObject { + bool enabled; + int concurrency; + MuxObject(): enabled(), concurrency() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(enabled, concurrency)) +#endif + }; + } + template + struct OutboundObject { + static_assert(std::is_base_of::value, "XOUTBOUNDSETTINGOBJECT must extend XOutBoundsType"); + string sendThrough; + string protocol; + XOUTBOUNDSETTINGOBJECT settings; + string tag; + StreamSettingsObject streamSettings; + OUTBOUNDObjects::ProxySettingsObject proxySettings; + OUTBOUNDObjects::MuxObject mux; + OutboundObject(): sendThrough(), protocol(), settings(), tag(), streamSettings(), proxySettings(), mux() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(sendThrough, protocol, settings, tag, streamSettings, proxySettings, mux)) +#endif + }; + + struct StatsObject { + bool _; // Placeholder... + StatsObject(): _() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(_)) +#endif + }; + namespace REVERSEObjects + { + + struct BridgeObject { + string tag; + string domain; + BridgeObject() : tag(), domain() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tag, domain)) +#endif + }; + + struct PortalObject { + string tag; + string domain; + PortalObject() : tag(), domain() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(tag, domain)) +#endif + }; + + } + struct ReverseObject { + list bridges; + list portals; + ReverseObject() : bridges(), portals() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(bridges, portals)) +#endif + }; +#if USE_TODO_FEATURES + template +#else + template +#endif + struct RootObject { + LogObject log; + ApiObject api; + DnsObject dns; + RoutingObject routing; +#if USE_TODO_FEATURES + // Default support 5 inBounds and 5 outBounds + tuple, InboundObject, InboundObject, InboundObject, InboundObject> inbounds; + tuple, OutboundObject, OutboundObject, OutboundObject, OutboundObject> outbounds; +#else + list> inbounds; + list> outbounds; +#endif + TransportObject transport; + StatsObject stats; + ReverseObject reverse; + PolicyObject policy; + RootObject(): log(), api(), dns(), routing(), inbounds(), outbounds(), transport(), stats(), reverse(), policy() {} +#if USE_TODO_FEATURES == false + XTOSTRUCT(O(log, api, dns, routing, inbounds, outbounds, transport, stats, reverse, policy)) +#endif + }; + } +} + +namespace Hv2ray +{ + namespace V2ConfigModels + { + /// Some protocols from: https://v2ray.com/chapter_02/02_protocols.html + namespace Protocols + { + /// BlackHole Protocol, OutBound + struct BlackHole : XOutBoundsType { + struct ResponseObject { + string type; + }; + ResponseObject response; + }; + + /// DNS, OutBound + struct DNS: XOutBoundsType { + string network; + string address; + int port; + }; + + /// Dokodemo-door, InBound + struct Dokodemo_door : XInBoundsType { + string address; + int port; + string network; + int timeout; + bool followRedirect; + int userLevel; + }; + + /// Freedom, OutBound + struct Freedom: XOutBoundsType { + string domainStrategy; + string redirect; + int userLevel; + }; + + struct AccountObject { + string user; + string pass; + XTOSTRUCT(O(user, pass)) + }; + + /// HTTP, InBound + struct HTTP: XInBoundsType { + int timeout; + list accounts; + bool allowTransparent; + int userLevel; + XTOSTRUCT(O(timeout, accounts, allowTransparent, userLevel)) + }; + + /// MTProto, InBound || OutBound + struct MTProto: XInBoundsType, XOutBoundsType { + struct UserObject { + string email; + int level; + string secret; + }; + list users; + }; + + // We don't add shadowsocks, (As it's quite complex and I'm quite lazy...) + + /// Socks, InBound, OutBound + struct Socks: XInBoundsType, XOutBoundsType { + struct UserObject { + }; + struct ServerObject { + string address; + int port; + list users; + }; + list servers; + string auth; + list accounts; + bool udp; + string ip; + int userLevel; + }; + + struct VMess: XInBoundsType, XOutBoundsType { + struct ServerObject { + struct UserObject { + string id; + int alterId; + string security; + int level; + XTOSTRUCT(O(id, alterId, security, level)) + }; + // OUTBound; + string address; + int port; + list users; + XTOSTRUCT(O(address, port, users)) + }; + list vnext; + // INBound; + struct ClientObject { + string id; + int level; + int alterId; + string email; + XTOSTRUCT(O(id, level, alterId, email)) + }; + list clients; + // detour and default will not be implemented as it's complicated... + bool disableInsecureEncryption; + XTOSTRUCT(O(vnext, clients, disableInsecureEncryption)) + }; + } + } +} + +#if USE_TODO_FEATURES +using namespace Hv2ray; +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::LogObject, access, error, loglevel) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::ApiObject, tag, services) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::DNSObjects::ServerObject, address, port, domains) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::DnsObject, hosts, servers) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::ROUTINGObjects::RuleObject, type, domain, ip, port, network, source, user, inboundTag, protocol, attrs) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::ROUTINGObjects::BalancerObject, tag, selector) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::RoutingObject, domainStrategy, rules, balancers) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::POLICYObjects::SystemPolicyObject, statsInboundUplink, statsInboundDownlink) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::POLICYObjects::LevelPolicyObject, handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::PolicyObject, level, system) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::TRANSFERObjectsInternal::HTTPRequestObject, version, method, path, headers) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::TRANSFERObjectsInternal::HTTPResponseObject, version, status, reason, headers) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::TRANSFERObjectsInternal::TCPHeader_M_Object, type, request, response) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::TRANSFERObjectsInternal::HeaderObject, type) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::TCPObject, header) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::KCPObject, mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::WebSocketObject, path, headers) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::HttpObject, host, path) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::DomainSocketObject, path) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TRANSFERObjects::QuicObject, security, key, header) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::TransportObject, tcpSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::INBOUNDObjects::SniffingObject, enabled, destOverride) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::INBOUNDObjects::AllocateObject, strategy, refresh, concurrency) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::OUTBOUNDObjects::ProxySettingsObject, tag) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::OUTBOUNDObjects::MuxObject, enabled, concurrency) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::REVERSEObjects::BridgeObject, tag, domain) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::REVERSEObjects::PortalObject, tag, domain) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::ReverseObject, bridges, portals) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::StatsObject, _) + +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::StreamSettingsObject, tcpSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, tlsSettings, sockopt, network, security) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::STREAMSETTINGSObjects::TLSObject, serverName, allowInsecure, alpn, certificates, disableSystemRoot) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::STREAMSETTINGSObjects::CertificateObject, usage, certificateFile, keyFile, certificate, key) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::STREAMSETTINGSObjects::SockoptObject, mark, tcpFastOpen, tproxy) + +// These 3 are used as templates. +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::RootObject, log, api, dns, routing, inbounds, outbounds, transport, stats, reverse, policy) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::InboundObject, port, listen, protocol, settings, streamSettings, tag, sniffing, allocate) +JSONCONS_MEMBER_TRAITS_DECL(V2ConfigModels::OutboundObject, sendThrough, protocol, settings, tag, streamSettings, proxySettings, mux) + +#endif + +/// Code above has passed these tests. +//using namespace Hv2ray::V2ConfigModels; +//RootObject x; +//InboundObject inH; +//x.inbounds.insert(x.inbounds.end(), inH); +//OutboundObject inV; +//x.outbounds.insert(x.outbounds.end(), inV); +//QString jsonConfig = Utils::StructToJSON(x); +//cout << jsonConfig.toStdString() << endl; +/// +#endif diff --git a/src/main.cpp b/src/main.cpp index 2a7f729d..82b9d6bf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,87 +1,98 @@ -#include -#include -#include #include #include -#include -#include -#include -#include +#include #include +#include -#include "runguard.h" -#include "utils.h" -#include "MainWindow.h" -#include "ConnectionEditWindow.h" +#include "HUtils.hpp" +#include "HConfigObjects.hpp" +#include "runguard.hpp" +#include "w_MainWindow.h" using namespace std; +using namespace Hv2ray; +using namespace Hv2ray::Utils; +using namespace Hv2ray::HConfigModels; -void firstRunCheck() +bool initializeHv() { - if(!QDir("conf").exists()) { - QDir().mkdir("conf"); - qDebug() << "Config directory created."; - } + /// Hv2ray Config Path and ends with "/" + QString configPath = ""; +#if defined(__WIN32) || defined(__APPLE__) + // For Windows and MacOS, there's no such 'installation' of a software + // package, So as what ShadowSocks and v2rayX does, save config files next to + // the executable. + configPath = HV2RAY_CONFIG_DIR_NAME; +#else + // However, for linux, this software can be and/or will be provided as a + // package and install to whatever /usr/bin or /usr/local/bin or even /opt/ + // Thus we save config files in the user's home directory. + configPath = QDir::homePath() + HV2RAY_CONFIG_DIR_NAME; +#endif + ConfigDir = QDir(configPath); - QFileInfo hvConfInfo("conf/Hv2ray.config.json"); + if (!ConfigDir.exists()) { + auto result = QDir().mkdir(configPath); - // First Run? - if(!hvConfInfo.exists()) { - QFile confFile("conf/Hv2ray.config.json"); - if(!confFile.open(QIODevice::ReadWrite)) { - qDebug() << "Can not open Hv2ray.conf.json for read and write."; + if (result) { + qDebug() << "Created hv2ray config file path at: " + configPath; + } else { + // We cannot continue as it failed to create a dir. + qDebug() << "Failed to create config file folder under " + configPath; + return false; } - - QJsonObject settings; - settings.insert("auth", "noauth"); - settings.insert("udp", true); - settings.insert("ip", "127.0.0.1"); - - QJsonObject socks; - socks.insert("settings", QJsonValue(settings)); - socks.insert("tag", "socks-in"); - socks.insert("port", 1080); - socks.insert("listen", "127.0.0.1"); - socks.insert("protocol", "socks"); - - QJsonArray inbounds; - inbounds.append(socks); - - QJsonObject rootObj; - rootObj.insert("inbounds", QJsonValue(inbounds)); - rootObj.insert("v2suidEnabled", false); - - QJsonDocument defaultConf; - defaultConf.setObject(rootObj); - - QByteArray byteArray = defaultConf.toJson(QJsonDocument::Indented); - confFile.write(byteArray); - confFile.close(); } + + QFile configFile(configPath + "hv2ray.conf"); + + if (!Utils::hasFile(&ConfigDir, ".initialised")) { + // This is first run! + // These below genenrated very basic global config. + HInbondSetting inHttp = HInbondSetting(true, "127.0.0.1", 8080); + HInbondSetting inSocks = HInbondSetting(true, "127.0.0.1", 1080); + Hv2Config conf = Hv2Config("zh-CN", "info", inHttp, inSocks); + SetGlobalConfig(conf); + SaveConfig(&configFile); + // Create Placeholder for initialise indicator. + QFile initPlaceHolder(configPath + ".initialised"); + initPlaceHolder.open(QFile::WriteOnly); + initPlaceHolder.close(); + } else { + LoadConfig(&configFile); + } + + return true; } int main(int argc, char *argv[]) { QApplication _qApp(argc, argv); + RunGuard guard("Hv2ray-Instance-Identifier"); - QTranslator translator; - if (translator.load(QString(":/translations/zh-CN.qm"), QString("translations"))) - { - cout << "Loaded Chinese translations" << endl; + if (!guard.isSingleInstance()) { + Utils::showWarnMessageBox(nullptr, QObject::tr("Hv2Ray"), QObject::tr("AnotherInstanceRunning")); + return -1; } - _qApp.installTranslator(&translator); - - RunGuard guard("Hv2ray"); - if(!guard.isSingleInstance()) { - showWarnMessageBox(nullptr, QObject::tr("Hv2Ray"), QObject::tr("AnotherInstanceRunning")); - return -1; - } + // Set file startup path as Path + // WARNING: This may be changed in the future. QDir::setCurrent(QFileInfo(QCoreApplication::applicationFilePath()).path()); + // Hv2ray Initialize + initializeHv(); - firstRunCheck(); - MainWindow w; + if (_qApp.installTranslator(getTranslator(GetGlobalConfig().language))) { + cout << "Loaded translations " << GetGlobalConfig().language << endl; + } else if (_qApp.installTranslator(getTranslator("en-US"))) { + cout << "Loaded default translations" << endl; + } else { + showWarnMessageBox( + nullptr, "Failed to load translations 无法加载语言文件", + "Failed to load translations, user experience may be downgraded. \r\n" + "无法加载语言文件,用户体验可能会降级."); + } + // Show MainWindow + Ui::MainWindow w; w.show(); return _qApp.exec(); } diff --git a/src/runguard.cpp b/src/runguard.cpp index 486941d1..d8456e04 100644 --- a/src/runguard.cpp +++ b/src/runguard.cpp @@ -1,77 +1,81 @@ #include -#include "runguard.h" - -//from https://stackoverflow.com/a/28172162 -namespace +#include "runguard.hpp" +namespace Hv2ray { - QString generateKeyHash( const QString& key, const QString& salt ) + //from https://stackoverflow.com/a/28172162 + QString RunGuard::generateKeyHash(const QString &key, const QString &salt) { QByteArray data; - data.append( key.toUtf8() ); - data.append( salt.toUtf8() ); - data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex(); + data.append(key.toUtf8()); + data.append(salt.toUtf8()); + data = QCryptographicHash::hash(data, QCryptographicHash::Sha1).toHex(); return data; } - -} - - -RunGuard::RunGuard( const QString& key ) - : key( key ) - , memLockKey( generateKeyHash( key, "_memLockKey" ) ) - , sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) ) - , sharedMem( sharedmemKey ) - , memLock( memLockKey, 1 ) -{ - memLock.acquire(); + RunGuard::RunGuard(const QString &key) + : key(key) + , memLockKey(generateKeyHash(key, "_memLockKey")) + , sharedmemKey(generateKeyHash(key, "_sharedmemKey")) + , sharedMem(sharedmemKey) + , memLock(memLockKey, 1) { - QSharedMemory fix( sharedmemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/ - fix.attach(); + memLock.acquire(); + { + QSharedMemory fix(sharedmemKey); // Fix for *nix: http://habrahabr.ru/post/173281/ + fix.attach(); + } + memLock.release(); } - memLock.release(); -} -RunGuard::~RunGuard() -{ - release(); -} - -bool RunGuard::isAnotherRunning() -{ - if ( sharedMem.isAttached() ) { - return false; - } - memLock.acquire(); - const bool isRunning = sharedMem.attach(); - if ( isRunning ) { - sharedMem.detach(); - } - memLock.release(); - return isRunning; -} - -bool RunGuard::isSingleInstance() -{ - if ( isAnotherRunning() ) { // Extra check - return false; - } - memLock.acquire(); - const bool result = sharedMem.create( sizeof( quint64 ) ); - memLock.release(); - if ( !result ) { + RunGuard::~RunGuard() + { release(); - return false; } - return true; -} -void RunGuard::release() -{ - memLock.acquire(); - if ( sharedMem.isAttached() ) { - sharedMem.detach(); + bool RunGuard::isAnotherRunning() + { + if (sharedMem.isAttached()) { + return false; + } + + memLock.acquire(); + const bool isRunning = sharedMem.attach(); + + if (isRunning) { + sharedMem.detach(); + } + + memLock.release(); + return isRunning; + } + + bool RunGuard::isSingleInstance() + { + if (isAnotherRunning()) { // Extra check + return false; + } + + memLock.acquire(); + const bool result = sharedMem.create(sizeof(quint64)); + memLock.release(); + + if (!result) { + release(); + return false; + } + + return true; + } + + void RunGuard::release() + { + memLock.acquire(); + + if (sharedMem.isAttached()) { + sharedMem.detach(); + } + + memLock.release(); } - memLock.release(); } diff --git a/src/runguard.h b/src/runguard.h deleted file mode 100644 index 45e7e3ba..00000000 --- a/src/runguard.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef RUNGUARD_H -#define RUNGUARD_H - -#include -#include -#include - -// From https://stackoverflow.com/a/28172162 -class RunGuard -{ - -public: - RunGuard( const QString& key ); - ~RunGuard(); - - bool isAnotherRunning(); - bool isSingleInstance(); - void release(); - -private: - const QString key; - const QString memLockKey; - const QString sharedmemKey; - - QSharedMemory sharedMem; - QSystemSemaphore memLock; - - Q_DISABLE_COPY( RunGuard ) -}; - -#endif // RUNGUARD_H diff --git a/src/runguard.hpp b/src/runguard.hpp new file mode 100644 index 00000000..3da1afb5 --- /dev/null +++ b/src/runguard.hpp @@ -0,0 +1,33 @@ +#ifndef RUNGUARD_H +#define RUNGUARD_H + +#include +#include +#include + +namespace Hv2ray +{ + // From https://stackoverflow.com/a/28172162 + class RunGuard + { + public: + explicit RunGuard(const QString &key); + ~RunGuard(); + + bool isAnotherRunning(); + bool isSingleInstance(); + void release(); + + private: + QString generateKeyHash(const QString &key, const QString &salt); + const QString key; + const QString memLockKey; + const QString sharedmemKey; + + QSharedMemory sharedMem; + QSystemSemaphore memLock; + + Q_DISABLE_COPY(RunGuard) + }; +} +#endif // RUNGUARD_H diff --git a/src/utils.cpp b/src/utils.cpp deleted file mode 100644 index 30621892..00000000 --- a/src/utils.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "utils.h" - -QJsonObject switchJsonArrayObject(QJsonObject obj, QString value) -{ - QJsonObject returnObj = obj.value(value).isNull() - ? obj.value(value).toObject() - : obj.value(value).toArray().first().toObject(); - return returnObj; -} - -QJsonObject findValueFromJsonArray(QJsonArray arr, QString key, QString val) -{ - for (const auto obj : arr) { - if (obj.toObject().value(key).toString() == val) { - return obj.toObject(); - } - } - return QJsonObject(); -} - -QJsonObject loadRootObjFromConf() -{ - QFile globalConfigFile("Hv2ray.conf"); - globalConfigFile.open(QIODevice::ReadOnly); - QByteArray conf = globalConfigFile.readAll(); - globalConfigFile.close(); - QJsonDocument v2conf(QJsonDocument::fromJson(conf)); - QJsonObject rootObj = v2conf.object(); - return rootObj; -} - -QJsonArray getInbounds() -{ - QJsonArray inbounds; - inbounds = loadRootObjFromConf().value("inbounds").toArray(); - return inbounds; -} - -bool getRootEnabled() -{ - return loadRootObjFromConf().value("v2suidEnabled").toBool(); -} - -bool checkVCoreExes() -{ - if (QFileInfo("v2ray").exists() && QFileInfo("geoip.dat").exists() && QFileInfo("geosite.dat").exists() && QFileInfo("v2ctl").exists()) { - return true; - } else { - showWarnMessageBox(nullptr, QObject::tr("CoreNotFound"), QObject::tr("CoreFileNotFoundExplaination")); - return false; - } -} - -void showWarnMessageBox(QWidget* parent, QString title, QString text) -{ - QMessageBox::warning(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); -} - -void overrideInbounds(QString path) -{ - QFile confFile(path); - confFile.open(QIODevice::ReadOnly); - QByteArray conf = confFile.readAll(); - confFile.close(); - QJsonDocument v2conf(QJsonDocument::fromJson(conf)); - QJsonObject rootObj = v2conf.object(); - QJsonArray modifiedIn = getInbounds(); - rootObj.remove("inbounds"); - rootObj.remove("inbound"); - rootObj.remove("inboundDetour"); - rootObj.insert("inbounds", QJsonValue(modifiedIn)); - v2conf.setObject(rootObj); - conf = v2conf.toJson(QJsonDocument::Indented); - confFile.open(QIODevice::WriteOnly | QIODevice::Truncate); - confFile.write(conf); - confFile.close(); -} - -int getIndexByValue(QJsonArray array, QString key, QString val) -{ - QJsonArray::iterator it; - int index = 0; - for(it = array.begin(); it != array.end(); it ++) { - if(it->toObject().value(key) == val) { - return index; - } - index ++; - } - return -1; -} diff --git a/src/utils.h b/src/utils.h deleted file mode 100644 index d9f1b861..00000000 --- a/src/utils.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef UTILS_H -#define UTILS_H -#include - -QJsonObject switchJsonArrayObject(QJsonObject objest, QString value); -QJsonObject findValueFromJsonArray(QJsonArray arr, QString key, QString val); -QJsonObject loadRootObjFromConf(); -QJsonArray getInbounds(); -bool checkVCoreExes(); -void showWarnMessageBox(QWidget* parent, QString title, QString text); -void overrideInbounds(QString path); -int getIndexByValue(QJsonArray array, QString key, QString val); - -#endif // UTILS_H diff --git a/src/vinteract.cpp b/src/vinteract.cpp index 898486cb..1d53e4a8 100644 --- a/src/vinteract.cpp +++ b/src/vinteract.cpp @@ -1,66 +1,87 @@ -#include #include -#include #include +#include -#include "utils.h" -#include "MainWindow.h" -#include "vinteract.h" +#include "HUtils.hpp" +#include "vinteract.hpp" +#include "w_MainWindow.h" -bool validationCheck(QString path) +namespace Hv2ray { - if(checkVCoreExes()) { - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("V2RAY_LOCATION_ASSET", QDir::currentPath()); + bool v2Instance::checkConfigFile(const QString path) + { + if (checkVCoreExes()) { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("V2RAY_LOCATION_ASSET", QDir::currentPath()); + QProcess process; + process.setProcessEnvironment(env); + process.start("v2ray", QStringList() << "-test" + << "-config" << path, + QIODevice::ReadWrite | QIODevice::Text); - QProcess process; - process.setProcessEnvironment(env); - process.start("v2ray", QStringList() << "-test" << "-config" << path, QIODevice::ReadWrite | QIODevice::Text); + if (!process.waitForFinished()) { + qDebug() << "v2ray core failed with exit code " << process.exitCode(); + return false; + } - if(!process.waitForFinished()) { - qDebug() << "v2ray core failed with exit code " << process.exitCode(); + QString output = QString(process.readAllStandardOutput()); + + if (!output.contains("Configuration OK")) { + Utils::showWarnMessageBox(nullptr, QObject::tr("ConfigurationError"), output.mid(output.indexOf("anti-censorship.") + 17)); + return false; + } else + return true; + } else return false; - } - - QString output = QString(process.readAllStandardOutput()); - - if (!output.contains("Configuration OK")) { - showWarnMessageBox(nullptr, QObject::tr("ConfigurationError"), output.mid(output.indexOf("anti-censorship.") + 17)); - return false; - } - else return true; } - else return false; -} -v2Instance::v2Instance() -{ - this->vProcess = new QProcess(); -} + v2Instance::v2Instance(QWidget *parent) + { + QProcess *proc = new QProcess(); + this->vProcess = proc; + QObject::connect(vProcess, SIGNAL(readyReadStandardOutput()), parent, SLOT(updateLog())); + processStatus = STOPPED; + } -v2Instance::~v2Instance() -{ - this->stop(); -} + bool v2Instance::checkVCoreExes() + { + if (QFileInfo("v2ray").exists() && QFileInfo("geoip.dat").exists() && QFileInfo("geosite.dat").exists() && QFileInfo("v2ctl").exists()) { + return true; + } else { + Utils::showWarnMessageBox(nullptr, QObject::tr("CoreNotFound"), QObject::tr("CoreFileNotFoundExplaination")); + return false; + } + } -bool v2Instance::start(QWidget *parent) -{ - if(this->vProcess->state() == QProcess::Running) { + bool v2Instance::start() + { + if (this->vProcess->state() == QProcess::Running) { + this->stop(); + } + + if (checkVCoreExes()) { + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + env.insert("V2RAY_LOCATION_ASSET", QDir::currentPath()); + this->vProcess->setProcessEnvironment(env); + this->vProcess->start("./v2ray", QStringList() << "-config" + << "config.json", + QIODevice::ReadWrite | QIODevice::Text); + this->vProcess->waitForStarted(); + processStatus = STARTED; + return true; + } else + return false; + } + + void v2Instance::stop() + { + this->vProcess->close(); + processStatus = STOPPED; + } + + v2Instance::~v2Instance() + { this->stop(); } - if (checkVCoreExes()) { - QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); - env.insert("V2RAY_LOCATION_ASSET", QDir::currentPath()); - this->vProcess->setProcessEnvironment(env); - this->vProcess->start("./v2ray", QStringList() << "-config" << "config.json", QIODevice::ReadWrite | QIODevice::Text); - this->vProcess->waitForStarted(); - QObject::connect(vProcess, SIGNAL(readyReadStandardOutput()), parent, SLOT(updateLog())); - return true; - } - else return false; -} -void v2Instance::stop() -{ - this->vProcess->close(); } diff --git a/src/vinteract.h b/src/vinteract.h deleted file mode 100644 index cdc9d5d6..00000000 --- a/src/vinteract.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef VINTERACT_H -#define VINTERACT_H -#include -#include - -bool validationCheck(QString path); - -class v2Instance -{ -public: - explicit v2Instance(); - bool start(QWidget *parent); - void stop(); - void restart(); - QProcess *vProcess; - ~v2Instance(); -}; - -#endif // VINTERACT_H diff --git a/src/vinteract.hpp b/src/vinteract.hpp new file mode 100644 index 00000000..ffd623c2 --- /dev/null +++ b/src/vinteract.hpp @@ -0,0 +1,33 @@ +#ifndef VINTERACT_H +#define VINTERACT_H +#include +#include + +namespace Hv2ray +{ + enum V2RAY_INSTANCE_STARTUP_STATUS { + STOPPED, + STARTING, + STARTED + }; + + class v2Instance + { + public: + explicit v2Instance(QWidget *parent); + + bool start(); + void stop(); + void restart(); + + static bool checkVCoreExes(); + static bool checkConfigFile(QString path); + ~v2Instance(); + QProcess *vProcess; + + private: + V2RAY_INSTANCE_STARTUP_STATUS processStatus; + }; +} + +#endif // VINTERACT_H diff --git a/src/w_ConnectionEditWindow.cpp b/src/w_ConnectionEditWindow.cpp new file mode 100644 index 00000000..aad4b0d8 --- /dev/null +++ b/src/w_ConnectionEditWindow.cpp @@ -0,0 +1,37 @@ +#include "w_ConnectionEditWindow.h" +#include "w_MainWindow.h" +#include +#include +#include +#include + +namespace Hv2ray +{ + namespace Ui + { + ConnectionEditWindow::ConnectionEditWindow(QWidget *parent) + : QDialog(parent) + , ui(new Ui_ConnectionEditWindow) + { + ui->setupUi(this); + ui->portLineEdit->setValidator(new QIntValidator()); + ui->alterLineEdit->setValidator(new QIntValidator()); + } + + ConnectionEditWindow::~ConnectionEditWindow() + { + delete ui; + } + + //void ConnectionEditWindow::getConfigFromDialog(Ui::ConnectionEditWindow *ui) + //{ + //this->host = ui->ipLineEdit->text(); + //this->port = ui->portLineEdit->text(); + //this->alias = ui->aliasLineEdit->text(); + //this->uuid = ui->idLineEdit->text(); + //this->alterid = ui->alterLineEdit->text(); + //this->security = ui->securityCombo->currentText(); + //this->isCustom = 0; + //} + } +} diff --git a/src/w_ConnectionEditWindow.h b/src/w_ConnectionEditWindow.h new file mode 100644 index 00000000..a37eeece --- /dev/null +++ b/src/w_ConnectionEditWindow.h @@ -0,0 +1,24 @@ +#ifndef CONFEDIT_H +#define CONFEDIT_H + +#include +#include "ui_w_ConnectionEditWindow.h" + +namespace Hv2ray +{ + namespace Ui + { + class ConnectionEditWindow : public QDialog + { + Q_OBJECT + + public: + explicit ConnectionEditWindow(QWidget *parent = nullptr); + ~ConnectionEditWindow(); + + private: + Ui_ConnectionEditWindow *ui; + }; + } +} +#endif // CONFEDIT_H diff --git a/src/ConnectionEditWindow.ui b/src/w_ConnectionEditWindow.ui similarity index 99% rename from src/ConnectionEditWindow.ui rename to src/w_ConnectionEditWindow.ui index 0bbb1213..d40f5d05 100644 --- a/src/ConnectionEditWindow.ui +++ b/src/w_ConnectionEditWindow.ui @@ -1,7 +1,7 @@ - ConnectionEditWindow - + Hv2ray::Ui::ConnectionEditWindow + 0 @@ -864,7 +864,7 @@ buttonBox accepted() - ConnectionEditWindow + Hv2ray::Ui::ConnectionEditWindow accept() @@ -880,7 +880,7 @@ buttonBox rejected() - ConnectionEditWindow + Hv2ray::Ui::ConnectionEditWindow reject() diff --git a/src/w_ImportConfig.cpp b/src/w_ImportConfig.cpp new file mode 100644 index 00000000..ad73ad1c --- /dev/null +++ b/src/w_ImportConfig.cpp @@ -0,0 +1,137 @@ +#include +#include +#include +#include +#include +#include + +#pragma push_macro("slots") +#undef slots +#include "Python.h" +#pragma pop_macro("slots") + +#include "HUtils.hpp" +#include "vinteract.hpp" +#include "w_ConnectionEditWindow.h" +#include "w_ImportConfig.h" + +using namespace Hv2ray; +namespace Hv2ray +{ + namespace Ui + { + ImportConfig::ImportConfig(QWidget *parent) + : QDialog(parent) + , ui(new Ui_ImportConfig) + { + ui->setupUi(this); + connect(this, SIGNAL(updateConfTable()), parentWidget(), SLOT(updateConfTable())); + } + + ImportConfig::~ImportConfig() + { + delete ui; + } + + void ImportConfig::on_pushButton_clicked() + { + QString dir = QFileDialog::getOpenFileName(this, tr("OpenConfigFile"), "~/"); + ui->fileLineTxt->setText(dir); + } + + void ImportConfig::savefromFile(QString path, QString alias) + { + Q_UNUSED(path) + Q_UNUSED(alias) + //Hv2Config newConfig; + //newConfig.alias = alias; + //QFile configFile(path); + //if(!configFile.open(QIODevice::ReadOnly)) { + // showWarnMessageBox(this, tr("ImportConfig"), tr("CannotOpenFile")); + // qDebug() << "ImportConfig::CannotOpenFile"; + // return; + //} + //QByteArray allData = configFile.readAll(); + //configFile.close(); + //QJsonDocument v2conf(QJsonDocument::fromJson(allData)); + //QJsonObject rootobj = v2conf.object(); + //QJsonObject outbound; + //if(rootobj.contains("outbounds")) { + // outbound = rootobj.value("outbounds").toArray().first().toObject(); + //} else { + // outbound = rootobj.value("outbound").toObject(); + //} + //QJsonObject vnext = switchJsonArrayObject(outbound.value("settings").toObject(), "vnext"); + //QJsonObject user = switchJsonArrayObject(vnext, "users"); + //newConfig.host = vnext.value("address").toString(); + //newConfig.port = QString::number(vnext.value("port").toInt()); + //newConfig.alterid = QString::number(user.value("alterId").toInt()); + //newConfig.uuid = user.value("id").toString(); + //newConfig.security = user.value("security").toString(); + //if (newConfig.security.isNull()) { + // newConfig.security = "auto"; + //} + //newConfig.isCustom = 1; + //int id = newConfig.save(); + //if(id < 0) + //{ + // showWarnMessageBox(this, tr("ImportConfig"), tr("SaveFailed")); + // qDebug() << "ImportConfig::SaveFailed"; + // return; + //} + //emit updateConfTable(); + //QString newFile = "conf/" + QString::number(id) + ".conf"; + //if(!QFile::copy(path, newFile)) { + // showWarnMessageBox(this, tr("ImportConfig"), tr("CannotCopyCustomConfig")); + // qDebug() << "ImportConfig::CannotCopyCustomConfig"; + //} + } + + void ImportConfig::on_buttonBox_accepted() + { + QString alias = ui->nameTxt->text(); + + if (ui->importSourceCombo->currentIndex() == 0) { // From File... + QString path = ui->fileLineTxt->text(); + bool isValid = v2Instance::checkConfigFile(path); + + if (isValid) { + savefromFile(path, alias); + } + } else { + QString vmess = ui->vmessConnectionStringTxt->toPlainText(); + Py_Initialize(); + assert(Py_IsInitialized()); + QString param = "--inbound socks:1080 " + vmess + " -o config.json.tmp"; + PyRun_SimpleString("import sys"); + PyRun_SimpleString("sys.path.append('./utils')"); + PyObject *pModule = PyImport_ImportModule("vmess2json"); + PyObject *pFunc = PyObject_GetAttrString(pModule, "main"); + PyObject *arg = PyTuple_New(1); + PyObject *arg1 = Py_BuildValue("s", param.toStdString().c_str()); + PyTuple_SetItem(arg, 0, arg1); + PyObject_CallObject(pFunc, arg); + Py_Finalize(); + + if (QFile::exists(QCoreApplication::applicationDirPath() + "/config.json.tmp")) { + ImportConfig *im = new ImportConfig(this->parentWidget()); + + if (v2Instance::checkConfigFile(QCoreApplication::applicationDirPath() + "/config.json.tmp")) { + im->savefromFile("config.json.tmp", alias); + } + + QFile::remove("config.json.tmp"); + } else { + Utils::showWarnMessageBox(this, tr("ImportConfig"), tr("CannotGenerateConfig")); + qDebug() << "ImportConfig::CannotGenerateConfig"; + } + } + + if (ui->useCurrentSettingRidBtn->isChecked()) { + // TODO: Use Current Settings... + } else { + // TODO: Override Inbound.... + } + } + } +} diff --git a/src/w_ImportConfig.h b/src/w_ImportConfig.h new file mode 100644 index 00000000..74f797e3 --- /dev/null +++ b/src/w_ImportConfig.h @@ -0,0 +1,32 @@ +#ifndef IMPORTCONF_H +#define IMPORTCONF_H + +#include +#include "ui_w_ImportConfig.h" + +namespace Hv2ray +{ + namespace Ui + { + class ImportConfig : public QDialog + { + Q_OBJECT + + public: + explicit ImportConfig(QWidget *parent = nullptr); + void savefromFile(QString path, QString alias); + ~ImportConfig(); + + private slots: + void on_pushButton_clicked(); + void on_buttonBox_accepted(); + signals: + void updateConfTable(); + + private: + Ui_ImportConfig *ui; + }; + } +} + +#endif // IMPORTCONF_H diff --git a/src/ImportConfig.ui b/src/w_ImportConfig.ui similarity index 96% rename from src/ImportConfig.ui rename to src/w_ImportConfig.ui index dc5f219f..bd473e73 100644 --- a/src/ImportConfig.ui +++ b/src/w_ImportConfig.ui @@ -1,7 +1,7 @@ - ImportConfig - + Hv2ray::Ui::ImportConfig + 0 @@ -162,7 +162,7 @@ buttonBox accepted() - ImportConfig + Hv2ray::Ui::ImportConfig accept() @@ -178,7 +178,7 @@ buttonBox rejected() - ImportConfig + Hv2ray::Ui::ImportConfig reject() diff --git a/src/w_MainWindow.cpp b/src/w_MainWindow.cpp new file mode 100644 index 00000000..1ff13ea6 --- /dev/null +++ b/src/w_MainWindow.cpp @@ -0,0 +1,282 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HUtils.hpp" +#include "vinteract.hpp" +#include "w_ConnectionEditWindow.h" +#include "w_ImportConfig.h" +#include "w_MainWindow.h" +#include "w_PrefrencesWindow.h" + +namespace Hv2ray +{ + namespace Ui + { + void MainWindow::CreateTrayIcon() + { + hTray = new QSystemTrayIcon(); + hTray->setToolTip(tr("Hv2ray")); + hTray->setIcon(this->windowIcon()); + connect(hTray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(on_activatedTray(QSystemTrayIcon::ActivationReason))); + QAction *actionShow = new QAction(this); + QAction *actionQuit = new QAction(this); + QAction *actionStart = new QAction(this); + QAction *actionRestart = new QAction(this); + QAction *actionStop = new QAction(this); + actionShow->setText(tr("#Hide")); + actionQuit->setText(tr("#Quit")); + actionStart->setText(tr("#Start")); + actionStop->setText(tr("#Stop")); + actionRestart->setText(tr("#Restart")); + actionStart->setEnabled(true); + actionStop->setEnabled(false); + actionRestart->setEnabled(false); + trayMenu->addAction(actionShow); + trayMenu->addSeparator(); + trayMenu->addAction(actionStart); + trayMenu->addAction(actionStop); + trayMenu->addAction(actionRestart); + trayMenu->addSeparator(); + trayMenu->addAction(actionQuit); + connect(actionShow, SIGNAL(triggered()), this, SLOT(toggleMainWindowVisibility())); + connect(actionStart, SIGNAL(triggered()), this, SLOT(on_startButton_clicked())); + connect(actionStop, SIGNAL(triggered()), this, SLOT(on_stopButton_clicked())); + connect(actionRestart, SIGNAL(triggered()), this, SLOT(on_restartButton_clicked())); + connect(actionQuit, SIGNAL(triggered()), this, SLOT(quit())); + hTray->setContextMenu(trayMenu); + hTray->show(); + } + + MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui_MainWindow) + { + this->setWindowIcon(QIcon(":/icons/Hv2ray.ico")); + ui->setupUi(this); + UpdateConfigTable(); + // ui->configTable->setContextMenuPolicy(Qt::CustomContextMenu); + // connect(ui->configTable, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + this->vinstance = new v2Instance(this); + CreateTrayIcon(); + + if (QFileInfo("config.json").exists()) { + vinstance->start(); + } + + // QAction *select = new QAction("Select", ui->configTable); + // QAction *del = new QAction("Delete", ui->configTable); + // QAction *rename = new QAction("Rename", ui->configTable); + // popMenu->addAction(select); + // popMenu->addAction(del); + // popMenu->addAction(rename); + // connect(select, SIGNAL(triggered()), this, SLOT(select_triggered())); + // connect(del, SIGNAL(triggered()), this, SLOT(delConf())); + // connect(rename, SIGNAL(triggered()), this, SLOT(renameRow())); + // connect(ui->logText, SIGNAL(textChanged()), this, SLOT(scrollToBottom())); + // bar = ui->logText->verticalScrollBar(); + } + + MainWindow::~MainWindow() + { + hTray->hide(); + delete this->hTray; + delete this->vinstance; + delete ui; + } + + void MainWindow::on_actionEdit_triggered() + { + ConnectionEditWindow *e = new ConnectionEditWindow(this); + e->setAttribute(Qt::WA_DeleteOnClose); + e->show(); + } + + void MainWindow::on_actionExisting_config_triggered() + { + ImportConfig *f = new ImportConfig(this); + f->setAttribute(Qt::WA_DeleteOnClose); + f->show(); + } + + void MainWindow::showMenu(QPoint pos) + { + Q_UNUSED(pos) + // if(ui->configTable->indexAt(pos).column() != -1) { + // popMenu->move(cursor().pos()); + // popMenu->show(); + // } + } + void MainWindow::select_triggered() + { + // int row = ui->configTable->selectionModel()->currentIndex().row(); + // int idIntable = ui->configTable->model()->data(ui->configTable->model()->index(row, 4)).toInt(); + // this->geneConf(idIntable); + // if(this->v2Inst->v2Process->state() == QProcess::Running) { + // this->on_restartButton_clicked(); + // } + } + + void MainWindow::DeleteConfig() + { + } + void MainWindow::UpdateConfigTable() + { + } + void MainWindow::GenerateConfig(int idIntable) + { + Q_UNUSED(idIntable) + //Hv2Config tmpConf; + //emit UpdateConfigTable(); + //if (tmpConf.isCustom == 1) { + // QString src = "conf/" + QString::number(idIntable) + ".conf"; + // overrideInbounds(src); + // if (QFile::exists("config.json")) { + // QFile::remove("config.json"); + // } + // QFile::copy(src, "config.json"); + //} else { + // // TODO: Config generator + //} + } + void MainWindow::UpdateLog() + { + ui->logText->insertPlainText(this->vinstance->vProcess->readAllStandardOutput()); + } + + void MainWindow::on_startButton_clicked() + { + ui->logText->clear(); + bool startFlag = this->vinstance->start(); + trayMenu->actions()[2]->setEnabled(!startFlag); + trayMenu->actions()[3]->setEnabled(startFlag); + trayMenu->actions()[4]->setEnabled(startFlag); + } + + void MainWindow::on_stopButton_clicked() + { + this->vinstance->stop(); + ui->logText->clear(); + trayMenu->actions()[2]->setEnabled(true); + trayMenu->actions()[3]->setEnabled(false); + trayMenu->actions()[4]->setEnabled(false); + } + + void MainWindow::on_restartButton_clicked() + { + on_stopButton_clicked(); + on_startButton_clicked(); + } + + void MainWindow::on_clbutton_clicked() + { + ui->logText->clear(); + } + + void MainWindow::on_rtButton_clicked() + { + emit UpdateConfigTable(); + } + + void MainWindow::closeEvent(QCloseEvent *event) + { + this->hide(); + event->ignore(); + } + + void MainWindow::on_activatedTray(QSystemTrayIcon::ActivationReason reason) + { + switch (reason) { + case QSystemTrayIcon::Trigger: + // Toggle Show/Hide +#ifndef __APPLE__ + // Every single click will trigger the Show/Hide toggling. + // So, as a hobby on common MacOS Apps, we 'don't toggle visibility on click'. + toggleMainWindowVisibility(); +#endif + break; + + case QSystemTrayIcon::DoubleClick: + if (this->isHidden()) { + this->show(); + } + + break; + + case QSystemTrayIcon::MiddleClick: + + // TODO: Check if an alert message box is present. + // If so, do nothing but please wait for the message box to be closed. + if (this->vinstance->vProcess->state() == QProcess::ProcessState::Running) { + on_stopButton_clicked(); + } else { + on_startButton_clicked(); + } + + break; + + case QSystemTrayIcon::Unknown: + break; + + case QSystemTrayIcon::Context: + break; + } + } + + void MainWindow::toggleMainWindowVisibility() + { + if (this->isHidden()) { + this->show(); + trayMenu->actions()[0]->setText(tr("#Hide")); + } else { + this->hide(); + trayMenu->actions()[0]->setText(tr("#Show")); + } + } + + void MainWindow::quit() + { + QCoreApplication::quit(); + } + + void MainWindow::on_actionExit_triggered() + { + quit(); + } + + void MainWindow::renameRow() + { + // QString text = QInputDialog::getText(this, "Rename config", "New name:", QLineEdit::Normal); + // int row = ui->configTable->currentIndex().row(); + // int idIntable = ui->configTable->model()->data(ui->configTable->model()->index(row, 4)).toInt(); + // SQLiteDB mydb; + // QString updateString = "update confs set alias = '" + text + "' where id = " + QString::number(idIntable); + // mydb.DoQuery(updateString); + // emit updateConfTable(); + } + + void MainWindow::scrollToBottom() + { + bar->setValue(bar->maximum()); + } + + void MainWindow::on_actionPreferences_triggered() + { + PrefrencesWindow *v = new PrefrencesWindow(this); + v->setAttribute(Qt::WA_DeleteOnClose); + v->show(); + } + + void MainWindow::on_pushButton_clicked() + { + auto confedit = new ConnectionEditWindow(); + confedit->show(); + } + } +} diff --git a/src/w_MainWindow.h b/src/w_MainWindow.h new file mode 100644 index 00000000..a9e32b52 --- /dev/null +++ b/src/w_MainWindow.h @@ -0,0 +1,63 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H +#include "w_ConnectionEditWindow.h" +#include +#include +#include +#include + +#include "ui_w_MainWindow.h" +#include "vinteract.hpp" +#include "V2ConfigObjects.hpp" + +namespace Hv2ray +{ + namespace Ui + { + class MainWindow : public QMainWindow + { + Q_OBJECT + public: + explicit MainWindow(QWidget *parent = nullptr); + v2Instance *vinstance; + QSystemTrayIcon *hTray; + QMenu *trayMenu = new QMenu(this); + QMenu *popMenu = new QMenu(this); + QScrollBar *bar; + ~MainWindow(); + + private slots: + void on_restartButton_clicked(); + void on_actionEdit_triggered(); + void on_actionExisting_config_triggered(); + void UpdateConfigTable(); + void DeleteConfig(); + void showMenu(QPoint pos); + void UpdateLog(); + void on_startButton_clicked(); + void on_stopButton_clicked(); + void select_triggered(); + void on_clbutton_clicked(); + void on_rtButton_clicked(); + void GenerateConfig(int idIntable); + void on_activatedTray(QSystemTrayIcon::ActivationReason reason); + void toggleMainWindowVisibility(); + void quit(); + void on_actionExit_triggered(); + void renameRow(); + void scrollToBottom(); + void on_actionPreferences_triggered(); + + void on_pushButton_clicked(); + + private: + Ui_MainWindow *ui; + void closeEvent(QCloseEvent *); + void createTrayAction(); + void CreateTrayIcon(); + }; + } +} + + +#endif // MAINWINDOW_H diff --git a/src/MainWindow.ui b/src/w_MainWindow.ui similarity index 97% rename from src/MainWindow.ui rename to src/w_MainWindow.ui index 7d890a69..8d2640b0 100644 --- a/src/MainWindow.ui +++ b/src/w_MainWindow.ui @@ -1,7 +1,7 @@ - MainWindow - + Hv2ray::Ui::MainWindow + 0 @@ -143,6 +143,9 @@ QFormLayout::ExpandingFieldsGrow + + 10 + @@ -199,6 +202,13 @@ + + + + #ConnectionSettings + + + @@ -212,13 +222,6 @@ - - - - #ConnectionSettings - - - @@ -241,7 +244,7 @@ 0 0 568 - 21 + 29 @@ -291,7 +294,6 @@ restartButton clearlogButton listWidget - pushButton logText diff --git a/src/w_PrefrencesWindow.cpp b/src/w_PrefrencesWindow.cpp new file mode 100644 index 00000000..660c208b --- /dev/null +++ b/src/w_PrefrencesWindow.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "HUtils.hpp" +#include "vinteract.hpp" +#include "w_PrefrencesWindow.h" + +#ifndef _WIN32 +#include +#endif + +using namespace Hv2ray; +using namespace Utils; + +namespace Hv2ray +{ + namespace Ui + { + PrefrencesWindow::PrefrencesWindow(QWidget *parent) : QDialog(parent), CurrentConfig(), ui(new Ui_PrefrencesWindow) + { + ui->setupUi(this); + CurrentConfig = GetGlobalConfig(); + ui->languageComboBox->setCurrentText(QString::fromStdString(CurrentConfig.language)); + ui->runAsRootCheckBox->setChecked(CurrentConfig.runAsRoot); + ui->logLevelCheckBox->setCurrentText(QString::fromStdString(CurrentConfig.logLevel)); + // + ui->httpCB->setChecked(CurrentConfig.httpSetting.enabled); + ui->httpPortLE->setText(QString::fromStdString(to_string(CurrentConfig.httpSetting.port))); + ui->httpAuthCB->setChecked(CurrentConfig.httpSetting.useAuthentication); + ui->httpAuthUsernameTxt->setText(QString::fromStdString(CurrentConfig.httpSetting.authUsername)); + ui->httpAuthPasswordTxt->setText(QString::fromStdString(CurrentConfig.httpSetting.authPassword)); + ui->httpPortLE->setValidator(new QIntValidator()); + // + ui->socksCB->setChecked(CurrentConfig.socksSetting.enabled); + ui->socksPortLE->setText(QString::fromStdString(to_string(CurrentConfig.socksSetting.port))); + ui->socksAuthCB->setChecked(CurrentConfig.socksSetting.useAuthentication); + ui->socksAuthUsernameTxt->setText(QString::fromStdString(CurrentConfig.socksSetting.authUsername)); + ui->socksAuthPasswordTxt->setText(QString::fromStdString(CurrentConfig.socksSetting.authPassword)); + // + ui->httpPortLE->setValidator(new QIntValidator()); + ui->socksPortLE->setValidator(new QIntValidator()); + parentMW = parent; + } + + PrefrencesWindow::~PrefrencesWindow() + { + delete ui; + } + + void PrefrencesWindow::on_buttonBox_accepted() + { + if (v2Instance::checkVCoreExes()) { + if (ui->httpPortLE->text().toInt() != ui->socksPortLE->text().toInt()) { +#ifndef _WIN32 + // Set UID and GID in *nix + // The file is actually not here + QFileInfo v2rayCoreExeFile("v2ray"); + + if (ui->runAsRootCheckBox->isChecked() && v2rayCoreExeFile.ownerId() != 0) { + QProcess::execute("pkexec", QStringList() << "bash" + << "-c" + << "chown root:root " + QCoreApplication::applicationDirPath() + "/v2ray" + ";chmod +s " + QCoreApplication::applicationDirPath() + "/v2ray"); + } else if (!ui->runAsRootCheckBox->isChecked() && v2rayCoreExeFile.ownerId() == 0) { + uid_t uid = getuid(); + gid_t gid = getgid(); + QProcess::execute("pkexec", QStringList() << "chown" << QString::number(uid) + ":" + QString::number(gid) << QCoreApplication::applicationDirPath() + "/v2ray"); + } + + v2rayCoreExeFile.refresh(); + //rootObj.insert("v2suidEnabled", v2rayCoreExeFile.ownerId() == 0); +#else + // No such uid gid thing on windows.... +#endif + } else { + showWarnMessageBox(this, tr("Prefrences"), tr("PortNumbersCannotBeSame")); + } + } + } + + void PrefrencesWindow::on_httpCB_stateChanged(int checked) + { + if (checked != Qt::Checked) { + ui->httpPortLE->setDisabled(true); + } else { + ui->httpPortLE->setEnabled(true); + } + } + + void PrefrencesWindow::on_socksCB_stateChanged(int checked) + { + if (checked != Qt::Checked) { + ui->socksPortLE->setEnabled(false); + } else { + ui->socksPortLE->setEnabled(true); + } + } + + void PrefrencesWindow::on_httpAuthCB_stateChanged(int checked) + { + if (checked) { + } + } + + void PrefrencesWindow::on_runAsRootCheckBox_stateChanged(int arg1) + { + Q_UNUSED(arg1) +#ifdef _WIN32 + showWarnMessageBox(this, tr("Prefrences"), tr("RunAsRootNotOnWindows")); +#endif + } + } +} diff --git a/src/w_PrefrencesWindow.h b/src/w_PrefrencesWindow.h new file mode 100644 index 00000000..7a7e8579 --- /dev/null +++ b/src/w_PrefrencesWindow.h @@ -0,0 +1,36 @@ +#ifndef HVCONF_H +#define HVCONF_H + +#include +#include +#include "HConfigObjects.hpp" + +namespace Hv2ray +{ + namespace Ui + { + class PrefrencesWindow : public QDialog + { + Q_OBJECT + + public: + explicit PrefrencesWindow(QWidget *parent = nullptr); + ~PrefrencesWindow(); + QWidget *parentMW; + + private slots: + void on_buttonBox_accepted(); + void on_httpCB_stateChanged(int arg1); + void on_socksCB_stateChanged(int arg1); + + void on_httpAuthCB_stateChanged(int arg1); + + void on_runAsRootCheckBox_stateChanged(int arg1); + + private: + Hv2ray::HConfigModels::Hv2Config CurrentConfig; + Ui_PrefrencesWindow *ui; + }; + } +} +#endif // HVCONF_H diff --git a/src/PrefrencesWindow.ui b/src/w_PrefrencesWindow.ui similarity index 98% rename from src/PrefrencesWindow.ui rename to src/w_PrefrencesWindow.ui index 12586791..28f5cd5f 100644 --- a/src/PrefrencesWindow.ui +++ b/src/w_PrefrencesWindow.ui @@ -1,7 +1,7 @@ - PrefrencesWindow - + Hv2ray::Ui::PrefrencesWindow + 0 @@ -345,7 +345,7 @@ buttonBox accepted() - PrefrencesWindow + Hv2ray::Ui::PrefrencesWindow accept() @@ -361,7 +361,7 @@ buttonBox rejected() - PrefrencesWindow + Hv2ray::Ui::PrefrencesWindow reject() diff --git a/translations/en-US.ts b/translations/en-US.ts index f0cdb3e3..4d8ba137 100644 --- a/translations/en-US.ts +++ b/translations/en-US.ts @@ -4,655 +4,1091 @@ ConnectionEditWindow - + #ConnectionSettings + Connection Settings + + + #Host + Host + + + #Port + Port + + + #Name + Name + + + #UUID + UUID + + + #AlterID + Alter ID + + + #Security + Security Settings + + + auto + Auto + + + aes-128-gcm + aes-128-gcm + + + chacha20-poly1305 + chacha20-poly1305 + + + none + Do not use + + + #Transport + Transport Settings + + + tcp (TCP) + tcp (TCP) + + + http (HTTP) + http (HTTP) + + + ws (WebSocket) + ws (WebSocket) + + + kcp (mKCP) + kcp (mKCP) + + + domainsocket (Domain Socket) + domainsocket (Domain Socket) + + + quic (Quick UDP Internet Connection) + quic (Quick UDP Internet Connection) + + + #TransportSettings + Transport Settings + + + TCP + TCP + + + http + HTTP + + + #Type + Type + + + #Request + Request + + + #InsertDefaultContent + Insert Default Content + + + #Response + Response + + + HTTP + HTTP + + + #Path + Path + + + WebSocket + WebSocket + + + #Headers + Headers + + + mKCP + mKCP + + + #MTU + MTU + + + #TTI (ms) + TTI (ms) + + + #UplinkCapacity (MB/s) + Uplink Capacity (MB/s) + + + #Congestion + Congestion Control + + + #Enabled + Enabled + + + #DownlinkCapacity (MB/s) + Downlink Capacity (MB/s) + + + #ReadBufferSize (MB) + Read Buffer Size (MB) + + + #WriteBufferSize (MB) + Write Buffer Size (MB) + + + #Header + Headers + + + srtp (SRTP, FaceTime) + srtp (SRTP, FaceTime) + + + utp (BitTorrent) + utp (BitTorrent) + + + wechat-video (WeChat Video Message) + wechat-video (WeChat Video Message) + + + dtls (DTLS 1.2) + dtls (DTLS 1.2) + + + wireguard (WireGuard fake packets) + wireguard (WireGuard fake packets) + + + DomainSocket + DomainSocket + + + QUIC + QUIC + + + #Key + Key + + + + Hv2ray::Ui::ConnectionEditWindow + + #ConnectionSettings Connection Settings - - + + #Host Host - + #Port Port - + #Name Name - + #UUID UUID - + #AlterID Alter ID - - + + #Security Security Settings - + auto Auto - - + + aes-128-gcm aes-128-gcm - - + + chacha20-poly1305 chacha20-poly1305 - - - - - + + + + + none Do not use - + #Transport Transport Settings - + tcp (TCP) tcp (TCP) - + http (HTTP) http (HTTP) - + ws (WebSocket) ws (WebSocket) - + kcp (mKCP) kcp (mKCP) - + domainsocket (Domain Socket) domainsocket (Domain Socket) - + quic (Quick UDP Internet Connection) quic (Quick UDP Internet Connection) - + #TransportSettings Transport Settings - + TCP TCP - + http HTTP - - - + + + #Type Type - + #Request Request - - + + #InsertDefaultContent Insert Default Content - + #Response Response - + HTTP HTTP - - - + + + #Path Path - + WebSocket WebSocket - - + + #Headers Headers - + mKCP mKCP - + #MTU MTU - + #TTI (ms) TTI (ms) - + #UplinkCapacity (MB/s) Uplink Capacity (MB/s) - + #Congestion Congestion Control - + #Enabled Enabled - + #DownlinkCapacity (MB/s) Downlink Capacity (MB/s) - + #ReadBufferSize (MB) Read Buffer Size (MB) - + #WriteBufferSize (MB) Write Buffer Size (MB) - - + + #Header Headers - - + + srtp (SRTP, FaceTime) srtp (SRTP, FaceTime) - - + + utp (BitTorrent) utp (BitTorrent) - - + + wechat-video (WeChat Video Message) wechat-video (WeChat Video Message) - - + + dtls (DTLS 1.2) dtls (DTLS 1.2) - - + + wireguard (WireGuard fake packets) wireguard (WireGuard fake packets) - + DomainSocket DomainSocket - + QUIC QUIC - + #Key Key - ImportConfig + Hv2ray::Ui::ImportConfig - + Import file Import file - + #ImportFrom Import From - + Existing File Existing File - + VMess Connection String VMess Connection String - + #FromFile From file - + #Path Path - + #SelectFile Select File - + #Name Name - + #Inbound Inbound Settings - + #UseCurrent Use Current Settings - + #UseImported Use Imported Inbound Settings - + #From VMess Connection String From VMess Connection String - + #VMess Connection String VMess Connection String - + OpenConfigFile Open Config File - - - - + ImportConfig Import Config - - CannotOpenFile - Cannot open file - - - - SaveFailed - Failed to save - - - - CannotCopyCustomConfig - Failed to copy config file - - - + CannotGenerateConfig Failed to generate config file - MainWindow + Hv2ray::Ui::MainWindow - - + + Hv2ray Hv2ray - - + + #Start Start - - + + #Stop Stop - - + + #Restart Restart - + #ClearLog Clear Log - + #HostList Host List - + #ConfigDetail Detailed Config Info - + #Host Host - + #Port Port - + #UUID UUID - + #Transport Transport Settings - - + + #ConnectionSettings Connection Settings - + #File File - + #NewConnection New Connection - + #ManuallyInput Manually Input Config - + #ImportConnection Import Config File - + #Exit Exit - + #Preferences Preferences - - + + #Hide Hide - + #Quit Quit - + #Show Show - PrefrencesWindow + Hv2ray::Ui::PrefrencesWindow - - - + + + Prefrences Preferences - - zh-CN - - - - - en-US - - - - + #General General - + #Language Language - + + zh-CN + + + + + en-US + + + + #RunAsRoot Run v2ray as root - - - - - - - + + + + + + + #Enabled Enabled - + #LogLevel Log Level - + debug Debug - + info Info - + warning Warning - + error Error - + none Do not use - + #MuxCool Mux Settings - + #Concurrency Maximum Connections - + #InboundSettings Inbound Settings - - - #Port - Port - - - - - #Username - Username - - - - - #Auth - Authentication - - - - - #Password - Password - - - + HTTP HTTP - + + + #Port + Port + + + 8080 - + + + #Username + Username + + + + + #Auth + Authentication + + + + + #Password + Password + + + SOCKS SOCKS - + 9001 - #Prefrences - Preferences + Preferences - #CannotOpenConfigFile - Cannot open config file + Cannot open config file - + PortNumbersCannotBeSame Port numbers cannot be the same - + RunAsRootNotOnWindows Run as root is not avaliable on Windows Platform + + ImportConfig + + Import file + Import file + + + #ImportFrom + Import From + + + Existing File + Existing File + + + VMess Connection String + VMess Connection String + + + #FromFile + From file + + + #Path + Path + + + #SelectFile + Select File + + + #Name + Name + + + #Inbound + Inbound Settings + + + #UseCurrent + Use Current Settings + + + #UseImported + Use Imported Inbound Settings + + + #From VMess Connection String + From VMess Connection String + + + #VMess Connection String + VMess Connection String + + + OpenConfigFile + Open Config File + + + ImportConfig + Import Config + + + CannotOpenFile + Cannot open file + + + SaveFailed + Failed to save + + + CannotCopyCustomConfig + Failed to copy config file + + + CannotGenerateConfig + Failed to generate config file + + + + MainWindow + + Hv2ray + Hv2ray + + + #Start + Start + + + #Stop + Stop + + + #Restart + Restart + + + #ClearLog + Clear Log + + + #HostList + Host List + + + #ConfigDetail + Detailed Config Info + + + #Host + Host + + + #Port + Port + + + #UUID + UUID + + + #Transport + Transport Settings + + + #ConnectionSettings + Connection Settings + + + #File + File + + + #NewConnection + New Connection + + + #ManuallyInput + Manually Input Config + + + #ImportConnection + Import Config File + + + #Exit + Exit + + + #Preferences + Preferences + + + #Hide + Hide + + + #Quit + Quit + + + #Show + Show + + + + PrefrencesWindow + + Prefrences + Preferences + + + #General + General + + + #Language + Language + + + #RunAsRoot + Run v2ray as root + + + #Enabled + Enabled + + + #LogLevel + Log Level + + + debug + Debug + + + info + Info + + + warning + Warning + + + error + Error + + + none + Do not use + + + #MuxCool + Mux Settings + + + #Concurrency + Maximum Connections + + + #InboundSettings + Inbound Settings + + + #Port + Port + + + #Username + Username + + + #Auth + Authentication + + + #Password + Password + + + HTTP + HTTP + + + SOCKS + SOCKS + + + #Prefrences + Preferences + + + #CannotOpenConfigFile + Cannot open config file + + + PortNumbersCannotBeSame + Port numbers cannot be the same + + + RunAsRootNotOnWindows + Run as root is not avaliable on Windows Platform + + QObject - + Hv2Ray Hv2ray - + AnotherInstanceRunning Another instance is already running - + CoreNotFound Core files are not found - + CoreFileNotFoundExplaination Please go to the official website or Github to download the latest release of v2ray executable - + ConfigurationError Configuration Error diff --git a/translations/zh-CN.ts b/translations/zh-CN.ts index d38e304d..1e0f5a54 100644 --- a/translations/zh-CN.ts +++ b/translations/zh-CN.ts @@ -4,655 +4,1091 @@ ConnectionEditWindow - + #ConnectionSettings + 连接设置 + + + #Host + 域名 + + + #Port + 端口 + + + #Name + 名称 + + + #UUID + UUID + + + #AlterID + Alter ID + + + #Security + 安全设置 + + + auto + 自动 + + + aes-128-gcm + aes-128-gcm + + + chacha20-poly1305 + chacha20-poly1305 + + + none + 不使用 + + + #Transport + 传输设置 + + + tcp (TCP) + tcp (TCP) + + + http (HTTP) + http (HTTP) + + + ws (WebSocket) + ws (WebSocket) + + + kcp (mKCP) + kcp (mKCP) + + + domainsocket (Domain Socket) + domainsocket (Domain Socket) + + + quic (Quick UDP Internet Connection) + quic (Quick UDP Internet Connection) + + + #TransportSettings + 传输设置 + + + TCP + TCP + + + http + HTTP + + + #Type + 类型 + + + #Request + 请求 + + + #InsertDefaultContent + 默认值 + + + #Response + 相应 + + + HTTP + HTTP + + + #Path + 路径 + + + WebSocket + WebSocket + + + #Headers + 头伪装 + + + mKCP + mKCP + + + #MTU + 最大传输单元 + + + #TTI (ms) + 传输时间间隔 + + + #UplinkCapacity (MB/s) + 上行链路容量 (MB/s) + + + #Congestion + 拥塞控制 + + + #Enabled + 启用 + + + #DownlinkCapacity (MB/s) + 下行链路容量 (MB/s) + + + #ReadBufferSize (MB) + 读取缓冲区大小 (MB) + + + #WriteBufferSize (MB) + 写入缓冲区大小 (MB) + + + #Header + 头伪装 + + + srtp (SRTP, FaceTime) + srtp (SRTP, FaceTime) + + + utp (BitTorrent) + utp (BitTorrent) + + + wechat-video (WeChat Video Message) + wechat-video (WeChat Video Message) + + + dtls (DTLS 1.2) + dtls (DTLS 1.2) + + + wireguard (WireGuard fake packets) + wireguard (WireGuard fake packets) + + + DomainSocket + DomainSocket + + + QUIC + QUIC + + + #Key + 密钥 + + + + Hv2ray::Ui::ConnectionEditWindow + + #ConnectionSettings 连接设置 - - + + #Host 域名 - + #Port 端口 - + #Name 名称 - + #UUID UUID - + #AlterID Alter ID - - + + #Security 安全设置 - + auto 自动 - - + + aes-128-gcm aes-128-gcm - - + + chacha20-poly1305 chacha20-poly1305 - - - - - + + + + + none 不使用 - + #Transport 传输设置 - + tcp (TCP) tcp (TCP) - + http (HTTP) http (HTTP) - + ws (WebSocket) ws (WebSocket) - + kcp (mKCP) kcp (mKCP) - + domainsocket (Domain Socket) domainsocket (Domain Socket) - + quic (Quick UDP Internet Connection) quic (Quick UDP Internet Connection) - + #TransportSettings 传输设置 - + TCP TCP - + http HTTP - - - + + + #Type 类型 - + #Request 请求 - - + + #InsertDefaultContent 默认值 - + #Response 相应 - + HTTP HTTP - - - + + + #Path 路径 - + WebSocket WebSocket - - + + #Headers 头伪装 - + mKCP mKCP - + #MTU 最大传输单元 - + #TTI (ms) 传输时间间隔 - + #UplinkCapacity (MB/s) 上行链路容量 (MB/s) - + #Congestion 拥塞控制 - + #Enabled 启用 - + #DownlinkCapacity (MB/s) 下行链路容量 (MB/s) - + #ReadBufferSize (MB) 读取缓冲区大小 (MB) - + #WriteBufferSize (MB) 写入缓冲区大小 (MB) - - + + #Header 头伪装 - - + + srtp (SRTP, FaceTime) srtp (SRTP, FaceTime) - - + + utp (BitTorrent) utp (BitTorrent) - - + + wechat-video (WeChat Video Message) wechat-video (WeChat Video Message) - - + + dtls (DTLS 1.2) dtls (DTLS 1.2) - - + + wireguard (WireGuard fake packets) wireguard (WireGuard fake packets) - + DomainSocket DomainSocket - + QUIC QUIC - + #Key 密钥 - ImportConfig + Hv2ray::Ui::ImportConfig - + Import file 导入文件 - + #ImportFrom 导入源 - + Existing File 现有文件 - + VMess Connection String VMess 连接字符串 - + #FromFile 从文件 - + #Path 路径 - + #SelectFile 选择文件 - + #Name 名称 - + #Inbound 入站设置 - + #UseCurrent 使用现有设置 - + #UseImported 使用导入的设置 - + #From VMess Connection String 从 VMess 连接字符串 - + #VMess Connection String VMess 连接字符串 - + OpenConfigFile 打开配置文件 - - - - + ImportConfig 导入配置 - - CannotOpenFile - 无法打开文件 - - - - SaveFailed - 保存失败 - - - - CannotCopyCustomConfig - 复制配置文件失败 - - - + CannotGenerateConfig 无法生成配置文件 - MainWindow + Hv2ray::Ui::MainWindow - - + + Hv2ray Hv2ray - - + + #Start 启动 - - + + #Stop 停止 - - + + #Restart 重新启动 - + #ClearLog 清除日志 - + #HostList 服务器列表 - + #ConfigDetail 配置详细信息 - + #Host 域名 - + #Port 端口 - + #UUID UUID - + #Transport 传输设置 - - + + #ConnectionSettings 连接设置 - + #File 文件 - + #NewConnection 新建连接 - + #ManuallyInput 手动输入配置 - + #ImportConnection 导入配置文件 - + #Exit 退出 - + #Preferences 首选项 - - + + #Hide 隐藏 - + #Quit 退出 - + #Show 显示 - PrefrencesWindow + Hv2ray::Ui::PrefrencesWindow - - - + + + Prefrences 首选项 - - zh-CN - - - - - en-US - - - - + #General 一般 - + #Language 语言 - + + zh-CN + + + + + en-US + + + + #RunAsRoot 使用 root 启动 - - - - - - - + + + + + + + #Enabled 启用 - + #LogLevel 日志等级 - + debug 调试 - + info 信息 - + warning 警告 - + error 错误 - + none 不使用 - + #MuxCool Mux 设置 - + #Concurrency 最大并发连接数 - + #InboundSettings 入站设置 - - - #Port - 端口 - - - - - #Username - 用户名 - - - - - #Auth - 鉴权 - - - - - #Password - 密码 - - - + HTTP HTTP - + + + #Port + 端口 + + + 8080 - + + + #Username + 用户名 + + + + + #Auth + 鉴权 + + + + + #Password + 密码 + + + SOCKS SOCKS - + 9001 - #Prefrences - 首选项 + 首选项 - #CannotOpenConfigFile - 无法打开配置文件 + 无法打开配置文件 - + PortNumbersCannotBeSame 端口号不能相同 - + RunAsRootNotOnWindows Windows 平台不支持这个选项 + + ImportConfig + + Import file + 导入文件 + + + #ImportFrom + 导入源 + + + Existing File + 现有文件 + + + VMess Connection String + VMess 连接字符串 + + + #FromFile + 从文件 + + + #Path + 路径 + + + #SelectFile + 选择文件 + + + #Name + 名称 + + + #Inbound + 入站设置 + + + #UseCurrent + 使用现有设置 + + + #UseImported + 使用导入的设置 + + + #From VMess Connection String + 从 VMess 连接字符串 + + + #VMess Connection String + VMess 连接字符串 + + + OpenConfigFile + 打开配置文件 + + + ImportConfig + 导入配置 + + + CannotOpenFile + 无法打开文件 + + + SaveFailed + 保存失败 + + + CannotCopyCustomConfig + 复制配置文件失败 + + + CannotGenerateConfig + 无法生成配置文件 + + + + MainWindow + + Hv2ray + Hv2ray + + + #Start + 启动 + + + #Stop + 停止 + + + #Restart + 重新启动 + + + #ClearLog + 清除日志 + + + #HostList + 服务器列表 + + + #ConfigDetail + 配置详细信息 + + + #Host + 域名 + + + #Port + 端口 + + + #UUID + UUID + + + #Transport + 传输设置 + + + #ConnectionSettings + 连接设置 + + + #File + 文件 + + + #NewConnection + 新建连接 + + + #ManuallyInput + 手动输入配置 + + + #ImportConnection + 导入配置文件 + + + #Exit + 退出 + + + #Preferences + 首选项 + + + #Hide + 隐藏 + + + #Quit + 退出 + + + #Show + 显示 + + + + PrefrencesWindow + + Prefrences + 首选项 + + + #General + 一般 + + + #Language + 语言 + + + #RunAsRoot + 使用 root 启动 + + + #Enabled + 启用 + + + #LogLevel + 日志等级 + + + debug + 调试 + + + info + 信息 + + + warning + 警告 + + + error + 错误 + + + none + 不使用 + + + #MuxCool + Mux 设置 + + + #Concurrency + 最大并发连接数 + + + #InboundSettings + 入站设置 + + + #Port + 端口 + + + #Username + 用户名 + + + #Auth + 鉴权 + + + #Password + 密码 + + + HTTP + HTTP + + + SOCKS + SOCKS + + + #Prefrences + 首选项 + + + #CannotOpenConfigFile + 无法打开配置文件 + + + PortNumbersCannotBeSame + 端口号不能相同 + + + RunAsRootNotOnWindows + Windows 平台不支持这个选项 + + QObject - + Hv2Ray Hv2ray - + AnotherInstanceRunning 另一个实例正在运行 - + CoreNotFound 核心文件未找到 - + CoreFileNotFoundExplaination 请到官网或 GitHub 下载最新版本的 v2ray 主程序 - + ConfigurationError 配置出错