diff --git a/.github/ISSUE_TEMPLATE/bug---english.md b/.github/ISSUE_TEMPLATE/bug---english.md index e3aafaf3..1c769dc1 100644 --- a/.github/ISSUE_TEMPLATE/bug---english.md +++ b/.github/ISSUE_TEMPLATE/bug---english.md @@ -45,7 +45,7 @@ Please paste your Qv2ray log here: -### Open Preferences -> Aabout, and enter the following info +### Open Preferences -> About, and enter the following info ``` Version: @@ -82,3 +82,5 @@ Extra build info: ## Additional Info + +*Please hide your server address and UUID if you wish to post the vmess string or your connection setting.* diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md index f6ab4ae3..fef1911c 100644 --- a/.github/ISSUE_TEMPLATE/bug.md +++ b/.github/ISSUE_TEMPLATE/bug.md @@ -82,3 +82,6 @@ assignees: '' ## 附加信息 + +*请注意打码隐私相关信息。* + diff --git a/.github/workflows/build-qv2ray-cmake.yml b/.github/workflows/build-qv2ray-cmake.yml index af4e0b14..38cde3bd 100644 --- a/.github/workflows/build-qv2ray-cmake.yml +++ b/.github/workflows/build-qv2ray-cmake.yml @@ -74,7 +74,7 @@ jobs: path: ../Qt key: QtCache-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.qt_version }} - name: Installing Qt - ${{ matrix.arch }} - uses: jurplel/install-qt-action@v2.5.0 + uses: jurplel/install-qt-action@v2 with: version: ${{ matrix.qt_version }} arch: ${{ matrix.qtarch }} @@ -118,6 +118,7 @@ jobs: shell: bash if: matrix.platform == 'macos-latest' run: | + sudo xcode-select -s "/Applications/Xcode_10.3.app" mkdir build cd build cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDS_STORE_SCRIPT=ON @@ -142,8 +143,8 @@ jobs: if: matrix.platform == 'ubuntu-16.04' shell: bash env: - CC: /usr/bin/gcc-9 - CXX: /usr/bin/g++-9 + CC: /usr/bin/gcc-7 + CXX: /usr/bin/g++-7 run: | mkdir build cd build @@ -171,12 +172,23 @@ jobs: cd .. squashfs-root/AppRun AppDir/usr/share/applications/qv2ray.desktop -appimage -no-strip -always-overwrite mv ./Qv2ray*.AppImage ./Qv2ray.AppImage + wget https://github.com/Qv2ray/Distribution/releases/download/0.0.0/fakeDeb.tar.gz + tar xzf fakeDeb.tar.gz + cd fakeDeb + cp ../Qv2ray.AppImage opt/qv2ray/Qv2ray.AppImage + dpkg-deb -b . ../qv2ray.deb - name: Linux - ${{ matrix.qt_version }} - Uploading artifact if: matrix.platform == 'ubuntu-16.04' uses: actions/upload-artifact@master with: name: Qv2ray-${{ github.sha }}.linux-${{ matrix.arch }}.qt${{ matrix.qt_version }}.AppImage path: build/Qv2ray.AppImage + - name: Linux - ${{ matrix.qt_version }} - Uploading debian package + if: matrix.platform == 'ubuntu-16.04' + uses: actions/upload-artifact@master + with: + name: Qv2ray-${{ github.sha }}.linux-${{ matrix.arch }}.qt${{ matrix.qt_version }}.deb + path: build/qv2ray.deb - name: Linux - ${{ matrix.qt_version }} - Upload binaries to release uses: svenstaro/upload-release-action@v1-release if: github.event_name == 'release' && matrix.platform == 'ubuntu-16.04' && matrix.qt_version == '5.14.2' diff --git a/.github/workflows/deb.yml b/.github/workflows/deb.yml index 1802f907..d23d601c 100644 --- a/.github/workflows/deb.yml +++ b/.github/workflows/deb.yml @@ -21,8 +21,9 @@ jobs: linux: strategy: - matrix: - distro: [stable, unstable] + fail-fast: false + matrix: + distro: [stable, unstable] needs: check_commit_msg if: ${{ !contains( needs.check_commit_msg.outputs.commit_message, 'NO_DEB') }} name: Debian ${{ matrix.distro }} @@ -40,20 +41,39 @@ jobs: submodules: 'recursive' - name: Install build dependencies run: | - apt-get install -y build-essential debhelper ninja-build libgrpc++-dev libprotobuf-dev protobuf-compiler-grpc qtbase5-dev qttools5-dev cmake pkg-config + apt-get install -y build-essential devscripts reprepro debhelper ninja-build libgrpc++-dev libprotobuf-dev protobuf-compiler-grpc qtbase5-dev qttools5-dev cmake pkg-config qtdeclarative5-dev - name: Build run: | + dch -l${{ matrix.distro }} -m 'Build against ${{ matrix.distro }}' -D ${{ matrix.distro }} dpkg-buildpackage -us -uc -i -b - name: Copy binary run: | cp ../qv2ray_*.deb ./ + - name: Sleep + if: github.event_name == 'release' && matrix.distro == 'unstable' + run: | + sleep 120 + - name: Setup Repository + if: github.event_name == 'release' && !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, 'rc') && !contains(github.ref, 'pre') + run: | + git clone https://github.com/Qv2ray/debian.git archive + echo ${{ secrets.DEBIAN_REPO_KEY }} | base64 -d > private.key + gpg --import private.key + cd archive + git config --local user.name "${{ github.actor }}" + git config --local user.email "${{ github.actor }}@users.noreply.github.com" + git remote set-url origin https://${{ github.actor }}:${{ secrets.DEBIAN_REPO_TOKEN }}@github.com/Qv2ray/debian.git + reprepro includedeb ${{ matrix.distro }} ../*.deb + git add -A + git commit -am 'update' + git push origin master - name: Get package name id: get_package run: echo ::set-output name=NAME::$(basename qv2ray_*.deb) - name: Upload artifact uses: actions/upload-artifact@v2-preview with: - name: qv2ray_${{ matrix.distro }} + name: ${{ steps.get_package.outputs.NAME }} path: ${{ steps.get_package.outputs.NAME }} - name: Upload binaries to release uses: svenstaro/upload-release-action@v1-release diff --git a/.github/workflows/nsis.yml b/.github/workflows/nsis.yml index 6b84868c..36f9dbc9 100644 --- a/.github/workflows/nsis.yml +++ b/.github/workflows/nsis.yml @@ -68,7 +68,7 @@ jobs: path: ../Qt key: QtCache-${{ matrix.platform }}-${{ matrix.arch }}-${{ matrix.qt_version }} - name: Installing Qt - ${{ matrix.arch }} - uses: jurplel/install-qt-action@v2.5.0 + uses: jurplel/install-qt-action@v2 with: version: ${{ matrix.qt_version }} arch: ${{ matrix.qtarch }} diff --git a/.gitmodules b/.gitmodules index acd86d16..b1cabddb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ -[submodule "3rdparty/x2struct"] - path = 3rdparty/x2struct - url = https://github.com/xyz347/x2struct [submodule "3rdparty/SingleApplication"] path = 3rdparty/SingleApplication url = https://github.com/itay-grudev/SingleApplication @@ -16,6 +13,15 @@ [submodule "3rdparty/zxing-cpp"] path = 3rdparty/zxing-cpp url = https://github.com/nu-book/zxing-cpp -[submodule "src/components/plugins/interface"] - path = src/components/plugins/interface +[submodule "src/plugin-interface"] + path = src/plugin-interface url = https://github.com/Qv2ray/QvPlugin-Interface/ +[submodule "libs/QJsonStruct"] + path = libs/QJsonStruct + url = https://github.com/Qv2ray/QJsonStruct +[submodule "3rdparty/uistyles"] + path = 3rdparty/uistyles + url = https://github.com/Qv2ray/QvUIStyles +[submodule "3rdparty/backward-cpp"] + path = 3rdparty/backward-cpp + url = https://github.com/bombela/backward-cpp diff --git a/.travis.yml b/.travis.yml index 59026963..37e70b6f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,27 +2,8 @@ language: shell os: linux dist: bionic -env: - global: - - LC_ALL: C.UTF-8 - - LANG: C.UTF-8 - -addons: - snaps: - - name: snapcraft - channel: stable - confinement: classic - - name: lxd - channel: stable - script: - - sudo apt-get autoremove lxd --purge - - sudo /snap/bin/lxd waitready - - sudo /snap/bin/lxd init --auto - - sudo snapcraft --use-lxd - -after_failure: - - sudo journalctl -u snapd + - echo Refreshing launchpad repository deploy: - provider: launchpad diff --git a/3rdparty/QNodeEditor b/3rdparty/QNodeEditor index db07dd4f..427b6fe1 160000 --- a/3rdparty/QNodeEditor +++ b/3rdparty/QNodeEditor @@ -1 +1 @@ -Subproject commit db07dd4ffcbfdd62431584d499928e45b5864f40 +Subproject commit 427b6fe13049aae62855b893b524f17278e8c06f diff --git a/3rdparty/SingleApplication b/3rdparty/SingleApplication index 4baf2e74..bae7c331 160000 --- a/3rdparty/SingleApplication +++ b/3rdparty/SingleApplication @@ -1 +1 @@ -Subproject commit 4baf2e74f64c9a6ce36d456491bb41d0f2ae999e +Subproject commit bae7c331ca7203a242e4533ba859c0c6016521ba diff --git a/3rdparty/backward-cpp b/3rdparty/backward-cpp new file mode 160000 index 00000000..29e40614 --- /dev/null +++ b/3rdparty/backward-cpp @@ -0,0 +1 @@ +Subproject commit 29e4061494e1ac4e40b2b09fd3e35c310ca137fc diff --git a/3rdparty/uistyles b/3rdparty/uistyles new file mode 160000 index 00000000..ccbc5a7e --- /dev/null +++ b/3rdparty/uistyles @@ -0,0 +1 @@ +Subproject commit ccbc5a7ec83229e18804bf6b650b181f5279ca74 diff --git a/3rdparty/x2struct b/3rdparty/x2struct deleted file mode 160000 index 45397646..00000000 --- a/3rdparty/x2struct +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4539764671509655370b2ff4fae90bfd8a12df40 diff --git a/3rdparty/zxing-cpp b/3rdparty/zxing-cpp index 66cc26b2..6d40262e 160000 --- a/3rdparty/zxing-cpp +++ b/3rdparty/zxing-cpp @@ -1 +1 @@ -Subproject commit 66cc26b25633cb7f1e20f2bf7711960c321e3a7b +Subproject commit 6d40262e7293666909f7325075d452637f2fca75 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c32e6b2..0eb186c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,9 +5,7 @@ file(STRINGS "${CMAKE_SOURCE_DIR}/makespec/BUILDVERSION" QV2RAY_BUILD_VERSION) file(STRINGS "${CMAKE_SOURCE_DIR}/makespec/VERSIONSUFFIX" QV2RAY_VERSION_SUFFIX) if(NOT CMAKE_BUILD_TYPE STREQUAL "Release") - math(EXPR QV2RAY_BUILD_VERSION "1 + ${QV2RAY_BUILD_VERSION}") - message("Increasing BUILDVERSION") - file(WRITE "${CMAKE_SOURCE_DIR}/makespec/BUILDVERSION" ${QV2RAY_BUILD_VERSION}) + add_definitions(-DNODE_DEBUG_DRAWING) endif() set(QV2RAY_VERSION_STRING "${QV2RAY_VERSION}${QV2RAY_VERSION_SUFFIX}") @@ -28,12 +26,30 @@ add_definitions(-DQV2RAY_VERSION_BUGFIX=${CPACK_PACKAGE_VERSION_PATCH}) add_definitions(-DQV2RAY_VERSION_BUILD=${QV2RAY_BUILD_VERSION}) add_definitions(-DQV2RAY_VERSION_STRING="${QV2RAY_VERSION_STRING}") -add_definitions(-DXTOSTRUCT_QT) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) +if(MSVC) + set(CMAKE_CXX_EXTENSIONS OFF) +endif() -find_package(Qt5 5.11 COMPONENTS Core Gui Widgets Network REQUIRED) +if(ANDROID) + include(${ANDROID_SDK}/android_openssl/CMakeLists.txt) +endif() + +set(QV2RAY_ONLY_USE_QML OFF CACHE BOOL "Only use QML when building Qv2ray") + +if(QV2RAY_ONLY_USE_QML) + find_package(Qt5 5.11 COMPONENTS Core Gui Network Qml Quick REQUIRED) + set(QV2RAY_QT_LIBS Qt5::Core Qt5::Network Qt5::Quick Qt5::Qml Qt5::Gui) + add_definitions(-DQV2RAY_QML) +else() + find_package(Qt5 5.11 COMPONENTS Core Gui Widgets Network REQUIRED) + set(QV2RAY_QT_LIBS Qt5::Core Qt5::Network Qt5::Widgets Qt5::Gui) + add_definitions(-DQV2RAY_QWIDGETS) +endif() + +find_package(Threads REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) @@ -65,7 +81,7 @@ message(" ") if(WIN32) add_compile_options("/std:c++17") add_definitions(-DUNICODE -D_UNICODE) - add_definitions(-D_HAS_STD_BYTE=0 -D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_WIN32_WINNT=0x600 -D_SCL_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_WARNINGS) set(GUI_TYPE WIN32) if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) if(CMAKE_CL_64) @@ -76,19 +92,68 @@ if(WIN32) endif() endif() -find_package(Threads REQUIRED) - +# ================================================================================== +# Qv2ray Build Arguments +# ================================================================================== set(QV2RAY_QNODEEDITOR_PROVIDER "module" CACHE STRING "qnodeeditor provider") set(QV2RAY_ZXING_PROVIDER "module" CACHE STRING "zxing-cpp provider") +if (ANDROID) +else() + set(QV2RAY_SINGLEAPPLICATION_PROVIDER "module" CACHE STRING "SingleApplication provider") +endif() +set(QV2RAY_DEFAULT_VASSETS_PATH "unset" CACHE STRING "v2ray assets path") +set(QV2RAY_DEFAULT_VCORE_PATH "unset" CACHE STRING "v2ray core path") +set(QV2RAY_TRANSLATION_PATH "unset" CACHE STRING "Qv2ray translations path") +set(QV2RAY_DISABLE_AUTO_UPDATE OFF CACHE BOOL "Disable update checker") +set(BUILD_TESTING OFF CACHE BOOL "Build test") +set(EMBED_TRANSLATIONS OFF CACHE BOOL "Embed translations") +set(QV2RAY_AUTO_DEPLOY ON CACHE BOOL "Automatically run deploy command after build") +if(QV2RAY_DEFAULT_VASSETS_PATH AND NOT QV2RAY_DEFAULT_VASSETS_PATH STREQUAL "unset") + add_definitions(-DQV2RAY_DEFAULT_VASSETS_PATH="${QV2RAY_DEFAULT_VASSETS_PATH}") +endif() + +if(QV2RAY_DEFAULT_VCORE_PATH AND NOT QV2RAY_DEFAULT_VCORE_PATH STREQUAL "unset") + add_definitions(-DQV2RAY_DEFAULT_VCORE_PATH="${QV2RAY_DEFAULT_VCORE_PATH}") +endif() + +if(QV2RAY_TRANSLATION_PATH AND NOT QV2RAY_TRANSLATION_PATH STREQUAL "unset") + add_definitions(-DQV2RAY_TRANSLATION_PATH="${QV2RAY_TRANSLATION_PATH}") +endif() + +if(QV2RAY_DISABLE_AUTO_UPDATE) + add_definitions(-DDISABLE_AUTO_UPDATE) +endif() + +if(FALL_BACK_TO_XDG_OPEN) + add_definitions(-DFALL_BACK_TO_XDG_OPEN) +endif() + +if(EMBED_TRANSLATIONS) + add_definitions(-DEMBED_TRANSLATIONS) + configure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY) + set(QV2RAY_EMBED_TRANSLATION_QRC ${CMAKE_BINARY_DIR}/translations.qrc) +endif() + + +# ================================================================================== +# 3rdparty Sources +# ================================================================================== include(cmake/translations.cmake) include(cmake/qnodeeditor.cmake) -include(cmake/singleapplication.cmake) -include(cmake/protobuf.cmake) -include(cmake/cpp-httplib.cmake) -include(cmake/backend.cmake) +if (ANDROID) +else() + include(cmake/singleapplication.cmake) + include(cmake/protobuf.cmake) + include(cmake/backend.cmake) +endif() include(cmake/zxing-cpp.cmake) +include(cmake/libsemver.cmake) + +# ================================================================================== +# Qv2ray Build Info +# ================================================================================== if(QV2RAY_BUILD_INFO) set(_QV2RAY_BUILD_INFO_STR_ "${QV2RAY_BUILD_INFO}") elseif(DEFINED ENV{_QV2RAY_BUILD_INFO_}) @@ -105,264 +170,130 @@ else() set(_QV2RAY_BUILD_EXTRA_INFO_STR_ "Qv2ray ${QV2RAY_VERSION_STRING}:${QV2RAY_BUILD_VERSION}") endif() -set(QV2RAY_BUILD_INFO ${_QV2RAY_BUILD_INFO_STR_} CACHE STRING "Qv2ray build info") -set(QV2RAY_BUILD_EXTRA_INFO ${_QV2RAY_BUILD_EXTRA_INFO_STR_} CACHE STRING "Qv2ray build extra info") +set(QV2RAY_BUILD_INFO ${_QV2RAY_BUILD_INFO_STR_}) +set(QV2RAY_BUILD_EXTRA_INFO ${_QV2RAY_BUILD_EXTRA_INFO_STR_}) add_definitions(-D_QV2RAY_BUILD_INFO_STR_="${_QV2RAY_BUILD_INFO_STR_}") add_definitions(-D_QV2RAY_BUILD_EXTRA_INFO_STR_="${_QV2RAY_BUILD_EXTRA_INFO_STR_}") message("Qv2ray build info: ${_QV2RAY_BUILD_INFO_STR_}") message("Qv2ray build info ex: ${_QV2RAY_BUILD_EXTRA_INFO_STR_}") -set(QV2RAY_DEFAULT_VASSETS_PATH "unset" CACHE STRING "v2ray assets path") -set(QV2RAY_DEFAULT_VCORE_PATH "unset" CACHE STRING "v2ray core path") -set(QV2RAY_TRANSLATION_PATH "unset" CACHE STRING "Qv2ray translations path") -set(QV2RAY_DISABLE_AUTO_UPDATE OFF CACHE BOOL "Disable update checker") -if(UNIX AND NOT APPLE) - set(QV2RAY_USE_BUILTIN_DARKTHEME OFF CACHE BOOL "Use built-in dark theme instead of followed by the system settings") -else() - set(QV2RAY_USE_BUILTIN_DARKTHEME ON CACHE BOOL "Use built-in dark theme instead of followed by the system settings") -endif() -set(EMBED_TRANSLATIONS OFF CACHE BOOL "Embed translations") -if(QV2RAY_DEFAULT_VASSETS_PATH AND NOT QV2RAY_DEFAULT_VASSETS_PATH STREQUAL "unset") - add_definitions(-DQV2RAY_DEFAULT_VASSETS_PATH="${QV2RAY_DEFAULT_VASSETS_PATH}") -endif() +# ================================================================================== +# Plugin Interface Headers +# ================================================================================== +set(QVPLUGIN_INTERFACE_INCLUDE_DIR "src/plugin-interface") +include(src/plugin-interface/QvPluginInterface.cmake) -if(QV2RAY_DEFAULT_VCORE_PATH AND NOT QV2RAY_DEFAULT_VCORE_PATH STREQUAL "unset") - add_definitions(-DQV2RAY_DEFAULT_VCORE_PATH="${QV2RAY_DEFAULT_VCORE_PATH}") -endif() -if(QV2RAY_TRANSLATION_PATH AND NOT QV2RAY_TRANSLATION_PATH STREQUAL "unset") - add_definitions(-DQV2RAY_TRANSLATION_PATH="${QV2RAY_TRANSLATION_PATH}") -endif() +# ================================================================================== +# Qv2ray Sources and Headers +# ================================================================================== +include(cmake/components/qv2ray-lib.cmake) +include(cmake/components/qv2ray-ui.cmake) +include(3rdparty/uistyles/uistyles.cmake) -if(QV2RAY_USE_BUILTIN_DARKTHEME) - add_definitions(-DQV2RAY_USE_BUILTIN_DARKTHEME=true) -endif() +set(QRC_RESOURCES + ${UISTYLE_QRCS} + ${CMAKE_SOURCE_DIR}/resources.qrc) -if(QV2RAY_DISABLE_AUTO_UPDATE) - add_definitions(-DDISABLE_AUTO_UPDATE) -endif() - -set(QVPLUGIN_INTERFACE_INCLUDE_DIR "src/components/plugins/interface") -include(src/components/plugins/interface/QvPluginInterface.cmake) - -set(QV2RAY_SOURCES +# Qv2ray baselib +add_library(qv2ray-baselib STATIC + ${QV2RAY_BASE_HEADERS} + ${QV2RAY_LIB_SOURCES} ${QVPLUGIN_INTERFACE_HEADERS} - 3rdparty/libsemver/version.cpp - src/base/Qv2rayLog.cpp - src/common/CommandArgs.cpp - src/common/HTTPRequestHelper.cpp - src/common/LogHighlighter.cpp - src/common/JsonHighlighter.cpp - src/common/QJsonModel.cpp - src/common/QvHelpers.cpp - src/common/QvTranslator.cpp - src/common/QRCodeHelper.cpp - src/components/autolaunch/QvAutoLaunch.cpp - src/components/geosite/QvGeositeReader.cpp - src/components/latency/win/ICMPPinger.cpp - src/components/latency/QvTCPing.cpp - src/components/plugins/toolbar/QvToolbar.cpp - src/components/plugins/toolbar/QvToolbar_linux.cpp - src/components/plugins/toolbar/QvToolbar_win.cpp - src/components/plugins/QvPluginHost.cpp - src/components/proxy/QvProxyConfigurator.cpp - src/components/route/RouteSchemeIO.cpp - src/components/speedchart/speedplotview.cpp - src/components/speedchart/speedwidget.cpp - src/components/darkmode/DarkmodeDetector.cpp - src/components/update/UpdateChecker.cpp - src/core/connection/ConnectionIO.cpp - src/core/connection/Generation.cpp - src/core/connection/Serialization.cpp - src/core/connection/Serialization_ss.cpp - src/core/connection/Serialization_ssd.cpp - src/core/connection/Serialization_vmess.cpp - src/core/CoreUtils.cpp - src/core/handler/ConfigHandler.cpp - src/core/handler/KernelInstanceHandler.cpp - src/core/kernel/APIBackend.cpp - src/core/kernel/V2rayKernelInteractions.cpp - src/core/kernel/PluginKernelInteractions.cpp - src/core/kernel/QvKernelABIChecker.cpp - src/core/settings/SettingsBackend.cpp - src/core/settings/SettingsUpgrade.cpp - src/main.cpp - src/ui/editors/w_InboundEditor.cpp - src/ui/editors/w_JsonEditor.cpp - src/ui/editors/w_OutboundEditor.cpp - src/ui/editors/w_RoutesEditor.cpp - src/ui/editors/w_RoutesEditor_extra.cpp - src/ui/messaging/QvMessageBus.cpp - src/ui/models/InboundNodeModel.cpp - src/ui/models/OutboundNodeModel.cpp - src/ui/models/RuleNodeModel.cpp - src/ui/widgets/ConnectionInfoWidget.cpp - src/ui/widgets/ConnectionItemWidget.cpp - src/ui/widgets/QvAutoCompleteTextEdit.cpp - src/ui/widgets/StreamSettingsWidget.cpp - src/ui/widgets/RouteSettingsMatrix.cpp - src/ui/w_ImportConfig.cpp - src/ui/w_MainWindow.cpp - src/ui/w_MainWindow_extra.cpp - src/ui/w_PreferencesWindow.cpp - src/ui/w_PluginManager.cpp - src/ui/w_ScreenShot_Core.cpp - src/ui/w_SubscriptionManager.cpp - # ui files - src/ui/w_SubscriptionManager.ui - src/ui/editors/w_OutboundEditor.ui - src/ui/editors/w_InboundEditor.ui - src/ui/editors/w_JsonEditor.ui - src/ui/editors/w_RoutesEditor.ui - src/ui/w_ImportConfig.ui - src/ui/widgets/StreamSettingsWidget.ui - src/ui/widgets/ConnectionInfoWidget.ui - src/ui/widgets/ConnectionItemWidget.ui - src/ui/widgets/RouteSettingsMatrix.ui - src/ui/w_MainWindow.ui - src/ui/w_PreferencesWindow.ui - src/ui/w_PluginManager.ui - src/ui/w_ScreenShot_Core.ui - # headers - 3rdparty/libsemver/version.hpp - src/base/JsonHelpers.hpp - src/base/models/CoreObjectModels.hpp - src/base/models/QvConfigIdentifier.hpp - src/base/models/QvRuntimeConfig.hpp - src/base/models/QvSafeType.hpp - src/base/models/QvSettingsObject.hpp - src/base/models/QvStartupConfig.hpp - src/base/Qv2rayBase.hpp - src/base/Qv2rayFeatures.hpp - src/base/Qv2rayLog.hpp - src/common/CommandArgs.hpp - src/common/HTTPRequestHelper.hpp - src/common/JsonHighlighter.hpp - src/common/LogHighlighter.hpp - src/common/QJsonModel.hpp - src/common/QvHelpers.hpp - src/common/QvTranslator.hpp - src/common/QRCodeHelper.hpp - src/components/autolaunch/QvAutoLaunch.hpp - src/components/darkmode/DarkmodeDetector.hpp - src/components/geosite/QvGeositeReader.hpp - src/components/latency/win/ICMPPinger.hpp - src/components/latency/QvTCPing.hpp - src/components/plugins/toolbar/QvToolbar.hpp - src/components/plugins/QvPluginHost.hpp - src/components/proxy/QvProxyConfigurator.hpp - src/components/route/RouteSchemeIO.hpp - src/components/route/presets/RouteScheme_V2rayN.hpp - src/components/speedchart/speedplotview.hpp - src/components/speedchart/speedwidget.hpp - src/components/update/UpdateChecker.hpp - src/core/connection/ConnectionIO.hpp - src/core/connection/Generation.hpp - src/core/connection/Serialization.hpp - src/core/CoreSafeTypes.hpp - src/core/CoreUtils.hpp - src/core/handler/ConfigHandler.hpp - src/core/handler/KernelInstanceHandler.hpp - src/core/kernel/APIBackend.hpp - src/core/kernel/V2rayKernelInteractions.hpp - src/core/kernel/PluginKernelInteractions.hpp - src/core/kernel/QvKernelABIChecker.hpp - src/core/settings/SettingsBackend.hpp - src/ui/editors/w_InboundEditor.hpp - src/ui/editors/w_JsonEditor.hpp - src/ui/editors/w_OutboundEditor.hpp - src/ui/editors/w_RoutesEditor.hpp - src/ui/messaging/QvMessageBus.hpp - src/ui/models/InboundNodeModel.hpp - src/ui/models/NodeModelsBase.hpp - src/ui/models/OutboundNodeModel.hpp - src/ui/models/RuleNodeModel.hpp - src/ui/widgets/ConnectionInfoWidget.hpp - src/ui/widgets/ConnectionItemWidget.hpp - src/ui/widgets/QvAutoCompleteTextEdit.hpp - src/ui/widgets/StreamSettingsWidget.hpp - src/ui/widgets/RouteSettingsMatrix.hpp - src/ui/w_ImportConfig.hpp - src/ui/w_MainWindow.hpp - src/ui/w_PreferencesWindow.hpp - src/ui/w_PluginManager.hpp - src/ui/w_ScreenShot_Core.hpp - src/ui/w_SubscriptionManager.hpp - assets/qv2ray.rc - ) - -if(EMBED_TRANSLATIONS) - add_definitions(-DEMBED_TRANSLATIONS) - configure_file(translations/translations.qrc ${CMAKE_BINARY_DIR} COPYONLY) - list(APPEND QV2RAY_SOURCES ${CMAKE_BINARY_DIR}/translations.qrc) -endif() - -add_custom_target(lupdate - COMMAND lupdate ${QV2RAY_SOURCES} -ts ${TRANSLATIONS_TS} -locations none - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - -set_target_properties(lupdate PROPERTIES EXCLUDE_FROM_ALL TRUE) - -set(QRC_RESOURCES ${CMAKE_SOURCE_DIR}/resources.qrc) - -set(QT_LIBRARY - Qt5::Core - Qt5::Gui - Qt5::Widgets - Qt5::Network - ) - -add_executable(${PROJECT_NAME} - ${GUI_TYPE} - ${QV2RAY_SOURCES} - ${QNODEEDITOR_SOURCES} - ${QNODEEDITOR_QRC_RESOURCES} - ${SINGLEAPPLICATION_SOURCES} - ${ZXING_SOURCES} + ${LIBSEMVER_SOURCES} ${PROTO_SRCS} ${PROTO_HDRS} ${API_GRPC_SRCS} ${API_PROTO_SRCS} - ${QRC_RESOURCES} - ${QM_FILES} ) -target_link_libraries(${PROJECT_NAME} - ${QT_LIBRARY} +set(QV2RAY_FULL_SOURCES + src/main.cpp + assets/qv2ray.rc + ${QV2RAY_UI_FORMS} + ${QV2RAY_UI_SOURCES} + ${SINGLEAPPLICATION_SOURCES} + ${QNODEEDITOR_QRC_RESOURCES} + ${QV2RAY_EMBED_TRANSLATION_QRC} + ${QRC_RESOURCES} + ${QM_FILES}) + +if(ANDROID) + add_library(qv2ray SHARED ${QV2RAY_FULL_SOURCES}) + target_link_libraries(qv2ray -llog -landroid) +else() + if (WIN32) + add_executable(qv2ray ${GUI_TYPE} ${QV2RAY_FULL_SOURCES}) + else() + include(cmake/backward-cpp.cmake) + add_executable(qv2ray ${GUI_TYPE} ${QV2RAY_FULL_SOURCES}) + add_backward(qv2ray) + endif() +endif() + + +target_link_libraries(qv2ray-baselib ${QV2RAY_PROTOBUF_LIBRARY} ${QV2RAY_BACKEND_LIBRARIES} - ${QNODEEDITOR_LIBRARY} ${ZXING_LIBRARY} Threads::Threads + ${QV2RAY_QT_LIBS} ) +target_link_libraries(qv2ray + qv2ray-baselib + ${QNODEEDITOR_LIBRARY} + ${SINGLEAPPLICATION_LIBRARY} + ) -target_include_directories(${PROJECT_NAME} PRIVATE +target_include_directories(qv2ray PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_BINARY_DIR} - ${QHTTPSERVER_DIR} ${QNODEEDITOR_INCLUDE_PATH} - ${ZXING_INCLUDE_PATH} ${SINGLEAPPLICATION_DIR} ${Protobuf_INCLUDE_DIRS} - ${cpp-httplib_INCLUDE_DIRS} ) +target_include_directories(qv2ray-baselib PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/src + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + ${ZXING_INCLUDE_PATH} + ${Protobuf_INCLUDE_DIRS} + ) + +if (BUILD_TESTING) + include(CTest) + add_subdirectory(test) +endif() + +# Qt language translations +add_custom_target(lupdate + COMMAND lupdate ${QV2RAY_BASE_HEADERS} + ${QV2RAY_LIB_SOURCES} + ${QVPLUGIN_INTERFACE_HEADERS} + ${QV2RAY_UI_SOURCES} + ${QV2RAY_UI_FORMS} + ${QNODEEDITOR_QRC_RESOURCES} + ${SINGLEAPPLICATION_SOURCES} -ts ${TRANSLATIONS_TS} -locations none -noobsolete + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +set_target_properties(lupdate PROPERTIES EXCLUDE_FROM_ALL TRUE) + if(APPLE) find_package(Iconv REQUIRED) find_library(CARBON NAMES Carbon) find_library(COCOA NAMES Cocoa) find_library(SECURITY NAMES Security) - target_link_libraries(${PROJECT_NAME} + target_link_libraries(qv2ray Iconv::Iconv ${CARBON} ${COCOA} ${SECURITY} ) - target_include_directories(${PROJECT_NAME} PRIVATE + target_include_directories(qv2ray PRIVATE ${Iconv_INCLUDE_DIR} ) @@ -373,11 +304,11 @@ if(APPLE) MACOSX_PACKAGE_LOCATION Resources/lang ) - target_sources(${PROJECT_NAME} PRIVATE + target_sources(qv2ray PRIVATE ${MACOSX_ICON} ) - set_target_properties(${PROJECT_NAME} + set_target_properties(qv2ray PROPERTIES MACOSX_BUNDLE TRUE MACOSX_BUNDLE_INFO_PLIST ${MACOSX_PLIST} @@ -394,20 +325,22 @@ if(APPLE) ) # Destination paths below are relative to ${CMAKE_INSTALL_PREFIX} - install(TARGETS ${PROJECT_NAME} + + install(TARGETS qv2ray BUNDLE DESTINATION . COMPONENT Runtime RUNTIME DESTINATION bin COMPONENT Runtime ) - - add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - COMMAND macdeployqt ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app - ) - set(APPS "\${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}.app") + if(QV2RAY_AUTO_DEPLOY) + add_custom_command(TARGET qv2ray POST_BUILD + COMMAND ${Qt5_DIR}/../../../bin/macdeployqt ${CMAKE_BINARY_DIR}/qv2ray.app + ) + endif() + set(APPS "\${CMAKE_INSTALL_PREFIX}/qv2ray.app") include(cmake/deployment.cmake) endif() if(UNIX AND NOT APPLE AND NOT WIN32) - install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin) + install(TARGETS qv2ray RUNTIME DESTINATION bin) install(FILES assets/qv2ray.metainfo.xml DESTINATION share/metainfo) install(FILES assets/qv2ray.desktop DESTINATION share/applications) install(FILES assets/icons/qv2ray.png DESTINATION share/icons/hicolor/256x256/apps) @@ -417,24 +350,18 @@ if(UNIX AND NOT APPLE AND NOT WIN32) endif() if(WIN32) - target_link_libraries(${PROJECT_NAME} wininet wsock32 ws2_32 user32) - install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION .) + find_package(OpenSSL REQUIRED) + target_link_libraries(qv2ray-baselib wininet wsock32 ws2_32 user32 Iphlpapi OpenSSL::SSL OpenSSL::Crypto Dbghelp) + install(TARGETS qv2ray RUNTIME DESTINATION .) if(NOT EMBED_TRANSLATIONS) install(FILES ${QM_FILES} DESTINATION lang) endif() - - if(CMAKE_CL_64) - install(FILES ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/libssl-1_1-x64.dll DESTINATION .) - install(FILES ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/libcrypto-1_1-x64.dll DESTINATION .) - else() - install(FILES ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/libssl-1_1.dll DESTINATION .) - install(FILES ${_VCPKG_INSTALLED_DIR}/${VCPKG_TARGET_TRIPLET}/bin/libcrypto-1_1.dll DESTINATION .) + if(QV2RAY_AUTO_DEPLOY) + add_custom_command(TARGET qv2ray POST_BUILD + COMMAND ${Qt5_DIR}/../../../bin/windeployqt ${CMAKE_BINARY_DIR}/qv2ray.exe --compiler-runtime --verbose 2 --dir ${CMAKE_BINARY_DIR}/winqt/ + ) endif() - - add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD - COMMAND windeployqt ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.exe --compiler-runtime --verbose 2 --dir ${CMAKE_BINARY_DIR}/winqt/ - ) install(DIRECTORY ${CMAKE_BINARY_DIR}/winqt/ DESTINATION .) - set(APPS "\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}.exe") + set(APPS "\${CMAKE_INSTALL_PREFIX}/qv2ray.exe") include(cmake/deployment.cmake) endif() diff --git a/_clang-format b/_clang-format index 28c9f4b1..13878bbc 100644 --- a/_clang-format +++ b/_clang-format @@ -5,7 +5,7 @@ AlignAfterOpenBracket: Align AllowAllArgumentsOnNextLine: false AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false -AllowShortBlocksOnASingleLine: false +AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: None AllowShortIfStatementsOnASingleLine: Never @@ -42,7 +42,7 @@ SpaceAfterTemplateKeyword: false SpaceBeforeParens: ControlStatements SpaceBeforeRangeBasedForLoopColon: true SpacesInParentheses: false -Standard: Cpp11 +Standard: c++17 StatementMacros: [ Q_UNUSED LOG DEBUG ] TabWidth: 4 UseTab: Never diff --git a/assets/MacOSXBundleInfo.plist.in b/assets/MacOSXBundleInfo.plist.in index e06b17ec..fb0c5edd 100644 --- a/assets/MacOSXBundleInfo.plist.in +++ b/assets/MacOSXBundleInfo.plist.in @@ -34,5 +34,20 @@ NSApplication NSHighResolutionCapable True + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLIconFile + Icon + CFBundleURLName + com.qv2ray.qv2ray + CFBundleURLSchemes + + qv2ray + + + diff --git a/assets/ProtocolSchemes.txt b/assets/ProtocolSchemes.txt new file mode 100644 index 00000000..d5d79c4c --- /dev/null +++ b/assets/ProtocolSchemes.txt @@ -0,0 +1,20 @@ +qv2ray://open/preference/general +qv2ray://open/preference/kernel +qv2ray://open/preference/inbound +qv2ray://open/preference/connection +qv2ray://open/preference/route +qv2ray://open/preference/about + +qv2ray://open/group/connection +qv2ray://open/group/subscription +qv2ray://open/group/dns +qv2ray://open/group/route + +qv2ray://open/import/link?name= +qv2ray://open/import/qr?name= +qv2ray://open/import/manual?name= +qv2ray://open/import/advanced?name= + +qv2ray://open/plugin/plugindir +qv2ray://open/plugin/metadata +qv2ray://open/plugin/settings diff --git a/QvRoute.schema.json b/assets/QvRoute.schema.json similarity index 100% rename from QvRoute.schema.json rename to assets/QvRoute.schema.json diff --git a/assets/credit.html b/assets/credit.html index 63ed7c1d..ec52fa8e 100644 --- a/assets/credit.html +++ b/assets/credit.html @@ -1,21 +1,32 @@ - -

This program comes with ABSOLUTELY NO WARRANTY.

-

This is free software, and you are welcome to redistribute it under certain conditions.

-

Copyright (c) 2019-2020 Qv2ray Development Group.

-


-

Libraries that have been used in Qv2ray are listed below (Sorted by date added):

-

Copyright (c) 2020 xyz347 (xyz347): X2Struct (Apache)

-

Copyright (c) 2011 SCHUTZ Sacha (@dridk): QJsonModel (MIT)

-

Copyright (c) 2020 Nikolaos Ftylitakis (@ftylitak): QZXing (Apache2)

-

Copyright (c) 2016 Singein (@Singein): ScreenShot (MIT)

-

Copyright (c) 2020 Itay Grudev (@itay-grudev): SingleApplication (MIT)

-

Copyright (c) 2020 paceholder (@paceholder): nodeeditor (Qv2ray group modified version) (BSD-3-Clause)

-

Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)

-

Copyright (c) 2020 DuckSoft (@DuckSoft): QvRPCBridge (WTFPL)

-

Copyright (c) 2019 ShadowSocks (@shadowsocks): libQtShadowsocks (LGPLv3)

-

Copyright (c) 2015-2020 qBittorrent (Anton Lashkov) (@qBittorrent): speedplotview (GPLv2)

-

Copyright (c) 2020 Diffusions Nu-book Inc. (nu-book): zxing-cpp (Apache)

- + + + + + + + + +

This program comes with ABSOLUTELY NO WARRANTY.

+

This is free software, and you are welcome to redistribute it under certain conditions.

+

Copyright (c) 2019-2020 Qv2ray Development Group.

+


+

Libraries that have been used in Qv2ray are listed below (Sorted by date added):

+

Copyright (c) 2011 SCHUTZ Sacha (@dridk): QJsonModel (MIT)

+

Copyright (c) 2016 Singein (@Singein): ScreenShot (MIT)

+

Copyright (c) 2020 Itay Grudev (@itay-grudev): SingleApplication (MIT)

+

Copyright (c) 2020 paceholder (@paceholder): nodeeditor (Qv2ray group modified version) (BSD-3-Clause)

+

Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)

+

Copyright (c) 2020 DuckSoft (@DuckSoft): QvRPCBridge (WTFPL)

+

Copyright (c) 2019 ShadowSocks (@shadowsocks): libQtShadowsocks (LGPLv3)

+

Copyright (c) 2015-2020 qBittorrent (Anton Lashkov) (@qBittorrent): speedplotview (GPLv2)

+

Copyright (c) 2020 Diffusions Nu-book Inc. (nu-book): zxing-cpp (Apache)

+

Copyright (c) 2020 feiyangqingyun: QWidgetDemo (Mulan PSL v1)

+ + + \ No newline at end of file diff --git a/assets/icons/Applogo_bird_b.png b/assets/icons/Applogo_bird_b.png new file mode 100644 index 00000000..0e266d5c Binary files /dev/null and b/assets/icons/Applogo_bird_b.png differ diff --git a/assets/icons/Applogo_bird_colorful.png b/assets/icons/Applogo_bird_colorful.png new file mode 100644 index 00000000..3c07bf50 Binary files /dev/null and b/assets/icons/Applogo_bird_colorful.png differ diff --git a/assets/icons/Applogo_bird_g.png b/assets/icons/Applogo_bird_g.png new file mode 100644 index 00000000..861bd989 Binary files /dev/null and b/assets/icons/Applogo_bird_g.png differ diff --git a/assets/icons/designs/Applogo_Bird_b.svg b/assets/icons/designs/Applogo_Bird_b.svg new file mode 100644 index 00000000..19ef1386 --- /dev/null +++ b/assets/icons/designs/Applogo_Bird_b.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/assets/icons/designs/Applogo_Bird_colorful.svg b/assets/icons/designs/Applogo_Bird_colorful.svg new file mode 100644 index 00000000..51164118 --- /dev/null +++ b/assets/icons/designs/Applogo_Bird_colorful.svg @@ -0,0 +1,237 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/assets/package_dmg.json.in b/assets/package_dmg.json.in index fb1b9dc8..809eb31f 100644 --- a/assets/package_dmg.json.in +++ b/assets/package_dmg.json.in @@ -11,7 +11,7 @@ } }, "contents": [ - { "x": 130, "y": 205, "type": "file", "path": "@CMAKE_BINARY_DIR@/qv2ray.app" }, + { "x": 130, "y": 205, "type": "file", "path": "@CMAKE_INSTALL_PREFIX@/qv2ray.app" }, { "x": 375, "y": 205, "type": "link", "path": "/Applications" } ] } diff --git a/qv2ray-config-models.qmodel b/assets/qv2ray-config-models.qmodel similarity index 100% rename from qv2ray-config-models.qmodel rename to assets/qv2ray-config-models.qmodel diff --git a/qv2ray-new.qmodel b/assets/qv2ray-new.qmodel similarity index 100% rename from qv2ray-new.qmodel rename to assets/qv2ray-new.qmodel diff --git a/assets/qv2ray.desktop b/assets/qv2ray.desktop index fe95a5fa..6a6a4369 100644 --- a/assets/qv2ray.desktop +++ b/assets/qv2ray.desktop @@ -2,25 +2,42 @@ Version=1.0 Name=Qv2ray Type=Application -GenericName=V2ray Graphical Frontend -GenericName[zh_CN]=V2ray 图形前端 -Comment=A Cross-platform v2ray Qt GUI Client -Comment[zh_CN]=跨平台的 v2ray Qt 客户端 +GenericName=V2Ray Graphical Frontend +GenericName[zh_CN]=V2Ray 图形前端 +GenericName[ja]=V2Rayグラフィックフロントエンド +Comment=A Cross-Platform V2Ray Qt GUI Client +Comment[zh_CN]=跨平台的 V2Ray Qt 图形客户端 +Comment[ja]=とあるクロスプラットフォームV2Rayグラフィックフロントエンド Keywords=Internet;VPN;Proxy;v2ray;Qt;qv; -Keywords[zh_CN]=Internet;VPN;Proxy;v2ray;Qt;qv;代理;翻墙;梯子; +Keywords[zh_CN]=Internet;VPN;Proxy;v2ray;Qt;qv;代理; +Keywords[ja]=インターネット;VPN;プロキシ;Qt;v2ray;qv; Categories=Network;Qt; Terminal=false Icon=qv2ray -Exec=qv2ray -Actions=noAPI;withToolbarPlugin; +Exec=qv2ray %u +MimeType=x-scheme-handler/qv2ray; +Actions=debug;noPlugin;noScaleFactor;noAPI; + +[Desktop Action debug] +Name=Start with Debug Mode +Name[zh_CN]=以调试模式运行 +Name[ja]=デバッグモードで起動 +Exec=qv2ray --debug + +[Desktop Action noPlugin] +Name=Start without Plugins +Name[zh_CN]=不使用插件启动 +Name[ja]=プラグイン使用せずに起動 +Exec=qv2ray --noPlugin + +[Desktop Action noScaleFactor] +Name=Start without Scaling Factor +Name[zh_CN]=禁用界面缩放启动 +Name[ja]=インターフェーススケーリング使用せずに起動 +Exec=qv2ray --noScaleFactor [Desktop Action noAPI] Name=Start without gRPC API Name[zh_CN]=不使用 gRPC API 启动 +Name[ja]=gRPC使用せずに起動 Exec=qv2ray --noAPI - -[Desktop Action withToolbarPlugin] -Name=Start with Toolbar Plugin -Name[zh_CN]=使用工具栏插件启动 -Exec=qv2ray --withToolbarPlugin - diff --git a/assets/qv2ray.metainfo.xml b/assets/qv2ray.metainfo.xml index 033474e2..31962225 100644 --- a/assets/qv2ray.metainfo.xml +++ b/assets/qv2ray.metainfo.xml @@ -1,8 +1,8 @@ com.github.Qv2ray - CC0-1.0 - GPL-3.0 + GPL-3.0+ + GPL-3.0+ Qv2ray Qv2ray is a cross-platform v2ray graphical frontend written in Qt. diff --git a/qv2ray.qmodel b/assets/qv2ray.qmodel similarity index 100% rename from qv2ray.qmodel rename to assets/qv2ray.qmodel diff --git a/assets/qv2ray.rc b/assets/qv2ray.rc index 93479262..5e5ca991 100644 --- a/assets/qv2ray.rc +++ b/assets/qv2ray.rc @@ -31,7 +31,7 @@ VS_VERSION_INFO VERSIONINFO BLOCK "040904b0" BEGIN VALUE "CompanyName", "Qv2ray Workgroup\0" - VALUE "FileDescription", "Qv2ray, a cross-platform v2ray GUI client.\0" + VALUE "FileDescription", "Qv2ray\0" VALUE "FileVersion", VER_FILEVERSION_STR VALUE "LegalCopyright", "Qv2ray is being distributed under GPLv3\0" VALUE "OriginalFilename", "qv2ray.exe\0" diff --git a/tools/v2ray_api.proto b/assets/v2ray_api.proto similarity index 100% rename from tools/v2ray_api.proto rename to assets/v2ray_api.proto diff --git a/tools/v2ray_geosite.proto b/assets/v2ray_geosite.proto similarity index 100% rename from tools/v2ray_geosite.proto rename to assets/v2ray_geosite.proto diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 66ce01c5..80ddadf9 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -15,21 +15,25 @@ pool: steps: - checkout: self submodules: recursive +- task: NodeTool@0 + inputs: + versionSpec: '12.x' - script: | - brew install protobuf grpc ninja qt5 + sudo xcode-select -s /Applications/Xcode_10.3.app + brew install protobuf grpc ninja qt5 pkg-config + npm install -g appdmg displayName: Prepare dependencies - script: | PATH=/usr/local/opt/qt5/bin:$PATH mkdir build cd build - cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 - sudo cmake --build . --target package --parallel $(sysctl -n hw.logicalcpu) + cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 + cmake --build . + sudo cmake --install . + appdmg ../assets/package_dmg.json ../qv2ray-legacy.dmg displayName: Build Qv2ray env: Qt5_DIR: /usr/local/opt/qt5/lib/cmake/Qt5 -- script: | - cp build/qv2ray-*.dmg ./qv2ray-legacy.dmg - displayName: Copy binary - task: PublishBuildArtifacts@1 inputs: PathtoPublish: 'qv2ray-legacy.dmg' diff --git a/cmake/backend.cmake b/cmake/backend.cmake index fb25d91f..d367dd14 100644 --- a/cmake/backend.cmake +++ b/cmake/backend.cmake @@ -11,13 +11,14 @@ if(NOT USE_LIBQVB) set(QV2RAY_BACKEND_LIBRARIES ${GRPC_LIBRARIES}) else() find_library(UPB_LIBRARY NAMES upb) + find_library(ADDRESS_SORTING NAMES address_sorting) pkg_check_modules(GRPC REQUIRED grpc++ grpc gpr) - set(QV2RAY_BACKEND_LIBRARIES ${GRPC_LINK_LIBRARIES} ${UPB_LIBRARY}) + set(QV2RAY_BACKEND_LIBRARIES ${GRPC_LINK_LIBRARIES} ${UPB_LIBRARY} ${ADDRESS_SORTING}) endif() endif() - set(API_PROTO "${CMAKE_SOURCE_DIR}/tools/v2ray_api.proto") - set(API_PROTO_PATH "${CMAKE_SOURCE_DIR}/tools") + set(API_PROTO "${CMAKE_SOURCE_DIR}/assets/v2ray_api.proto") + set(API_PROTO_PATH "${CMAKE_SOURCE_DIR}/assets") set(API_PROTO_SRCS "${CMAKE_CURRENT_BINARY_DIR}/v2ray_api.pb.cc") set(API_PROTO_HDRS "${CMAKE_CURRENT_BINARY_DIR}/v2ray_api.pb.h") set(API_GRPC_SRCS "${CMAKE_CURRENT_BINARY_DIR}/v2ray_api.grpc.pb.cc") @@ -26,12 +27,12 @@ if(NOT USE_LIBQVB) OUTPUT "${API_GRPC_SRCS}" "${API_GRPC_HDRS}" "${API_PROTO_HDRS}" "${API_PROTO_SRCS}" COMMAND ${Protobuf_PROTOC_EXECUTABLE} ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}" - --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" - -I "${API_PROTO_PATH}" - --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" - "${API_PROTO}" + --cpp_out "${CMAKE_CURRENT_BINARY_DIR}" + -I "${API_PROTO_PATH}" + --plugin=protoc-gen-grpc="${GRPC_CPP_PLUGIN}" + "${API_PROTO}" DEPENDS "${API_PROTO}" - ) + ) else() add_definitions(-DBACKEND_LIBQVB) if(UNIX AND NOT APPLE) diff --git a/cmake/backward-cpp.cmake b/cmake/backward-cpp.cmake new file mode 100644 index 00000000..58432b00 --- /dev/null +++ b/cmake/backward-cpp.cmake @@ -0,0 +1,46 @@ +include(${CMAKE_SOURCE_DIR}/3rdparty/backward-cpp/BackwardConfig.cmake) + +# check if compiler is nvcc or nvcc_wrapper +set(COMPILER_IS_NVCC false) +get_filename_component(COMPILER_NAME ${CMAKE_CXX_COMPILER} NAME) +if (COMPILER_NAME MATCHES "^nvcc") + set(COMPILER_IS_NVCC true) +endif() + +if (DEFINED ENV{OMPI_CXX} OR DEFINED ENV{MPICH_CXX}) + if ( ($ENV{OMPI_CXX} MATCHES "nvcc") OR ($ENV{MPICH_CXX} MATCHES "nvcc") ) + set(COMPILER_IS_NVCC true) + endif() +endif() + +# set CXX standard +set(CMAKE_CXX_STANDARD_REQUIRED True) +if (${COMPILER_IS_NVCC}) + # GNU CXX extensions are not supported by nvcc + set(CMAKE_CXX_EXTENSIONS OFF) +endif() + +############################################################################### +# COMPILER FLAGS +############################################################################### + +if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -g") +endif() + +############################################################################### +# BACKWARD OBJECT +############################################################################### + +add_library(backward_object OBJECT ${CMAKE_SOURCE_DIR}/3rdparty/backward-cpp/backward.cpp) +target_compile_definitions(backward_object PRIVATE ${BACKWARD_DEFINITIONS}) +target_include_directories(backward_object PRIVATE ${BACKWARD_INCLUDE_DIRS}) +set(BACKWARD_ENABLE $ CACHE STRING + "Link with this object to setup backward automatically") + +############################################################################### +# BACKWARD LIBRARY (Includes backward.cpp) +############################################################################### +add_library(backward ${CMAKE_SOURCE_DIR}/3rdparty/backward-cpp/backward.cpp) +target_compile_definitions(backward PUBLIC ${BACKWARD_DEFINITIONS}) +target_include_directories(backward PUBLIC ${BACKWARD_INCLUDE_DIRS}) diff --git a/cmake/components/qv2ray-lib.cmake b/cmake/components/qv2ray-lib.cmake new file mode 100644 index 00000000..5650901a --- /dev/null +++ b/cmake/components/qv2ray-lib.cmake @@ -0,0 +1,109 @@ +set(QV2RAY_BASE_HEADERS + ${CMAKE_SOURCE_DIR}/src/base/JsonHelpers.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/CoreObjectModels.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvConfigIdentifier.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvRuntimeConfig.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvSafeType.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvCoreSettings.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvSettingsObject.hpp + ${CMAKE_SOURCE_DIR}/src/base/models/QvStartupConfig.hpp + ${CMAKE_SOURCE_DIR}/src/base/Qv2rayBase.hpp + ${CMAKE_SOURCE_DIR}/src/base/Qv2rayFeatures.hpp + ${CMAKE_SOURCE_DIR}/src/base/Qv2rayLog.hpp + ) + +set(QV2RAY_LIB_SOURCES + # headers + ${CMAKE_SOURCE_DIR}/src/base/Qv2rayLog.cpp + ${CMAKE_SOURCE_DIR}/src/common/HTTPRequestHelper.cpp + ${CMAKE_SOURCE_DIR}/src/common/HTTPRequestHelper.hpp + ${CMAKE_SOURCE_DIR}/src/common/QJsonModel.cpp + ${CMAKE_SOURCE_DIR}/src/common/QJsonModel.hpp + ${CMAKE_SOURCE_DIR}/src/common/QvHelpers.cpp + ${CMAKE_SOURCE_DIR}/src/common/QvHelpers.hpp + ${CMAKE_SOURCE_DIR}/src/common/QvTranslator.cpp + ${CMAKE_SOURCE_DIR}/src/common/QvTranslator.hpp + # Components + ${CMAKE_SOURCE_DIR}/src/components/autolaunch/QvAutoLaunch.cpp + ${CMAKE_SOURCE_DIR}/src/components/autolaunch/QvAutoLaunch.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/geosite/QvGeositeReader.cpp + ${CMAKE_SOURCE_DIR}/src/components/geosite/QvGeositeReader.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/latency/LatencyTest.cpp + ${CMAKE_SOURCE_DIR}/src/components/latency/LatencyTest.hpp + ${CMAKE_SOURCE_DIR}/src/components/latency/LatencyTestThread.cpp + ${CMAKE_SOURCE_DIR}/src/components/latency/LatencyTestThread.hpp + ${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.cpp + ${CMAKE_SOURCE_DIR}/src/components/latency/TCPing.hpp + ${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPing.cpp + ${CMAKE_SOURCE_DIR}/src/components/latency/win/ICMPPing.hpp + ${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.cpp + ${CMAKE_SOURCE_DIR}/src/components/latency/unix/ICMPPing.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/ntp/QvNTPClient.cpp + ${CMAKE_SOURCE_DIR}/src/components/ntp/QvNTPClient.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/plugins/QvPluginHost.cpp + ${CMAKE_SOURCE_DIR}/src/components/plugins/QvPluginHost.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/port/QvPortDetector.cpp + ${CMAKE_SOURCE_DIR}/src/components/port/QvPortDetector.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/proxy/QvProxyConfigurator.cpp + ${CMAKE_SOURCE_DIR}/src/components/proxy/QvProxyConfigurator.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/route/presets/RouteScheme_V2rayN.hpp + ${CMAKE_SOURCE_DIR}/src/components/route/RouteSchemeIO.cpp + ${CMAKE_SOURCE_DIR}/src/components/route/RouteSchemeIO.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/update/UpdateChecker.cpp + ${CMAKE_SOURCE_DIR}/src/components/update/UpdateChecker.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/darkmode/DarkmodeDetector.cpp + ${CMAKE_SOURCE_DIR}/src/components/darkmode/DarkmodeDetector.hpp + # + ${CMAKE_SOURCE_DIR}/src/components/speedchart/speedplotview.cpp + ${CMAKE_SOURCE_DIR}/src/components/speedchart/speedplotview.hpp + ${CMAKE_SOURCE_DIR}/src/components/speedchart/speedwidget.cpp + ${CMAKE_SOURCE_DIR}/src/components/speedchart/speedwidget.hpp + # + ${CMAKE_SOURCE_DIR}/src/core/connection/ConnectionIO.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/ConnectionIO.hpp + ${CMAKE_SOURCE_DIR}/src/core/connection/Generation.hpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/final.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/inbounds.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/outbounds.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/filters.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/routing.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/generation/misc.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/Serialization.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/Serialization.hpp + ${CMAKE_SOURCE_DIR}/src/core/connection/serialization/ss.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/serialization/ssd.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/serialization/vmess.cpp + ${CMAKE_SOURCE_DIR}/src/core/connection/serialization/vmess_new.cpp + # + ${CMAKE_SOURCE_DIR}/src/core/CoreUtils.cpp + ${CMAKE_SOURCE_DIR}/src/core/CoreUtils.hpp + # + ${CMAKE_SOURCE_DIR}/src/core/handler/ConfigHandler.cpp + ${CMAKE_SOURCE_DIR}/src/core/handler/ConfigHandler.hpp + ${CMAKE_SOURCE_DIR}/src/core/handler/KernelInstanceHandler.cpp + ${CMAKE_SOURCE_DIR}/src/core/handler/KernelInstanceHandler.hpp + ${CMAKE_SOURCE_DIR}/src/core/handler/RouteHandler.cpp + ${CMAKE_SOURCE_DIR}/src/core/handler/RouteHandler.hpp + # + ${CMAKE_SOURCE_DIR}/src/core/kernel/APIBackend.cpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/APIBackend.hpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/PluginKernelInteractions.cpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/PluginKernelInteractions.hpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/QvKernelABIChecker.cpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/QvKernelABIChecker.hpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/V2rayKernelInteractions.cpp + ${CMAKE_SOURCE_DIR}/src/core/kernel/V2rayKernelInteractions.hpp + # + ${CMAKE_SOURCE_DIR}/src/core/settings/SettingsBackend.cpp + ${CMAKE_SOURCE_DIR}/src/core/settings/SettingsBackend.hpp + ${CMAKE_SOURCE_DIR}/src/core/settings/SettingsUpgrade.cpp + ) diff --git a/cmake/components/qv2ray-ui.cmake b/cmake/components/qv2ray-ui.cmake new file mode 100644 index 00000000..0916a8f9 --- /dev/null +++ b/cmake/components/qv2ray-ui.cmake @@ -0,0 +1,93 @@ +set(QV2RAY_UI_FORMS + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_OutboundEditor.ui + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_InboundEditor.ui + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_JsonEditor.ui + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_RoutesEditor.ui + # + ${CMAKE_SOURCE_DIR}/src/ui/widgets/StreamSettingsWidget.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionInfoWidget.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionItemWidget.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/RouteSettingsMatrix.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/InboundSettingsWidget.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionSettingsWidget.ui + ${CMAKE_SOURCE_DIR}/src/ui/widgets/DnsSettingsWidget.ui + # + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_GroupManager.ui + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ImportConfig.ui + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_MainWindow.ui + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PreferencesWindow.ui + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PluginManager.ui + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ScreenShot_Core.ui + ) + +set(QV2RAY_UI_SOURCES + # Qv2ray Application + ${CMAKE_SOURCE_DIR}/src/StackTraceHelper.hpp + ${CMAKE_SOURCE_DIR}/src/StackTraceHelper.cpp + ${CMAKE_SOURCE_DIR}/src/Qv2rayApplication.cpp + ${CMAKE_SOURCE_DIR}/src/Qv2rayApplication.hpp + # Common Utils + ${CMAKE_SOURCE_DIR}/src/ui/common/QvDialog.hpp + ${CMAKE_SOURCE_DIR}/src/ui/common/QRCodeHelper.cpp + ${CMAKE_SOURCE_DIR}/src/ui/common/QRCodeHelper.hpp + ${CMAKE_SOURCE_DIR}/src/ui/common/UIBase.hpp + ${CMAKE_SOURCE_DIR}/src/ui/common/JsonHighlighter.hpp + ${CMAKE_SOURCE_DIR}/src/ui/common/JsonHighlighter.cpp + ${CMAKE_SOURCE_DIR}/src/ui/common/LogHighlighter.hpp + ${CMAKE_SOURCE_DIR}/src/ui/common/LogHighlighter.cpp + # Message bus + ${CMAKE_SOURCE_DIR}/src/ui/messaging/QvMessageBus.hpp + ${CMAKE_SOURCE_DIR}/src/ui/messaging/QvMessageBus.cpp + # NodeEditor Models + ${CMAKE_SOURCE_DIR}/src/ui/models/NodeModelsBase.hpp + ${CMAKE_SOURCE_DIR}/src/ui/models/InboundNodeModel.hpp + ${CMAKE_SOURCE_DIR}/src/ui/models/InboundNodeModel.cpp + ${CMAKE_SOURCE_DIR}/src/ui/models/OutboundNodeModel.hpp + ${CMAKE_SOURCE_DIR}/src/ui/models/OutboundNodeModel.cpp + ${CMAKE_SOURCE_DIR}/src/ui/models/RuleNodeModel.hpp + ${CMAKE_SOURCE_DIR}/src/ui/models/RuleNodeModel.cpp + # Style Manager + ${CMAKE_SOURCE_DIR}/src/ui/styles/StyleManager.cpp + ${CMAKE_SOURCE_DIR}/src/ui/styles/StyleManager.hpp + # UI Widgets + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionInfoWidget.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionInfoWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/QvAutoCompleteTextEdit.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/QvAutoCompleteTextEdit.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/RouteSettingsMatrix.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/RouteSettingsMatrix.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionSettingsWidget.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionSettingsWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionItemWidget.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/ConnectionItemWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/StreamSettingsWidget.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/StreamSettingsWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/InboundSettingsWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/InboundSettingsWidget.hpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/DnsSettingsWidget.cpp + ${CMAKE_SOURCE_DIR}/src/ui/widgets/DnsSettingsWidget.hpp + # Editors + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_InboundEditor.cpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_InboundEditor.hpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_JsonEditor.cpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_JsonEditor.hpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_OutboundEditor.cpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_OutboundEditor.hpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_RoutesEditor.hpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_RoutesEditor.cpp + ${CMAKE_SOURCE_DIR}/src/ui/editors/w_RoutesEditor_extra.cpp + # Windows + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ImportConfig.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ImportConfig.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_MainWindow.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_MainWindow.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_MainWindow_extra.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PreferencesWindow.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PreferencesWindow.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PluginManager.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_PluginManager.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ScreenShot_Core.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_ScreenShot_Core.cpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_GroupManager.hpp + ${CMAKE_SOURCE_DIR}/src/ui/windows/w_GroupManager.cpp + ) diff --git a/cmake/cpp-httplib.cmake b/cmake/cpp-httplib.cmake deleted file mode 100644 index e09fe015..00000000 --- a/cmake/cpp-httplib.cmake +++ /dev/null @@ -1 +0,0 @@ -set(cpp-httplib_INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/3rdparty/cpp-httplib) diff --git a/cmake/deployment.cmake b/cmake/deployment.cmake index 3d3ab105..f75afc80 100644 --- a/cmake/deployment.cmake +++ b/cmake/deployment.cmake @@ -1,33 +1,3 @@ -# Directories to look for dependencies -set(DIRS "${CMAKE_BINARY_DIR}") - -# Path used for searching by FIND_XXX(), with appropriate suffixes added -if(CMAKE_PREFIX_PATH) - foreach(dir ${CMAKE_PREFIX_PATH}) - list(APPEND DIRS "${dir}/bin" "${dir}/lib") - endforeach() -endif() - -# Append Qt's lib folder which is two levels above Qt5Widgets_DIR -list(APPEND DIRS "${Qt5Widgets_DIR}/../..") -list(APPEND DIRS "/usr/local/lib") -list(APPEND DIRS "/usr/lib") - -if(MSVC) - if(NOT BUILD_NSIS) - set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION .) - endif() -endif() - -include(InstallRequiredSystemLibraries) - -message(STATUS "APPS: ${APPS}") -message(STATUS "QT_PLUGINS: ${QT_PLUGINS}") -message(STATUS "DIRS: ${DIRS}") - -install(CODE "include(BundleUtilities) - fixup_bundle(\"${APPS}\" \"${QT_PLUGINS}\" \"${DIRS}\")") - # Packaging set(CPACK_PACKAGE_VENDOR "Qv2ray Development Group") set(CPACK_PACKAGE_VERSION ${QV2RAY_VERSION_STRING}) @@ -45,6 +15,11 @@ if(MSVC) set(CPACK_NSIS_MUI_UNIICON "${CMAKE_SOURCE_DIR}/assets/icons/qv2ray.ico") set(CPACK_NSIS_DISPLAY_NAME "Qv2ray") set(CPACK_NSIS_PACKAGE_NAME "Qv2ray") + set(CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS " + ExecWait \\\"taskkill /f /im qv2ray.exe\\\" + ExecWait \\\"taskkill /f /im v2ray.exe\\\" + ExecWait \\\"taskkill /f /im wv2ray.exe\\\" + ") set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS " CreateShortCut \\\"$DESKTOP\\\\Qv2ray.lnk\\\" \\\"$INSTDIR\\\\qv2ray.exe\\\" CreateDirectory \\\"$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Qv2ray\\\" @@ -56,6 +31,7 @@ if(MSVC) WriteRegStr HKLM \\\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Uninstall\\\\qv2ray\\\" \\\"URLInfoAbout\\\" \\\"https://github.com/Qv2ray/Qv2ray\\\" ") set(CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS " + ExecWait \\\"taskkill /f /im qv2ray.exe\\\" Delete \\\"$DESKTOP\\\\Qv2ray.lnk\\\" Delete \\\"$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Qv2ray\\\\Qv2ray.lnk\\\" RMDir \\\"$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Qv2ray\\\" @@ -78,3 +54,33 @@ if(APPLE) endif() include(CPack) + +# Directories to look for dependencies +set(DIRS "${CMAKE_BINARY_DIR}") + +# Path used for searching by FIND_XXX(), with appropriate suffixes added +if(CMAKE_PREFIX_PATH) + foreach(dir ${CMAKE_PREFIX_PATH}) + list(APPEND DIRS "${dir}/bin" "${dir}/lib") + endforeach() +endif() + +# Append Qt's lib folder which is two levels above Qt5Widgets_DIR +list(APPEND DIRS "${Qt5Widgets_DIR}/../..") +list(APPEND DIRS "/usr/local/lib") +list(APPEND DIRS "/usr/lib") + +if(MSVC) + set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION .) +endif() + +include(InstallRequiredSystemLibraries) + +message(STATUS "APPS: ${APPS}") +message(STATUS "QT_PLUGINS: ${QT_PLUGINS}") +message(STATUS "DIRS: ${DIRS}") + +install(CODE " + include(BundleUtilities) + fixup_bundle(\"${APPS}\" \"\" \"${DIRS}\") + " COMPONENT Runtime) diff --git a/cmake/libsemver.cmake b/cmake/libsemver.cmake new file mode 100644 index 00000000..87141844 --- /dev/null +++ b/cmake/libsemver.cmake @@ -0,0 +1,4 @@ +set(LIBSEMVER_SOURCES + ${CMAKE_SOURCE_DIR}/3rdparty/libsemver/version.cpp + ${CMAKE_SOURCE_DIR}/3rdparty/libsemver/version.hpp + ) diff --git a/cmake/protobuf.cmake b/cmake/protobuf.cmake index 39573bbd..fea2e85b 100644 --- a/cmake/protobuf.cmake +++ b/cmake/protobuf.cmake @@ -1,5 +1,5 @@ find_package(Protobuf REQUIRED) -protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/tools/v2ray_geosite.proto) -set(QV2RAY_PROTOBUF_LIBRARY +protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS ${CMAKE_SOURCE_DIR}/assets/v2ray_geosite.proto) +set(QV2RAY_PROTOBUF_LIBRARY protobuf::libprotobuf -) \ No newline at end of file + ) diff --git a/cmake/qnodeeditor.cmake b/cmake/qnodeeditor.cmake index fe82837b..a4550f9d 100644 --- a/cmake/qnodeeditor.cmake +++ b/cmake/qnodeeditor.cmake @@ -23,14 +23,16 @@ if(QV2RAY_QNODEEDITOR_PROVIDER STREQUAL "module") ${QNODEEDITOR_DIR}/src/NodeStyle.cpp ${QNODEEDITOR_DIR}/src/Properties.cpp ${QNODEEDITOR_DIR}/src/StyleCollection.cpp - ) - set(QNODEEDITOR_INCLUDE_PATH - ${QNODEEDITOR_DIR}/include/nodes/internal - ) + ) - set(HEADERS_TO_MOC + set(QNODEEDITOR_INCLUDE_PATH + ${QNODEEDITOR_DIR}/include + ${QNODEEDITOR_DIR}/include/nodes/internal + ) + + set(QNODEEDITOR_HEADERS ${QNODEEDITOR_DIR}/include/nodes/internal/Compiler.hpp - ${QNODEEDITOR_DIR}/include/nodes/internal/Connection.hpp + ${QNODEEDITOR_DIR}/include/nodes/internal/Connection.hpp ${QNODEEDITOR_DIR}/include/nodes/internal/ConnectionGeometry.hpp ${QNODEEDITOR_DIR}/include/nodes/internal/ConnectionGraphicsObject.hpp ${QNODEEDITOR_DIR}/include/nodes/internal/ConnectionState.hpp @@ -56,17 +58,29 @@ if(QV2RAY_QNODEEDITOR_PROVIDER STREQUAL "module") ${QNODEEDITOR_DIR}/include/nodes/internal/Serializable.hpp ${QNODEEDITOR_DIR}/include/nodes/internal/Style.hpp ${QNODEEDITOR_DIR}/include/nodes/internal/TypeConverter.hpp - ) - - qt5_wrap_cpp(QNODEEDITOR_SOURCES - ${HEADERS_TO_MOC} - TARGET qv2ray - OPTIONS --no-notes # Don't display a note for the headers which don't produce a moc_*.cpp - ) + ) + # qt5_wrap_cpp(QNODEEDITOR_SOURCES + # ${HEADERS_TO_MOC} + # TARGET qv2ray + # OPTIONS --no-notes # Don't display a note for the headers which don't produce a moc_*.cpp + # ) + set(QNODEEDITOR_LIBRARY qv2ray-nodeeditor) + add_library(${QNODEEDITOR_LIBRARY} STATIC + ${QNODEEDITOR_SOURCES} + ${QNODEEDITOR_HEADERS} + ) + target_include_directories(${QNODEEDITOR_LIBRARY} PUBLIC + ${QNODEEDITOR_INCLUDE_PATH} + ) + target_link_libraries(${QNODEEDITOR_LIBRARY} + Qt5::Core + Qt5::Widgets + Qt5::Gui + ) set(QNODEEDITOR_QRC_RESOURCES ${QNODEEDITOR_DIR}/resources/resources.qrc) elseif(QV2RAY_QNODEEDITOR_PROVIDER STREQUAL "package") find_package(NodeEditor REQUIRED CONFIG) + find_path(QNODEEDITOR_INCLUDE_PATH NAMES Node.hpp PATH_SUFFIXES nodes/internal) set(QNODEEDITOR_LIBRARY NodeEditor::nodes) - find_path(QNODEEDITOR_INCLUDE_PATH Node.hpp PATH_SUFFIXES nodes/internal) -endif() \ No newline at end of file +endif() diff --git a/cmake/singleapplication.cmake b/cmake/singleapplication.cmake index 1f91b21b..d5925f63 100644 --- a/cmake/singleapplication.cmake +++ b/cmake/singleapplication.cmake @@ -1,6 +1,11 @@ add_definitions(-DQAPPLICATION_CLASS=QApplication) -set(SINGLEAPPLICATION_DIR ${CMAKE_SOURCE_DIR}/3rdparty/SingleApplication) -set(SINGLEAPPLICATION_SOURCES - ${SINGLEAPPLICATION_DIR}/singleapplication.cpp - ${SINGLEAPPLICATION_DIR}/singleapplication_p.cpp -) \ No newline at end of file +if(QV2RAY_SINGLEAPPLICATION_PROVIDER STREQUAL "module") + set(SINGLEAPPLICATION_DIR ${CMAKE_SOURCE_DIR}/3rdparty/SingleApplication) + set(SINGLEAPPLICATION_SOURCES + ${SINGLEAPPLICATION_DIR}/singleapplication.cpp + ${SINGLEAPPLICATION_DIR}/singleapplication_p.cpp + ) +elseif(QV2RAY_SINGLEAPPLICATION_PROVIDER STREQUAL "package") + find_library(SINGLEAPPLICATION_LIBRARY NAMES SingleApplication) + find_path(SINGLEAPPLICATION_DIR NAMES singleapplication.h PATH_SUFFIXES singleapplication) +endif() diff --git a/cmake/zxing-cpp.cmake b/cmake/zxing-cpp.cmake index 6ab908e9..5a85340f 100644 --- a/cmake/zxing-cpp.cmake +++ b/cmake/zxing-cpp.cmake @@ -125,7 +125,7 @@ if(QV2RAY_ZXING_PROVIDER STREQUAL "module") ${ZXING_DIR}/src/pdf417/PDFScanningDecoder.cpp ${ZXING_DIR}/src/pdf417/PDFHighLevelEncoder.cpp ${ZXING_DIR}/src/pdf417/PDFModulusPoly.cpp - ) + ) set(ZXING_INCLUDE_PATH ${ZXING_DIR}/src ${ZXING_DIR}/src/aztec @@ -136,10 +136,17 @@ if(QV2RAY_ZXING_PROVIDER STREQUAL "module") ${ZXING_DIR}/src/pdf417 ${ZXING_DIR}/src/qrcode ${ZXING_DIR}/src/textcodec - ) + ) + set(ZXING_LIBRARY qv2ray-zxing) + add_library(${ZXING_LIBRARY} STATIC + ${ZXING_SOURCES} + ) + target_include_directories(${ZXING_LIBRARY} PUBLIC + ${ZXING_INCLUDE_PATH} + ) elseif(QV2RAY_ZXING_PROVIDER STREQUAL "package") find_package(PkgConfig REQUIRED) pkg_check_modules(ZXING REQUIRED zxing) set(ZXING_LIBRARY ${ZXING_LIBRARIES}) set(ZXING_INCLUDE_PATH ${ZXING_INCLUDE_DIRS}) -endif() \ No newline at end of file +endif() diff --git a/debian/changelog b/debian/changelog index 1d4ffcfe..b860e6af 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,47 @@ +qv2ray (2.6.0~rc4-1) unstable; urgency=medium + + * fix: fixed ssd:// order issue, fixed #635 + * add: remove connections via delete key + * add: added qv2ray:// + * add: use env QV2RAY_CONFIG_PATH to specify config path, make darkmode theme cross-platform, refactor + * add: introducing Qv2rayApplication + + -- Guobang Bi Thu, 18 Jun 2020 16:10:05 +0800 + +qv2ray (2.6.0~rc3-1) unstable; urgency=medium + + * add: added DNS Settings and GroupRouteManager + * fix: bgcolor can't switch to default when swiching to flatwhite/psblack and then back to breeze or other + * refactor: change include structure + * fix: sort groups by displayName + * fix: improve GroupManager performance by 5000% + * fix: fixed Recent Connections jumplist + * fix: fixed EMBED_TRANSLATIONS option + * fix: prevent reading geosite/geoip every time + * add: added ICMP as latency tester + + -- Guobang Bi Wed, 27 May 2020 18:17:03 +0800 + +qv2ray (2.6.0~rc2-1) unstable; urgency=medium + + * fix: fixed tcping message + * refactor: refactored auto update algorithm + * fix: fixed Windows non-block TCPing + * fix: UI tweak, prevent connection list taking too much space + + -- Guobang Bi Mon, 18 May 2020 12:01:50 +0800 + +qv2ray (2.6.0~rc1-1) unstable; urgency=medium + + * NTP Check + * Port Detection + * Introduce unit test + * Introduce group editor/manager + * added vmess v1 upgrader (close #609) + * Refactor system proxy + + -- Guobang Bi Sun, 17 May 2020 20:23:35 +0800 + qv2ray (2.5.0-1) unstable; urgency=medium * updating translations diff --git a/debian/control b/debian/control index c3ce5108..7f4c0e05 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Build-Depends: debhelper (>= 11), protobuf-compiler (>= 3.6.1.3-1~), protobuf-compiler-grpc (>= 1.16.1-1~), qtbase5-dev (>= 5.11.3-1~), + qtdeclarative5-dev (>= 5.11.3-1~), qttools5-dev (>= 5.11.3-1~) Standards-Version: 4.5.0 Homepage: https://github.com/Qv2ray/Qv2ray @@ -21,4 +22,7 @@ Package: qv2ray Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Recommends: v2ray -Description: Qv2ray, A Qt frontend for v2ray. Written in c++. +Description: Qt frontend for v2ray + Project Qv2ray is a flexable cross platform Qt GUI client for V2ray. + It makes everything easier by using GUI editors. It also supports + ShadowsicksR, Trojan and NaiveProxy by making them plugins. diff --git a/debian/rules b/debian/rules index b102e12d..4d9457f4 100755 --- a/debian/rules +++ b/debian/rules @@ -16,6 +16,4 @@ export QT_SELECT := 5 dh $@ --buildsystem=cmake+ninja override_dh_auto_configure: - dh_auto_configure -- -DEMBED_TRANSLATIONS=ON -DCMAKE_BUILD_TYPE=Release - -override_dh_auto_test: + dh_auto_configure -- -DEMBED_TRANSLATIONS=ON -DBUILD_TESTING=ON diff --git a/hooks/pre-commit b/hooks/pre-commit new file mode 100755 index 00000000..a7d0ad5c --- /dev/null +++ b/hooks/pre-commit @@ -0,0 +1,12 @@ +#!/bin/bash +STAGE_FILES=$(git diff --cached --name-only --diff-filter=ACM -- 'makespec/BUILDVERSION') +#echo $STAGE_FILES +if test ${#STAGE_FILES} -gt 0 +then + echo 'BUILDVERSION already changed, not touching' +else + echo 'Increasing BUILDVERSION' + expr $(cat ./makespec/BUILDVERSION) + 1 > ./makespec/BUILDVERSION + cat ./makespec/BUILDVERSION + git add ./makespec/BUILDVERSION +fi diff --git a/libs/QJsonStruct b/libs/QJsonStruct new file mode 160000 index 00000000..91c3ca1c --- /dev/null +++ b/libs/QJsonStruct @@ -0,0 +1 @@ +Subproject commit 91c3ca1c3279448052b6be19dc8157517c35a7ca diff --git a/makespec/BUILDVERSION b/makespec/BUILDVERSION index 040c32b6..02d2a545 100644 --- a/makespec/BUILDVERSION +++ b/makespec/BUILDVERSION @@ -1 +1 @@ -5335 \ No newline at end of file +5697 diff --git a/makespec/VERSION b/makespec/VERSION index 437459cd..e70b4523 100644 --- a/makespec/VERSION +++ b/makespec/VERSION @@ -1 +1 @@ -2.5.0 +2.6.0 diff --git a/makespec/VERSIONSUFFIX b/makespec/VERSIONSUFFIX index 8b137891..2c1cea9a 100644 --- a/makespec/VERSIONSUFFIX +++ b/makespec/VERSIONSUFFIX @@ -1 +1 @@ - +-rc4 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index c1959052..d943b190 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,5 +1,5 @@ name: qv2ray -base: core18 +base: core20 adopt-info: qv2ray icon: assets/icons/qv2ray.png @@ -44,6 +44,15 @@ apps: desktop: "usr/share/applications/qv2ray.desktop" parts: + ppa: + plugin: nil + build-packages: + - software-properties-common + override-pull: | + sudo add-apt-repository ppa:ymshenyu/libuv + sudo apt-get update + sudo apt-get -y dist-upgrade + qv2ray: plugin: cmake source-type: git @@ -51,14 +60,13 @@ parts: parse-info: [usr/share/metainfo/qv2ray.metainfo.xml] build-packages: - build-essential - - qttools5-dev-tools - qttools5-dev - qt5-default - libgrpc++-dev - libprotobuf-dev - protobuf-compiler-grpc - - ninja-build - pkg-config + - libzxingcore-dev stage-packages: - libgcc1 - libstdc++6 @@ -70,11 +78,13 @@ parts: - libqt5network5 - libqt5widgets5 - libglib2.0-bin - configflags: + - libzxingcore1 + cmake-parameters: - -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_BUILD_TYPE=Release - - -GNinja - -DEMBED_TRANSLATIONS=ON + - -DFALL_BACK_TO_XDG_OPEN=ON + - -DQV2RAY_ZXING_PROVIDER=package override-pull: | snapcraftctl pull build_number=$(cat makespec/BUILDVERSION) @@ -84,7 +94,6 @@ parts: sed -i 's|^Icon=.*|Icon=/usr/share/icons/hicolor/256x256/apps/qv2ray.png|g' assets/qv2ray.desktop after: - desktop-qt5 - - ppa desktop-qt5: source: https://github.com/ubuntu/snapcraft-desktop-helpers.git @@ -110,21 +119,70 @@ parts: - locales-all - xdg-user-dirs - fcitx-frontend-qt5 - after: - - ppa qt5-gtk-platform: plugin: nil stage-packages: - qt5-gtk-platformtheme - after: - - ppa - ppa: - plugin: nil + qv2ray-ssr-plugin: + plugin: cmake + source-type: git + source: https://github.com/Qv2ray/QvPlugin-SSR.git + source-branch: dev build-packages: - - software-properties-common - - dirmngr - override-build: | - sudo add-apt-repository -y ppa:ymshenyu/qv2ray-deps - sudo apt-get dist-upgrade -y \ No newline at end of file + - build-essential + - libsodium-dev + - libuv1-dev + - libssl-dev + - qttools5-dev + - qt5-default + stage-packages: + - libgcc1 + - libstdc++6 + - libssl1.1 + - libqt5core5a + - libqt5gui5 + - libqt5network5 + - libqt5widgets5 + - libuv1 + - libsodium23 + cmake-parameters: + - -DCMAKE_INSTALL_PREFIX=/usr + - -DCMAKE_BUILD_TYPE=Release + - -DSSR_UVW_WITH_QT=ON + - -DUSE_SYSTEM_SODIUM=ON + - -DUSE_SYSTEM_LIBUV=ON + - -DSTATIC_LINK_LIBUV=OFF + - -DSTATIC_LINK_SODIUM=OFF + after: + - desktop-qt5 + + qv2ray-trojan-plugin: + plugin: cmake + source-type: git + source: https://github.com/Qv2ray/QvPlugin-Trojan.git + source-branch: dev + build-packages: + - build-essential + - libboost-system-dev + - libboost-program-options-dev + - libssl-dev + - qttools5-dev + - qt5-default + stage-packages: + - libgcc1 + - libstdc++6 + - libssl1.1 + - libqt5core5a + - libqt5gui5 + - libqt5network5 + - libqt5widgets5 + - libboost-program-options1.71.0 + - libboost-system1.71.0 + cmake-parameters: + - -DCMAKE_INSTALL_PREFIX=/usr + - -DCMAKE_BUILD_TYPE=Release + - -DFORCE_TCP_FASTOPEN=ON + after: + - desktop-qt5 \ No newline at end of file diff --git a/src/Qv2rayApplication.cpp b/src/Qv2rayApplication.cpp new file mode 100644 index 00000000..fb178819 --- /dev/null +++ b/src/Qv2rayApplication.cpp @@ -0,0 +1,541 @@ +#include "Qv2rayApplication.hpp" + +#include "3rdparty/libsemver/version.hpp" +#include "base/Qv2rayBase.hpp" +#include "common/QvHelpers.hpp" +#include "common/QvTranslator.hpp" +#include "core/handler/ConfigHandler.hpp" +#include "core/handler/RouteHandler.hpp" +#include "core/settings/SettingsBackend.hpp" +#include "ui/styles/StyleManager.hpp" +#include "ui/windows/w_MainWindow.hpp" + +#include +#include + +#ifdef Q_OS_WIN + #include +#endif + +namespace Qv2ray +{ + constexpr auto QV2RAY_CONFIG_PATH_ENV_NAME = "QV2RAY_CONFIG_PATH"; + + Qv2rayApplication::Qv2rayApplication(int &argc, char *argv[]) +#ifdef Q_OS_ANDROID + : QApplication(argc, argv) +#else + : SingleApplication(argc, argv, true, User | ExcludeAppPath | ExcludeAppVersion) +#endif + { + LOG(MODULE_INIT, "Qv2ray " QV2RAY_VERSION_STRING " on " + QSysInfo::prettyProductName() + " " + QSysInfo::currentCpuArchitecture()) + DEBUG(MODULE_INIT, "Qv2ray Start Time: " + QSTRN(QTime::currentTime().msecsSinceStartOfDay())) + DEBUG("QV2RAY_BUILD_INFO", QV2RAY_BUILD_INFO) + DEBUG("QV2RAY_BUILD_EXTRA_INFO", QV2RAY_BUILD_EXTRA_INFO) + DEBUG("QV2RAY_BUILD_NUMBER", QSTRN(QV2RAY_VERSION_BUILD)) + hTray = new QSystemTrayIcon(); + } + + void Qv2rayApplication::QuitApplication(int retCode) + { + isExiting = true; + QCoreApplication::exit(retCode); + } + + Qv2rayApplication::Qv2raySetupStatus Qv2rayApplication::SetupQv2ray() + { +#ifdef Q_OS_WIN + SetCurrentDirectory(applicationDirPath().toStdWString().c_str()); +#endif + // Install a default translater. From the OS/DE + Qv2rayTranslator = std::make_unique(); + Qv2rayTranslator->InstallTranslation(QLocale::system().name()); + // + setQuitOnLastWindowClosed(false); + +#ifndef Q_OS_ANDROID + connect(this, &SingleApplication::receivedMessage, this, &Qv2rayApplication::onMessageReceived); + connect(this, &SingleApplication::aboutToQuit, this, &Qv2rayApplication::aboutToQuitSlot); + if (isSecondary()) + { + Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::NORMAL; + sendMessage(JsonToString(Qv2rayProcessArgument.toJson(), QJsonDocument::Compact).toUtf8()); + return SINGLE_APPLICATION; + } +#endif + +#ifdef Q_OS_WIN + // Set special font in Windows + QFont font; + font.setPointSize(9); + font.setFamily("Microsoft YaHei"); + setFont(font); +#endif + +#ifdef Q_OS_LINUX + setFallbackSessionManagementEnabled(false); + connect(this, &QGuiApplication::commitDataRequest, [] { + ConnectionManager->SaveConnectionConfig(); + LOG(MODULE_INIT, "Quit triggered by session manager.") + }); +#endif + return NORMAL; + } + + void Qv2rayApplication::aboutToQuitSlot() + { + delete mainWindow; + delete hTray; + delete ConnectionManager; + delete RouteManager; + delete PluginHost; + delete StyleManager; + } + + void Qv2rayApplication::onMessageReceived(quint32 clientId, QByteArray _msg) + { + // Sometimes SingleApplication will send message with clientId == 0, ignore them. + if (clientId == instanceId()) + return; + const auto msg = Qv2rayProcessArguments::fromJson(JsonFromString(_msg)); + LOG(MODULE_INIT, "Client ID: " + QSTRN(clientId) + ", message received, version: " + msg.version) + DEBUG(MODULE_INIT, _msg) + // + const auto currentVersion = semver::version::from_string(QV2RAY_VERSION_STRING); + const auto newVersionString = msg.version.isEmpty() ? "0.0.0" : msg.version.toStdString(); + const auto newVersion = semver::version::from_string(newVersionString); + // + if (newVersion > currentVersion) + { + QTimer::singleShot(0, [=]() { + const auto newPath = msg.fullArgs.first(); + QString message; + message += tr("A new version of Qv2ray is attemping to start:") + NEWLINE; + message += NEWLINE; + message += tr("New version information: ") + NEWLINE; + message += tr("Qv2ray version: %1").arg(msg.version) + NEWLINE; + message += tr("Qv2ray path: %1").arg(newPath) + NEWLINE; + message += NEWLINE; + message += tr("Do you want to exit and launch that new version?"); + + const auto result = QvMessageBoxAsk(nullptr, tr("New version detected"), message); + if (result == QMessageBox::Yes) + { + Qv2rayProcessArgument._qvNewVersionPath = newPath; + QuitApplication(QV2RAY_NEW_VERSION); + } + }); + } + + for (const auto &argument : msg.arguments) + { + switch (argument) + { + case Qv2rayProcessArguments::EXIT: + { + QuitApplication(); + break; + } + case Qv2rayProcessArguments::NORMAL: + { + mainWindow->show(); + mainWindow->raise(); + mainWindow->activateWindow(); + break; + } + case Qv2rayProcessArguments::RECONNECT: + { + ConnectionManager->RestartConnection(); + break; + } + case Qv2rayProcessArguments::DISCONNECT: + { + ConnectionManager->StopConnection(); + break; + } + case Qv2rayProcessArguments::QV2RAY_LINK: + { + for (const auto &link : msg.links) + { + const auto url = QUrl::fromUserInput(link); + const auto command = url.host(); + auto subcommands = url.path().split("/"); + subcommands.removeAll(""); + QMap args; + for (const auto &kvp : QUrlQuery(url).queryItems()) + { + args.insert(kvp.first, kvp.second); + } + if (command == "open") + { + emit mainWindow->ProcessCommand(command, subcommands, args); + } + } + break; + } + } + } + } + + Qv2rayExitCode Qv2rayApplication::RunQv2ray() + { + // Show MainWindow + mainWindow = new MainWindow(); + return Qv2rayExitCode(exec()); + } + + bool Qv2rayApplication::FindAndCreateInitialConfiguration() + { + if (initialized) + { + LOG(MODULE_INIT, "Qv2ray has already been initialized!") + return false; + } + LOG(MODULE_INIT, "Application exec path: " + applicationDirPath()) + // Non-standard paths needs special handing for "_debug" + const auto currentPathConfig = applicationDirPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX; + const auto homeQv2ray = QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX; + // + // Standard paths already handles the "_debug" suffix for us. + const auto configQv2ray = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); + // + // + // Some built-in search paths for Qv2ray to find configs. (load the first one if possible). + QStringList configFilePaths; + const auto useManualConfigPath = qEnvironmentVariableIsSet(QV2RAY_CONFIG_PATH_ENV_NAME); + const auto manualConfigPath = qEnvironmentVariable(QV2RAY_CONFIG_PATH_ENV_NAME); + if (useManualConfigPath) + { + LOG(MODULE_INIT, "Using config path from env: " + manualConfigPath) + configFilePaths << manualConfigPath; + } + else + { + configFilePaths << currentPathConfig; + configFilePaths << configQv2ray; + configFilePaths << homeQv2ray; + } + QString configPath = ""; + bool hasExistingConfig = false; + + for (const auto &path : configFilePaths) + { + // Verify the config path, check if the config file exists and in the + // correct JSON format. True means we check for config existence as + // well. ----------------------------------------------|HERE| + bool isValidConfigPath = CheckSettingsPathAvailability(path, true); + + // If we already found a valid config file. just simply load it... + if (hasExistingConfig) + break; + + if (isValidConfigPath) + { + DEBUG(MODULE_INIT, "Path: " + path + " is valid.") + configPath = path; + hasExistingConfig = true; + } + else + { + LOG(MODULE_INIT, "Path: " + path + " does not contain a valid config file.") + } + } + + if (hasExistingConfig) + { + // Use the config path found by the checks above + SetConfigDirPath(configPath); + LOG(MODULE_INIT, "Using " + QV2RAY_CONFIG_DIR + " as the config path.") + } + else + { + // If there's no existing config. + // + // Create new config at these dirs, these are default values for each platform. + if (useManualConfigPath) + { + configPath = manualConfigPath; + } + else + { +#if defined(Q_OS_WIN) && !defined(QV2RAY_NO_ASIDECONFIG) + configPath = currentPathConfig; +#else + configPath = configQv2ray; +#endif + } + + bool hasPossibleNewLocation = QDir().mkpath(configPath) && CheckSettingsPathAvailability(configPath, false); + // Check if the dirs are write-able + if (!hasPossibleNewLocation) + { + // None of the path above can be used as a dir for storing config. + // Even the last folder failed to pass the check. + LOG(MODULE_INIT, "FATAL") + LOG(MODULE_INIT, " ---> CANNOT find a proper place to store Qv2ray config files.") + QvMessageBoxWarn(nullptr, tr("Cannot Start Qv2ray"), + tr("Cannot find a place to store config files.") + NEWLINE + // + tr("Qv2ray has searched these paths below:") + NEWLINE + NEWLINE + // + configFilePaths.join(NEWLINE) + NEWLINE + // + tr("It usually means you don't have the write permission to all of those locations.") + NEWLINE + // + tr("Qv2ray will now exit.")); // + return false; + } + + // Found a valid config dir, with write permission, but assume no config is located in it. + LOG(MODULE_INIT, "Set " + configPath + " as the config path.") + SetConfigDirPath(configPath); + + if (QFile::exists(QV2RAY_CONFIG_FILE)) + { + // As we already tried to load config from every possible dir. + // + // This condition branch (!hasExistingConfig check) holds the fact that current config dir, + // should NOT contain any valid file (at least in the same name) + // + // It usually means that QV2RAY_CONFIG_FILE here has a corrupted JSON format. + // + // Otherwise Qv2ray would have loaded this config already instead of notifying to create a new config in this folder. + // + LOG(MODULE_INIT, "This should not occur: Qv2ray config exists but failed to load.") + QvMessageBoxWarn(nullptr, tr("Failed to initialise Qv2ray"), + tr("Failed to determine the location of config file:") + NEWLINE + // + tr("Qv2ray has found a config file, but it failed to be loaded due to some errors.") + NEWLINE + // + tr("A workaround is to remove the this file and restart Qv2ray:") + NEWLINE + // + QV2RAY_CONFIG_FILE + NEWLINE + // + tr("Qv2ray will now exit.") + NEWLINE + // + tr("Please report if you think it's a bug.")); // + return false; + } + + Qv2rayConfigObject conf; + conf.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH)); + conf.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH)); + conf.logLevel = 3; + conf.uiConfig.language = QLocale::system().name(); + conf.defaultRouteConfig.dnsConfig.servers << QvConfig_DNS::DNSServerObject{ "1.1.1.1" } // + << QvConfig_DNS::DNSServerObject{ "8.8.8.8" } // + << QvConfig_DNS::DNSServerObject{ "8.8.4.4" }; + + // Save initial config. + SaveGlobalSettings(conf); + LOG(MODULE_INIT, "Created initial config file.") + } + + if (!QDir(QV2RAY_GENERATED_DIR).exists()) + { + // The dir used to generate final config file, for V2ray interaction. + QDir().mkdir(QV2RAY_GENERATED_DIR); + LOG(MODULE_INIT, "Created config generation dir at: " + QV2RAY_GENERATED_DIR) + } + return true; + } + + bool Qv2rayApplication::LoadConfiguration() + { + // Load the config for upgrade, but do not parse it to the struct. + auto conf = JsonFromString(StringFromFile(QV2RAY_CONFIG_FILE)); + const auto configVersion = conf["config_version"].toInt(); + + if (configVersion > QV2RAY_CONFIG_VERSION) + { + // Config version is larger than the current version... + // This is rare but it may happen.... + QvMessageBoxWarn(nullptr, tr("Qv2ray Cannot Continue"), // + tr("You are running a lower version of Qv2ray compared to the current config file.") + NEWLINE + // + tr("Please check if there's an issue explaining about it.") + NEWLINE + // + tr("Or submit a new issue if you think this is an error.") + NEWLINE + NEWLINE + // + tr("Qv2ray will now exit.")); + return false; + } + else if (configVersion < QV2RAY_CONFIG_VERSION) + { + // That is the config file needs to be upgraded. + conf = Qv2ray::UpgradeSettingsVersion(configVersion, QV2RAY_CONFIG_VERSION, conf); + } + + // Load config object from upgraded config QJsonObject + auto confObject = Qv2rayConfigObject::fromJson(conf); + + if (!Qv2rayTranslator->GetAvailableLanguages().contains(confObject.uiConfig.language)) + { + // Prevent empty. + LOG(MODULE_UI, "Setting default UI language to system locale.") + confObject.uiConfig.language = QLocale::system().name(); + } + + if (!Qv2rayTranslator->InstallTranslation(confObject.uiConfig.language)) + { + QvMessageBoxWarn(nullptr, "Translation Failed", + "Cannot load translation for " + confObject.uiConfig.language + NEWLINE + // + "English is now used." + NEWLINE + NEWLINE + // + "Please go to Preferences Window to change language or open an Issue"); + } + + // Let's save the config. + SaveGlobalSettings(confObject); + return true; + } + + void Qv2rayApplication::InitializeGlobalVariables() + { + StyleManager = new QvStyleManager(); + PluginHost = new QvPluginHost(); + RouteManager = new RouteHandler(); + ConnectionManager = new QvConfigHandler(); + StyleManager->ApplyStyle(GlobalConfig.uiConfig.theme); + } + + bool Qv2rayApplication::PreInitialize(int argc, char **argv) + { + QString errorMessage; + + { + QCoreApplication coreApp(argc, argv); + const auto &args = coreApp.arguments(); + Qv2rayProcessArgument.version = QV2RAY_VERSION_STRING; + Qv2rayProcessArgument.fullArgs = args; + switch (ParseCommandLine(&errorMessage, args)) + { + case QV2RAY_QUIT: return false; + case QV2RAY_ERROR: LOG(MODULE_INIT, errorMessage) return false; + default: break; + } +#ifdef Q_OS_WIN + const auto urlScheme = coreApp.applicationName(); + const auto appPath = QDir::toNativeSeparators(coreApp.applicationFilePath()); + const auto regPath = "HKEY_CURRENT_USER\\Software\\Classes\\" + urlScheme; + + QSettings reg(regPath, QSettings::NativeFormat); + + reg.setValue("Default", "Qv2ray"); + reg.setValue("URL Protocol", ""); + + reg.beginGroup("DefaultIcon"); + reg.setValue("Default", QString("%1,1").arg(appPath)); + reg.endGroup(); + + reg.beginGroup("shell"); + reg.beginGroup("open"); + reg.beginGroup("command"); + reg.setValue("Default", appPath + " %1"); +#endif + } + + // noScaleFactors = disable HiDPI + if (StartupOption.noScaleFactor) + { + LOG(MODULE_INIT, "Force set QT_SCALE_FACTOR to 1.") + DEBUG(MODULE_UI, "Original QT_SCALE_FACTOR was: " + qEnvironmentVariable("QT_SCALE_FACTOR")) + qputenv("QT_SCALE_FACTOR", "1"); + } + else + { + DEBUG(MODULE_INIT, "High DPI scaling is enabled.") + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif + } + return true; + } + + Qv2rayApplication::commandline_status Qv2rayApplication::ParseCommandLine(QString *errorMessage, const QStringList &args) + { + QCommandLineParser parser; + // + QCommandLineOption noAPIOption("noAPI", tr("Disable gRPC API subsystem")); + QCommandLineOption noPluginsOption("noPlugin", tr("Disable plugins feature")); + QCommandLineOption noScaleFactorOption("noScaleFactor", tr("Disable Qt UI scale factor")); + QCommandLineOption debugOption("debug", tr("Enable debug output")); + QCommandLineOption disconnectOption("disconnect", tr("Stop current connection")); + QCommandLineOption reconnectOption("reconnect", tr("Reconnect last connection")); + QCommandLineOption exitOption("exit", tr("Exit Qv2ray")); + // + parser.setApplicationDescription(tr("Qv2ray - A cross-platform Qt frontend for V2ray.")); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + // + parser.addOption(noAPIOption); + parser.addOption(noPluginsOption); + parser.addOption(noScaleFactorOption); + parser.addOption(debugOption); + parser.addOption(disconnectOption); + parser.addOption(reconnectOption); + parser.addOption(exitOption); + // + auto helpOption = parser.addHelpOption(); + auto versionOption = parser.addVersionOption(); + + if (!parser.parse(args)) + { + *errorMessage = parser.errorText(); + return QV2RAY_ERROR; + } + + if (parser.isSet(versionOption)) + { + parser.showVersion(); + return QV2RAY_QUIT; + } + + if (parser.isSet(helpOption)) + { + parser.showHelp(); + return QV2RAY_QUIT; + } + + for (const auto &arg : parser.positionalArguments()) + { + if (arg.startsWith("qv2ray://")) + { + Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::QV2RAY_LINK; + Qv2rayProcessArgument.links << arg; + } + } + + if (parser.isSet(exitOption)) + { + DEBUG(MODULE_INIT, "disconnectOption is set.") + Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::EXIT; + } + + if (parser.isSet(disconnectOption)) + { + DEBUG(MODULE_INIT, "disconnectOption is set.") + Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::DISCONNECT; + } + + if (parser.isSet(reconnectOption)) + { + DEBUG(MODULE_INIT, "reconnectOption is set.") + Qv2rayProcessArgument.arguments << Qv2rayProcessArguments::RECONNECT; + } + + if (parser.isSet(noAPIOption)) + { + DEBUG(MODULE_INIT, "noAPIOption is set.") + StartupOption.noAPI = true; + } + + if (parser.isSet(debugOption)) + { + DEBUG(MODULE_INIT, "debugOption is set.") + StartupOption.debugLog = true; + } + + if (parser.isSet(noScaleFactorOption)) + { + DEBUG(MODULE_INIT, "noScaleFactorOption is set.") + StartupOption.noScaleFactor = true; + } + + if (parser.isSet(noPluginsOption)) + { + DEBUG(MODULE_INIT, "noPluginOption is set.") + StartupOption.noPlugins = true; + } + + return QV2RAY_CONTINUE; + } + +} // namespace Qv2ray diff --git a/src/Qv2rayApplication.hpp b/src/Qv2rayApplication.hpp new file mode 100644 index 00000000..0efd2c4a --- /dev/null +++ b/src/Qv2rayApplication.hpp @@ -0,0 +1,108 @@ +#pragma once + +#include "libs/QJsonStruct/QJsonStruct.hpp" + +#include +#ifdef Q_OS_ANDROID + #include +#else + #include +#endif +class MainWindow; + +namespace Qv2ray +{ + enum Qv2rayExitCode + { + QV2RAY_NORMAL = 0, + QV2RAY_SECONDARY_INSTANCE = 0, + QV2RAY_PRE_INITIALIZE_FAIL = -1, + QV2RAY_EARLY_SETUP_FAIL = -2, + QV2RAY_CONFIG_PATH_FAIL = -3, + QV2RAY_CONFIG_FILE_FAIL = -4, + QV2RAY_SSL_FAIL = -5, + QV2RAY_NEW_VERSION = -6 + }; + struct Qv2rayProcessArguments + { + enum Argument + { + NORMAL = 0, + QV2RAY_LINK = 1, + EXIT = 2, + RECONNECT = 3, + DISCONNECT = 4 + }; + QList arguments; + QString version; + QString data; + QList links; + QList fullArgs; + // + QString _qvNewVersionPath; + JSONSTRUCT_REGISTER(Qv2rayProcessArguments, F(arguments, version, data, links, fullArgs)) + }; + + inline Qv2rayProcessArguments Qv2rayProcessArgument; +#ifdef Q_OS_ANDROID + class Qv2rayApplication : public QApplication +#else + class Qv2rayApplication : public SingleApplication +#endif + { + Q_OBJECT + + enum commandline_status + { + QV2RAY_ERROR, + QV2RAY_QUIT, + QV2RAY_CONTINUE + }; + + public: + enum Qv2raySetupStatus + { + NORMAL, + SINGLE_APPLICATION, + FAILED + }; + // + void QuitApplication(int retCode = 0); + static bool PreInitialize(int argc, char **argv); + explicit Qv2rayApplication(int &argc, char *argv[]); + Qv2raySetupStatus SetupQv2ray(); + bool FindAndCreateInitialConfiguration(); + bool LoadConfiguration(); + void InitializeGlobalVariables(); + Qv2rayExitCode RunQv2ray(); + + public: + QSystemTrayIcon **GetTrayIcon() + { + return &hTray; + } + void showMessage(const QString &m, const QIcon &icon, int msecs = 10000) + { + hTray->showMessage("Qv2ray", m, icon, msecs); + } + void showMessage(const QString &m, QSystemTrayIcon::MessageIcon icon = QSystemTrayIcon::Information, int msecs = 10000) + { + hTray->showMessage("Qv2ray", m, icon, msecs); + } + + private slots: + void aboutToQuitSlot(); + void onMessageReceived(quint32 clientID, QByteArray msg); + + private: + QSystemTrayIcon *hTray; + MainWindow *mainWindow; + static commandline_status ParseCommandLine(QString *errorMessage, const QStringList &args); + bool initialized = false; + }; +} // namespace Qv2ray + +using namespace Qv2ray; + +#define qvApp (dynamic_cast(QCoreApplication::instance())) +#define qvAppTrayIcon (*qvApp->GetTrayIcon()) diff --git a/src/StackTraceHelper.cpp b/src/StackTraceHelper.cpp new file mode 100644 index 00000000..28af3f22 --- /dev/null +++ b/src/StackTraceHelper.cpp @@ -0,0 +1,91 @@ +#include "StackTraceHelper.hpp" +namespace Qv2ray +{ +#ifdef Q_OS_UNIX + QString StackTraceHelper::GetStackTraceImpl_Unix() + { + backward::StackTrace st; + backward::TraceResolver resolver; + st.load_here(); + resolver.load_stacktrace(st); + // + #ifdef QV2RAY_PRINT_FULL_BACKTRACE + backward::Printer p; + std::stringstream o; + p.print(st, o); + QString msg; + msg += QString::fromStdString(o.str()); + return msg; + #else + QString msg; + for (size_t i = 0; i < st.size(); i++) + { + /* + * It works because in real life, most signals are not around failures of the memory allocator (ie: malloc) itself. + * As long as you can allocate memory, you are pretty ok. + * Now, here is an example where backward will deadlock: + * + * you buffer overflow inside your allocator data structure + * you ask your allocator to free or allocate something + * allocator acquires some locks + * allocator shit itself because its datastructures are corrupted + * signal is raised, backward-cpp kicks in + * while walking the stack, backward-cpp tries to allocate memory, calling your allocator... + * allocator tries to acquires some locks... + * + * oops, deadlock. + */ + auto trace = resolver.resolve(st[i]); + QString sourceFile; + if (!trace.source.filename.empty()) + sourceFile = QString("%0:[%1:%2]").arg(trace.source.filename.c_str()).arg(trace.source.line).arg(trace.source.col); + else + sourceFile = "[N/A]"; + + // #Index: [ADDRESS] Function File Line:Col + msg += QString("#%1: [%2] %3 in %4 --> %5\r\n") + .arg(i) + .arg(reinterpret_cast(trace.addr)) + .arg(trace.object_function.c_str()) + .arg(trace.object_filename.c_str()) + .arg(sourceFile); + } + return msg; + #endif + } +#endif + +#ifdef Q_OS_WIN + QString StackTraceHelper::GetStackTraceImpl_Windows() + { + void *stack[1024]; + HANDLE process = GetCurrentProcess(); + SymInitialize(process, NULL, TRUE); + SymSetOptions(SYMOPT_LOAD_ANYTHING); + WORD numberOfFrames = CaptureStackBackTrace(0, 1024, stack, NULL); + SYMBOL_INFO *symbol = (SYMBOL_INFO *) malloc(sizeof(SYMBOL_INFO) + (512 - 1) * sizeof(TCHAR)); + symbol->MaxNameLen = 512; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + DWORD displacement; + IMAGEHLP_LINE64 *line = (IMAGEHLP_LINE64 *) malloc(sizeof(IMAGEHLP_LINE64)); + line->SizeOfStruct = sizeof(IMAGEHLP_LINE64); + // + QString msg; + // + for (int i = 0; i < numberOfFrames; i++) + { + const auto address = (DWORD64) stack[i]; + SymFromAddr(process, address, NULL, symbol); + if (SymGetLineFromAddr64(process, address, &displacement, line)) + { + msg += QString("[%1]: %2 (%3:%4)\r\n").arg(symbol->Address).arg(symbol->Name).arg(line->FileName).arg(line->LineNumber); + } + else + { + msg += QString("[%1]: %2 SymGetLineFromAddr64[%3]\r\n").arg(symbol->Address).arg(symbol->Name).arg(GetLastError()); + } + } + return msg; + } +#endif +} // namespace Qv2ray diff --git a/src/StackTraceHelper.hpp b/src/StackTraceHelper.hpp new file mode 100644 index 00000000..b807a693 --- /dev/null +++ b/src/StackTraceHelper.hpp @@ -0,0 +1,36 @@ +#pragma once + +#include +#ifdef Q_OS_UNIX + #include "backward.hpp" + + #include +#endif +#ifdef Q_OS_WIN + #include + // + #include +#endif + +namespace Qv2ray +{ + class StackTraceHelper + { + public: + static QString GetStackTrace() + { +#ifdef Q_OS_UNIX + return GetStackTraceImpl_Unix(); +#elif defined(Q_OS_WIN) + return GetStackTraceImpl_Windows(); +#endif + } + + private: +#ifdef Q_OS_UNIX + static QString GetStackTraceImpl_Unix(); +#elif defined(Q_OS_WIN) + static QString GetStackTraceImpl_Windows(); +#endif + }; +} // namespace Qv2ray diff --git a/src/base/JsonHelpers.hpp b/src/base/JsonHelpers.hpp index 38114f90..3934b7a0 100644 --- a/src/base/JsonHelpers.hpp +++ b/src/base/JsonHelpers.hpp @@ -7,42 +7,13 @@ #define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2) #define CONCATENATE2(arg1, arg2) arg1##arg2 -#define EXPAND(x) x -#define FOR_EACH_1(what, x, ...) what(x) - -#define FOR_EACH_2(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_1(what, __VA_ARGS__)) -#define FOR_EACH_3(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_2(what, __VA_ARGS__)) -#define FOR_EACH_4(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_3(what, __VA_ARGS__)) -#define FOR_EACH_5(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_4(what, __VA_ARGS__)) -#define FOR_EACH_6(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_5(what, __VA_ARGS__)) -#define FOR_EACH_7(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_6(what, __VA_ARGS__)) -#define FOR_EACH_8(what, x, ...) \ - what(x); \ - EXPAND(FOR_EACH_7(what, __VA_ARGS__)) - -#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N()) -#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__)) -#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N -#define FOR_EACH_RSEQ_N() 8, 7, 6, 5, 4, 3, 2, 1, 0 -#define CONCATENATE(x, y) x##y -#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__)) -#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__) -#define JADDEx_(jsonObj, field) jsonObj.insert(#field, field); -#define JADDEx(field) JADDEx_(root, field) - // Add key value pair into JSON named 'root' +#define JADDEx(field) root.insert(#field, field); #define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__) -#define RROOT return root; +#define JAUTOREMOVE(jObj, key) \ + { \ + if ((jObj[key].isArray() && jObj[key].toArray().isEmpty()) || (jObj[key].isObject() && jObj[key].toObject().isEmpty()) || \ + (jObj[key].isString() && jObj[key].toString().isEmpty())) \ + jObj.remove(key); \ + } diff --git a/src/base/Qv2rayBase.hpp b/src/base/Qv2rayBase.hpp index 55d89ea8..597a9955 100644 --- a/src/base/Qv2rayBase.hpp +++ b/src/base/Qv2rayBase.hpp @@ -1,9 +1,7 @@ #pragma once // -#include #include #include -#include #include #include #include @@ -21,8 +19,6 @@ #include "base/models/QvSettingsObject.hpp" #include "base/models/QvStartupConfig.hpp" -using namespace std; -using namespace std::chrono; using namespace Qv2ray; using namespace Qv2ray::base; using namespace Qv2ray::base::safetype; @@ -31,6 +27,18 @@ using namespace Qv2ray::base::objects; using namespace Qv2ray::base::objects::protocol; using namespace Qv2ray::base::objects::transfer; +class _qv2ray_global_config_impl_details +{ + public: + static Qv2rayConfigObject _GlobalConfig; + static bool _isExiting; + static QString _Qv2rayConfigPath; +}; + +#define GlobalConfig (_qv2ray_global_config_impl_details::_GlobalConfig) +#define isExiting (_qv2ray_global_config_impl_details::_isExiting) +#define Qv2rayConfigPath (_qv2ray_global_config_impl_details::_Qv2rayConfigPath) + #define QV2RAY_BUILD_INFO QString(_QV2RAY_BUILD_INFO_STR_) #define QV2RAY_BUILD_EXTRA_INFO QString(_QV2RAY_BUILD_EXTRA_INFO_STR_) @@ -42,17 +50,14 @@ using namespace Qv2ray::base::objects::transfer; #endif // Get Configured Config Dir Path -#define QV2RAY_CONFIG_DIR (Qv2ray::Qv2rayConfigPath) +#define QV2RAY_CONFIG_DIR (Qv2rayConfigPath) #define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf") +// +#define QV2RAY_ROUTING_DIR (QV2RAY_CONFIG_DIR + "rounting/") #define QV2RAY_CONNECTIONS_DIR (QV2RAY_CONFIG_DIR + "connections/") -#define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/") +// #define QV2RAY_PLUGIN_SETTINGS_DIR (QV2RAY_CONFIG_DIR + "plugin_settings/") - -// Get GFWList and PAC file path. -#define QV2RAY_RULES_DIR (QV2RAY_CONFIG_DIR + "rules/") -#define QV2RAY_RULES_GFWLIST_PATH (QV2RAY_RULES_DIR + "gfwList.txt") -#define QV2RAY_RULES_PAC_PATH (QV2RAY_RULES_DIR + "pac.txt") - +// #define QV2RAY_CONFIG_FILE_EXTENSION ".qv2ray.json" #define QV2RAY_GENERATED_DIR (QV2RAY_CONFIG_DIR + "generated/") #define QV2RAY_GENERATED_FILE_PATH (QV2RAY_GENERATED_DIR + "config.gen.json") @@ -85,9 +90,11 @@ using namespace Qv2ray::base::objects::transfer; // GUI TOOLS #define RED(obj) \ - auto _temp = obj->palette(); \ - _temp.setColor(QPalette::Text, Qt::red); \ - obj->setPalette(_temp); + { \ + auto _temp = obj->palette(); \ + _temp.setColor(QPalette::Text, Qt::red); \ + obj->setPalette(_temp); \ + } #define BLACK(obj) obj->setPalette(QWidget::palette()); @@ -97,11 +104,11 @@ using namespace Qv2ray::base::objects::transfer; #define ACCESS_OPTIONAL_VALUE(obj) (obj.value()) #endif -#define Q_TRAYICON(name) (QIcon(GlobalConfig.uiConfig.useDarkTrayIcon ? ":/assets/icons/ui_dark/" name : ":/assets/icons/ui_light/" name)) +#define QV2RAY_COLORSCHEME_ROOT_X(flag) ((flag) ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/")) +#define QV2RAY_COLORSCHEME_ROOT QV2RAY_COLORSCHEME_ROOT_X(GlobalConfig.uiConfig.useDarkTheme) -#define QV2RAY_COLORSCHEME_ROOT \ - ((GlobalConfig.uiConfig.useDarkTheme) ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/")) #define QICON_R(file) QIcon(QV2RAY_COLORSCHEME_ROOT + file) +#define Q_TRAYICON(name) (QIcon(QV2RAY_COLORSCHEME_ROOT_X(GlobalConfig.uiConfig.useDarkTrayIcon) + name)) #define QSTRN(num) QString::number(num) @@ -115,48 +122,40 @@ using namespace Qv2ray::base::objects::transfer; #define QV2RAY_USE_FPROXY_KEY "_QV2RAY_USE_GLOBAL_FORWARD_PROXY_" -#define JSON_ROOT_TRY_REMOVE(obj) \ - if (root.contains(obj)) \ - { \ - root.remove(obj); \ - } - namespace Qv2ray { - // Extra header for QvConfigUpgrade.cpp - QJsonObject UpgradeSettingsVersion(int fromVersion, int toVersion, QJsonObject root); - - // Qv2ray runtime config - inline bool isExiting = false; - inline QString Qv2rayConfigPath = ""; - inline base::config::Qv2rayConfig GlobalConfig = base::config::Qv2rayConfig(); - // - inline void ExitQv2ray() - { - isExiting = true; - QApplication::quit(); - } - inline QStringList Qv2rayAssetsPaths(const QString &dirName) { // Configuration Path QStringList list; list << QV2RAY_CONFIG_DIR + dirName; + list << ":/" + dirName; // #ifdef Q_OS_LINUX // Linux platform directories. + list << QString("/lib/qv2ray/" + dirName); + list << QString("/usr/lib/qv2ray/" + dirName); + list << QString("/usr/local/lib/qv2ray/" + dirName); + // list << QString("/usr/share/qv2ray/" + dirName); list << QString("/usr/local/share/qv2ray/" + dirName); - list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, dirName, QStandardPaths::LocateDirectory); - list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, dirName, QStandardPaths::LocateDirectory); + // For AppImage? + list << QString(QDir(QCoreApplication::applicationDirPath() + "/../share/qv2ray/" + dirName).absolutePath()); + // For Snap + if (qEnvironmentVariableIsSet("SNAP")) + { + list << QString(qEnvironmentVariable("SNAP") + "/usr/share/qv2ray/" + dirName); + } #elif defined(Q_OS_MAC) // macOS platform directories. - list << QDir(QApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath(); + list << QDir(QCoreApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath(); #endif + list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, dirName, QStandardPaths::LocateDirectory); + list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, dirName, QStandardPaths::LocateDirectory); // This is the default behavior on Windows - list << QApplication::applicationDirPath() + "/" + dirName; + list << QCoreApplication::applicationDirPath() + "/" + dirName; list.removeDuplicates(); return list; - }; + } } // namespace Qv2ray diff --git a/src/base/Qv2rayFeatures.hpp b/src/base/Qv2rayFeatures.hpp index 2425dc43..2bdf1862 100644 --- a/src/base/Qv2rayFeatures.hpp +++ b/src/base/Qv2rayFeatures.hpp @@ -1,9 +1,22 @@ #pragma once +#include // Qv2ray build features. -// -// Always use libgRPC++ on windows platform. + +#ifdef Q_OS_LINUX + #define CanHasLibQvb 1 + #define NativeDarkmode 0 +#elif defined(Q_OS_MAC) + #define CanHasLibQvb 1 + #define NativeDarkmode 0 +#elif defined(Q_OS_WIN) + #define CanHasLibQvb 0 + #define NativeDarkmode 1 +#endif + #ifdef BACKEND_LIBQVB - #ifdef _WIN32 - #error "libQvb is not supported on Windows Platform" + #if !QvHasFeature(CanHasLibQvb) + #error Qv2ray API backend libQvb is not supported on this platform #endif #endif + +#define QvHasFeature(feat) ((feat / 1) == 1) diff --git a/src/base/Qv2rayLog.cpp b/src/base/Qv2rayLog.cpp index c2bb4f21..4871db38 100644 --- a/src/base/Qv2rayLog.cpp +++ b/src/base/Qv2rayLog.cpp @@ -4,6 +4,14 @@ #include +#ifdef Q_OS_ANDROID + #include +#endif + +Qv2rayConfigObject _qv2ray_global_config_impl_details::_GlobalConfig; +bool _qv2ray_global_config_impl_details::_isExiting; +QString _qv2ray_global_config_impl_details::_Qv2rayConfigPath; + namespace Qv2ray::base { // Forwarded from QvTinyLog @@ -15,7 +23,7 @@ namespace Qv2ray::base void __QV2RAY_LOG_FUNC__(int type, const std::string &func, int line, const QString &module, const QString &log) { auto logString = QString("[" % module % "]: " % log); - auto funcPrepend = QString::fromStdString(func + ":" + to_string(line) + " "); + auto funcPrepend = QString::fromStdString(func + ":" + std::to_string(line) + " "); #ifdef QT_DEBUG // Debug build version, we only print info for DEBUG logs and print @@ -40,7 +48,11 @@ namespace Qv2ray::base } } #endif - cout << logString.toStdString() << endl; +#ifdef Q_OS_ANDROID + __android_log_write(ANDROID_LOG_INFO, "Qv2ray", logString.toStdString().c_str()); +#else + std::cout << logString.toStdString() << std::endl; +#endif { QMutexLocker _(&__loggerMutex); __loggerBuffer->append(logString + NEWLINE); diff --git a/src/base/Qv2rayLog.hpp b/src/base/Qv2rayLog.hpp index c01499c0..61fbc88f 100644 --- a/src/base/Qv2rayLog.hpp +++ b/src/base/Qv2rayLog.hpp @@ -1,7 +1,6 @@ #pragma once #include -using namespace std; /* * Tiny log module. @@ -18,7 +17,7 @@ namespace Qv2ray::base #define QV2RAY_LOG_NORMAL 0 #define QV2RAY_LOG_DEBUG 1 -#define __LOG_IMPL(LEVEL, MODULE, MSG) __QV2RAY_LOG_FUNC__(LEVEL, Q_FUNC_INFO, __LINE__, MODULE, MSG); +#define __LOG_IMPL(LEVEL, MODULE, MSG) ::Qv2ray::base::__QV2RAY_LOG_FUNC__(LEVEL, Q_FUNC_INFO, __LINE__, MODULE, MSG); #define LOG(MODULE, MSG) __LOG_IMPL(QV2RAY_LOG_NORMAL, (MODULE), (MSG)); #define DEBUG(MODULE, MSG) __LOG_IMPL(QV2RAY_LOG_DEBUG, (MODULE), (MSG)); diff --git a/src/base/models/CoreObjectModels.hpp b/src/base/models/CoreObjectModels.hpp index 3c4e5a68..fa87d9b7 100644 --- a/src/base/models/CoreObjectModels.hpp +++ b/src/base/models/CoreObjectModels.hpp @@ -1,5 +1,6 @@ #pragma once -#include "3rdparty/x2struct/x2struct.hpp" +#include "libs/QJsonStruct/QJsonIO.hpp" +#include "libs/QJsonStruct/QJsonStruct.hpp" #include #include @@ -7,13 +8,49 @@ namespace Qv2ray::base::objects { + struct DNSObject + { + struct DNSServerObject + { + bool QV2RAY_DNS_IS_COMPLEX_DNS; + QString address; + int port; + QList domains; + QList expectIPs; + DNSServerObject() : QV2RAY_DNS_IS_COMPLEX_DNS(false), port(53){}; + DNSServerObject(const QString &_address) : DNSServerObject() + { + address = _address; + }; + + friend bool operator==(const DNSServerObject &left, const DNSServerObject &right) + { + return left.QV2RAY_DNS_IS_COMPLEX_DNS == right.QV2RAY_DNS_IS_COMPLEX_DNS && // + left.address == right.address && // + left.port == right.port && // + left.domains == right.domains && // + left.expectIPs == right.expectIPs; + } + JSONSTRUCT_REGISTER(DNSServerObject, F(QV2RAY_DNS_IS_COMPLEX_DNS, address, port, domains, expectIPs)) + }; + QMap hosts; + QList servers; + QString clientIp; + QString tag; + friend bool operator==(const DNSObject &left, const DNSObject &right) + { + return left.hosts == right.hosts && left.servers == right.servers && left.clientIp == right.clientIp && left.tag == right.tag; + } + JSONSTRUCT_REGISTER(DNSObject, F(hosts, servers, clientIp, tag)) + }; // // Used in config generation struct AccountObject { QString user; QString pass; - XTOSTRUCT(O(user, pass)) + AccountObject() : user(), pass(){}; + JSONSTRUCT_REGISTER(AccountObject, F(user, pass)) }; // // @@ -21,10 +58,8 @@ namespace Qv2ray::base::objects { QString tag; QList services; - ApiObject() : tag("api"), services() - { - } - XTOSTRUCT(O(tag, services)) + ApiObject() : tag("api"), services(){}; + JSONSTRUCT_REGISTER(ApiObject, F(tag, services)) }; // // @@ -32,10 +67,8 @@ namespace Qv2ray::base::objects { bool statsInboundUplink; bool statsInboundDownlink; - SystemPolicyObject() : statsInboundUplink(), statsInboundDownlink() - { - } - XTOSTRUCT(O(statsInboundUplink, statsInboundDownlink)) + SystemPolicyObject() : statsInboundUplink(), statsInboundDownlink(){}; + JSONSTRUCT_REGISTER(SystemPolicyObject, F(statsInboundUplink, statsInboundDownlink)) }; // // @@ -48,10 +81,8 @@ namespace Qv2ray::base::objects bool statsUserUplink; bool statsUserDownlink; int bufferSize; - LevelPolicyObject() : handshake(), connIdle(), uplinkOnly(), downlinkOnly(), statsUserUplink(), statsUserDownlink(), bufferSize() - { - } - XTOSTRUCT(O(handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize)) + LevelPolicyObject() : handshake(), connIdle(), uplinkOnly(), downlinkOnly(), statsUserUplink(), statsUserDownlink(), bufferSize(){}; + JSONSTRUCT_REGISTER(LevelPolicyObject, F(handshake, connIdle, uplinkOnly, downlinkOnly, statsUserUplink, statsUserDownlink, bufferSize)) }; // // @@ -59,10 +90,8 @@ namespace Qv2ray::base::objects { QMap level; QList system; - PolicyObject() : level(), system() - { - } - XTOSTRUCT(O(level, system)) + PolicyObject() : level(), system(){}; + JSONSTRUCT_REGISTER(PolicyObject, F(level, system)) }; // // @@ -87,11 +116,9 @@ namespace Qv2ray::base::objects QString balancerTag; RuleObject() : QV2RAY_RULE_ENABLED(true), QV2RAY_RULE_USE_BALANCER(false), QV2RAY_RULE_TAG("new rule"), type("field"), domain(), ip(), - port("1-65535"), network(""), source(), user(), inboundTag(), protocol(), attrs(), outboundTag(""), balancerTag("") - { - } - XTOSTRUCT(O(QV2RAY_RULE_ENABLED, QV2RAY_RULE_USE_BALANCER, QV2RAY_RULE_TAG, type, domain, ip, port, network, source, user, inboundTag, - protocol, attrs, outboundTag, balancerTag)) + port("1-65535"), network(""), source(), user(), inboundTag(), protocol(), attrs(), outboundTag(""), balancerTag(""){}; + JSONSTRUCT_REGISTER(RuleObject, F(QV2RAY_RULE_ENABLED, QV2RAY_RULE_USE_BALANCER, QV2RAY_RULE_TAG, type, domain, ip, port, network, + source, user, inboundTag, protocol, attrs, outboundTag, balancerTag)) }; // // @@ -99,10 +126,8 @@ namespace Qv2ray::base::objects { QString tag; QList selector; - BalancerObject() : tag(), selector() - { - } - XTOSTRUCT(O(tag, selector)) + BalancerObject() : tag(), selector(){}; + JSONSTRUCT_REGISTER(BalancerObject, F(tag, selector)) }; // // @@ -114,10 +139,8 @@ namespace Qv2ray::base::objects QString method; QList path; QMap> headers; - HTTPRequestObject() : version("1.1"), method("GET"), path(), headers() - { - } - XTOSTRUCT(O(version, method, path, headers)) + HTTPRequestObject() : version("1.1"), method("GET"), path(), headers(){}; + JSONSTRUCT_REGISTER(HTTPRequestObject, F(version, method, path, headers)) }; // // @@ -127,10 +150,8 @@ namespace Qv2ray::base::objects QString status; QString reason; QMap> headers; - HTTPResponseObject() : version("1.1"), status("200"), reason("OK"), headers() - { - } - XTOSTRUCT(O(version, status, reason, headers)) + HTTPResponseObject() : version("1.1"), status("200"), reason("OK"), headers(){}; + JSONSTRUCT_REGISTER(HTTPResponseObject, F(version, status, reason, headers)) }; // // @@ -139,30 +160,24 @@ namespace Qv2ray::base::objects QString type; HTTPRequestObject request; HTTPResponseObject response; - TCPHeader_M_Object() : type("none"), request(), response() - { - } - XTOSTRUCT(O(type, request, response)) + TCPHeader_M_Object() : type("none"), request(), response(){}; + JSONSTRUCT_REGISTER(TCPHeader_M_Object, F(type, request, response)) }; // // struct HeaderObject { QString type; - HeaderObject() : type("none") - { - } - XTOSTRUCT(O(type)) + HeaderObject() : type("none"){}; + JSONSTRUCT_REGISTER(HeaderObject, F(type)) }; // // struct TCPObject { TCPHeader_M_Object header; - TCPObject() : header() - { - } - XTOSTRUCT(O(header)) + TCPObject() : header(){}; + JSONSTRUCT_REGISTER(TCPObject, F(header)) }; // // @@ -175,11 +190,11 @@ namespace Qv2ray::base::objects bool congestion = false; int readBufferSize = 1; int writeBufferSize = 1; + QString seed; HeaderObject header; - KCPObject() : header() - { - } - XTOSTRUCT(O(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header)) + KCPObject() : header(){}; + JSONSTRUCT_REGISTER(KCPObject, + F(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header, seed)) }; // // @@ -187,10 +202,8 @@ namespace Qv2ray::base::objects { QString path; QMap headers; - WebSocketObject() : path("/"), headers() - { - } - XTOSTRUCT(O(path, headers)) + WebSocketObject() : path("/"), headers(){}; + JSONSTRUCT_REGISTER(WebSocketObject, F(path, headers)) }; // // @@ -198,20 +211,16 @@ namespace Qv2ray::base::objects { QList host; QString path; - HttpObject() : host(), path("/") - { - } - XTOSTRUCT(O(host, path)) + HttpObject() : host(), path("/"){}; + JSONSTRUCT_REGISTER(HttpObject, F(host, path)) }; // // struct DomainSocketObject { QString path; - DomainSocketObject() : path("/") - { - } - XTOSTRUCT(O(path)) + DomainSocketObject() : path("/"){}; + JSONSTRUCT_REGISTER(DomainSocketObject, F(path)) }; // // @@ -220,10 +229,8 @@ namespace Qv2ray::base::objects QString security; QString key; HeaderObject header; - QuicObject() : security(""), key(""), header() - { - } - XTOSTRUCT(O(security, key, header)) + QuicObject() : security(""), key(""), header(){}; + JSONSTRUCT_REGISTER(QuicObject, F(security, key, header)) }; // // @@ -232,10 +239,8 @@ namespace Qv2ray::base::objects int mark; bool tcpFastOpen; QString tproxy; - SockoptObject() : mark(0), tcpFastOpen(false), tproxy("off") - { - } - XTOSTRUCT(O(mark, tcpFastOpen, tproxy)) + SockoptObject() : mark(0), tcpFastOpen(false), tproxy("off"){}; + JSONSTRUCT_REGISTER(SockoptObject, F(mark, tcpFastOpen, tproxy)) }; // // @@ -246,10 +251,8 @@ namespace Qv2ray::base::objects QString keyFile; QList certificate; QList key; - CertificateObject() : usage(), certificateFile(), keyFile(), certificate(), key() - { - } - XTOSTRUCT(O(usage, certificateFile, keyFile, certificate, key)) + CertificateObject() : usage(), certificateFile(), keyFile(), certificate(), key(){}; + JSONSTRUCT_REGISTER(CertificateObject, F(usage, certificateFile, keyFile, certificate, key)) }; // // @@ -258,13 +261,14 @@ namespace Qv2ray::base::objects QString serverName; bool allowInsecure; bool allowInsecureCiphers; + bool disableSessionResumption; QList alpn; QList certificates; bool disableSystemRoot; - TLSObject() : serverName(), allowInsecure(), allowInsecureCiphers(), certificates(), disableSystemRoot() - { - } - XTOSTRUCT(O(serverName, allowInsecure, allowInsecureCiphers, alpn, certificates, disableSystemRoot)) + TLSObject() + : serverName(), allowInsecure(), allowInsecureCiphers(), disableSessionResumption(true), certificates(), disableSystemRoot(){}; + JSONSTRUCT_REGISTER(TLSObject, F(serverName, allowInsecure, allowInsecureCiphers, disableSessionResumption, alpn, certificates, + disableSystemRoot)) }; } // namespace transfer // @@ -273,10 +277,8 @@ namespace Qv2ray::base::objects { bool enabled = false; QList destOverride; - SniffingObject() : enabled(), destOverride() - { - } - XTOSTRUCT(O(enabled, destOverride)) + SniffingObject() : enabled(), destOverride(){}; + JSONSTRUCT_REGISTER(SniffingObject, F(enabled, destOverride)) }; // // @@ -294,10 +296,9 @@ namespace Qv2ray::base::objects transfer::QuicObject quicSettings; StreamSettingsObject() : network("tcp"), security("none"), sockopt(), tlsSettings(), tcpSettings(), kcpSettings(), wsSettings(), httpSettings(), - dsSettings(), quicSettings() - { - } - XTOSTRUCT(O(network, security, sockopt, tcpSettings, tlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings)) + dsSettings(), quicSettings(){}; + JSONSTRUCT_REGISTER(StreamSettingsObject, F(network, security, sockopt, tcpSettings, tlsSettings, kcpSettings, wsSettings, httpSettings, + dsSettings, quicSettings)) }; // // @@ -305,10 +306,8 @@ namespace Qv2ray::base::objects { bool enabled; int concurrency; - MuxObject() : enabled(), concurrency() - { - } - XTOSTRUCT(O(enabled, concurrency)) + MuxObject() : enabled(), concurrency(){}; + JSONSTRUCT_REGISTER(MuxObject, F(enabled, concurrency)) }; // // Some protocols from: https://v2ray.com/chapter_02/02_protocols.html @@ -320,10 +319,8 @@ namespace Qv2ray::base::objects QString network; QString address; int port; - DNSOut() : network(""), address("0.0.0.0"), port(0) - { - } - XTOSTRUCT(O(network, address, port)) + DNSOut() : network(""), address("0.0.0.0"), port(0){}; + JSONSTRUCT_REGISTER(DNSOut, F(network, address, port)) }; // // MTProto, InBound || OutBound @@ -334,13 +331,11 @@ namespace Qv2ray::base::objects QString email; int level; QString secret; - UserObject() : email("user@domain.com"), level(0), secret("") - { - } - XTOSTRUCT(O(email, level, secret)) + UserObject() : email("user@domain.com"), level(0), secret(""){}; + JSONSTRUCT_REGISTER(UserObject, F(email, level, secret)) }; QList users; - XTOSTRUCT(O(users)) + JSONSTRUCT_REGISTER(MTProtoIn, F(users)) }; // // Socks, OutBound @@ -351,19 +346,14 @@ namespace Qv2ray::base::objects QString user; QString pass; int level; - UserObject() : user(), pass(), level(0) - { - } - XTOSTRUCT(O(user, pass, level)) + UserObject() : user(), pass(), level(0){}; + JSONSTRUCT_REGISTER(UserObject, F(user, pass, level)) }; - QString address; int port; QList users; - SocksServerObject() : address("0.0.0.0"), port(0), users() - { - } - XTOSTRUCT(O(address, port, users)) + SocksServerObject() : address("0.0.0.0"), port(0), users(){}; + JSONSTRUCT_REGISTER(SocksServerObject, F(address, port, users)) }; // // VMess Server @@ -375,19 +365,16 @@ namespace Qv2ray::base::objects int alterId; QString security; int level; - UserObject() : id(""), alterId(64), security("auto"), level(0) - { - } - XTOSTRUCT(O(id, alterId, security, level)) + QString testsEnabled; + UserObject() : id(), alterId(64), security("auto"), level(0), testsEnabled("none"){}; + JSONSTRUCT_REGISTER(UserObject, F(id, alterId, security, level, testsEnabled)) }; QString address; int port; QList users; - VMessServerObject() : address(""), port(0), users() - { - } - XTOSTRUCT(O(address, port, users)) + VMessServerObject() : address(""), port(0), users(){}; + JSONSTRUCT_REGISTER(VMessServerObject, F(address, port, users)) }; // // ShadowSocks Server @@ -401,10 +388,8 @@ namespace Qv2ray::base::objects int level; int port; ShadowSocksServerObject() - : email("user@domain.com"), address("0.0.0.0"), method("aes-256-cfb"), password(""), ota(false), level(0), port(0) - { - } - XTOSTRUCT(O(email, address, port, method, password, ota, level)) + : email("user@domain.com"), address("0.0.0.0"), method("aes-256-cfb"), password(""), ota(false), level(0), port(0){}; + JSONSTRUCT_REGISTER(ShadowSocksServerObject, F(email, address, port, method, password, ota, level)) }; } // namespace protocol } // namespace Qv2ray::base::objects diff --git a/src/base/models/QvConfigIdentifier.hpp b/src/base/models/QvConfigIdentifier.hpp index 4137c9d9..dde9622c 100644 --- a/src/base/models/QvConfigIdentifier.hpp +++ b/src/base/models/QvConfigIdentifier.hpp @@ -1,52 +1,180 @@ #pragma once -#include "3rdparty/x2struct/x2struct.hpp" +#include "QvCoreSettings.hpp" +#include "libs/QJsonStruct/QJsonStruct.hpp" +#include +#include #include #include + namespace Qv2ray::base { - constexpr unsigned int QVTCPING_VALUE_ERROR = 99999; - constexpr unsigned int QVTCPING_VALUE_NODATA = QVTCPING_VALUE_ERROR - 1; + template + class IDType + { + public: + explicit IDType() : m_id("null"){}; + explicit IDType(const QString &id) : m_id(id){}; + friend bool operator==(const IDType &lhs, const IDType &rhs) + { + return lhs.m_id == rhs.m_id; + } + friend bool operator!=(const IDType &lhs, const IDType &rhs) + { + return lhs.m_id != rhs.m_id; + } + const QString toString() const + { + return m_id; + } + void loadJson(const QJsonValue &d) + { + m_id = d.toString("null"); + } + QJsonValue toJson() const + { + return m_id; + } + + private: + QString m_id; + }; + + // Define several safetypes to prevent misuse of QString. + class __QvGroup; + class __QvConnection; + class __QvRoute; + typedef IDType<__QvGroup> GroupId; + typedef IDType<__QvConnection> ConnectionId; + typedef IDType<__QvRoute> GroupRoutingId; + // + inline const static auto NullConnectionId = ConnectionId("null"); + inline const static auto NullGroupId = GroupId("null"); + inline const static auto NullRoutingId = GroupRoutingId("null"); + // + class ConnectionGroupPair + { + public: + ConnectionId connectionId = NullConnectionId; + GroupId groupId = NullGroupId; + ConnectionGroupPair() : connectionId(NullConnectionId), groupId(NullGroupId){}; + ConnectionGroupPair(const ConnectionId &conn, const GroupId &group) : connectionId(conn), groupId(group){}; + void clear() + { + connectionId = NullConnectionId; + groupId = NullGroupId; + } + bool isEmpty() const + { + return groupId == NullGroupId || connectionId == NullConnectionId; + } + friend bool operator==(const ConnectionGroupPair &lhs, const ConnectionGroupPair &rhs) + { + return lhs.groupId == rhs.groupId && lhs.connectionId == rhs.connectionId; + } + JSONSTRUCT_REGISTER(ConnectionGroupPair, F(connectionId, groupId)) + }; + // + constexpr unsigned int LATENCY_TEST_VALUE_ERROR = 99999; + constexpr unsigned int LATENCY_TEST_VALUE_NODATA = LATENCY_TEST_VALUE_ERROR - 1; using namespace std::chrono; - // Common struct for Groups and Subscriptions - struct GroupObject_Config + + struct __Qv2rayConfigObjectBase { QString displayName; - QList connections; - int64_t importDate; - GroupObject_Config() : displayName(), connections(), importDate() - { - } - XTOSTRUCT(O(displayName, connections, importDate)) + qint64 creationDate; + qint64 lastUpdatedDate; + __Qv2rayConfigObjectBase() + : displayName(), creationDate(system_clock::to_time_t(system_clock::now())), // + lastUpdatedDate(system_clock::to_time_t(system_clock::now())){}; // + JSONSTRUCT_REGISTER(__Qv2rayConfigObjectBase, F(displayName, creationDate, lastUpdatedDate)) }; - struct SubscriptionObject_Config : GroupObject_Config + struct GroupRoutingConfig : __Qv2rayConfigObjectBase { + bool overrideDNS; + config::QvConfig_DNS dnsConfig; // - QString address; - int64_t lastUpdated; - float updateInterval; - SubscriptionObject_Config() : address(""), lastUpdated(system_clock::to_time_t(system_clock::now())), updateInterval(10) - { - } - XTOSTRUCT(O(lastUpdated, updateInterval, address, connections, displayName, importDate)) + bool overrideRoute; + config::QvConfig_Route routeConfig; + // + bool overrideConnectionConfig; + config::QvConfig_Connection connectionConfig; + // + bool overrideForwardProxyConfig; + config::QvConfig_ForwardProxy forwardProxyConfig; + // + GroupRoutingConfig() + : overrideDNS(false), // + overrideRoute(false), // + overrideConnectionConfig(false), // + overrideForwardProxyConfig(false) // + {}; + JSONSTRUCT_REGISTER(GroupRoutingConfig, // + F(overrideRoute, routeConfig), // + F(overrideDNS, dnsConfig), // + F(overrideConnectionConfig, connectionConfig), // + F(overrideForwardProxyConfig, forwardProxyConfig)) }; - struct ConnectionObject_Config + enum SubscriptionFilterRelation { - QString displayName; - int64_t importDate; - int64_t lastConnected; - int64_t latency; - int64_t upLinkData; - int64_t downLinkData; - ConnectionObject_Config() - : displayName(), importDate(system_clock::to_time_t(system_clock::now())), lastConnected(), latency(QVTCPING_VALUE_NODATA), - upLinkData(0), downLinkData(0) - { - } - XTOSTRUCT(O(displayName, importDate, lastConnected, latency, upLinkData, downLinkData)) + RELATION_AND = 0, + RELATION_OR = 1 }; + + struct SubscriptionConfigObject + { + QString address; + float updateInterval; + SubscriptionFilterRelation IncludeRelation; + SubscriptionFilterRelation ExcludeRelation; + QList IncludeKeywords; + QList ExcludeKeywords; + SubscriptionConfigObject() + : address(""), updateInterval(10), // + IncludeRelation(RELATION_OR), ExcludeRelation(RELATION_AND), // + IncludeKeywords(), ExcludeKeywords(){}; + JSONSTRUCT_REGISTER(SubscriptionConfigObject, + F(updateInterval, address, IncludeRelation, ExcludeRelation, IncludeKeywords, ExcludeKeywords)) + }; + + struct GroupObject : __Qv2rayConfigObjectBase + { + QList connections; + bool isSubscription; + GroupRoutingId routeConfigId; + SubscriptionConfigObject subscriptionOption; + GroupObject() : __Qv2rayConfigObjectBase(), connections(), isSubscription(false), subscriptionOption(){}; + JSONSTRUCT_REGISTER(GroupObject, F(connections, isSubscription, routeConfigId, subscriptionOption), B(__Qv2rayConfigObjectBase)) + }; + + struct ConnectionObject : __Qv2rayConfigObjectBase + { + qint64 lastConnected; + qint64 latency; + qint64 upLinkData; + qint64 downLinkData; + // + int __qvConnectionRefCount; + // + ConnectionObject() : lastConnected(), latency(LATENCY_TEST_VALUE_NODATA), upLinkData(0), downLinkData(0), __qvConnectionRefCount(0){}; + JSONSTRUCT_REGISTER(ConnectionObject, F(lastConnected, latency, upLinkData, downLinkData), B(__Qv2rayConfigObjectBase)) + }; + + template + inline uint qHash(IDType key) + { + return ::qHash(key.toString()); + } + inline uint qHash(const Qv2ray::base::ConnectionGroupPair &pair) + { + return ::qHash(pair.connectionId.toString() + pair.groupId.toString()); + } } // namespace Qv2ray::base using namespace Qv2ray::base; +Q_DECLARE_METATYPE(ConnectionGroupPair) +Q_DECLARE_METATYPE(ConnectionId) +Q_DECLARE_METATYPE(GroupId) +Q_DECLARE_METATYPE(GroupRoutingId) diff --git a/src/base/models/QvCoreSettings.hpp b/src/base/models/QvCoreSettings.hpp new file mode 100644 index 00000000..86e3e64c --- /dev/null +++ b/src/base/models/QvCoreSettings.hpp @@ -0,0 +1,142 @@ +#pragma once +#include "base/models/CoreObjectModels.hpp" +#include "libs/QJsonStruct/QJsonStruct.hpp" +namespace Qv2ray::base::config +{ + struct QvConfig_Route + { + struct QvRouteConfig_Impl + { + QList direct; + QList block; + QList proxy; + QvRouteConfig_Impl(){}; + friend bool operator==(const QvRouteConfig_Impl &left, const QvRouteConfig_Impl &right) + { + return left.direct == right.direct && left.block == right.block && left.proxy == right.proxy; + } + QvRouteConfig_Impl(const QList &_direct, const QList &_block, const QList &_proxy) + : direct(_direct), // + block(_block), // + proxy(_proxy){}; + JSONSTRUCT_REGISTER(QvRouteConfig_Impl, F(proxy, block, direct)) + }; + QString domainStrategy; + QvRouteConfig_Impl domains; + QvRouteConfig_Impl ips; + friend bool operator==(const QvConfig_Route &left, const QvConfig_Route &right) + { + return left.domainStrategy == right.domainStrategy && left.domains == right.domains && left.ips == right.ips; + } + QvConfig_Route(){}; + QvConfig_Route(const QvRouteConfig_Impl &_domains, const QvRouteConfig_Impl &_ips, const QString &ds) + : domainStrategy(ds), // + domains(_domains), // + ips(_ips){}; + JSONSTRUCT_REGISTER(QvConfig_Route, F(domainStrategy, domains, ips)) + }; + + using QvConfig_DNS = objects::DNSObject; + + struct QvConfig_Outbounds + { + int mark; + QvConfig_Outbounds() : mark(255){}; + JSONSTRUCT_REGISTER(QvConfig_Outbounds, F(mark)) + }; + + struct QvConfig_ForwardProxy + { + bool enableForwardProxy; + QString type; + QString serverAddress; + int port; + bool useAuth; + QString username; + QString password; + QvConfig_ForwardProxy() + : enableForwardProxy(false), type("http"), serverAddress("127.0.0.1"), port(8008), useAuth(false), username(), password(){}; + JSONSTRUCT_REGISTER(QvConfig_ForwardProxy, F(enableForwardProxy, type, serverAddress, port, useAuth, username, password)) + }; + + struct QvConfig_Connection + { + bool enableProxy; + bool bypassCN; + bool bypassBT; + bool v2rayFreedomDNS; + bool withLocalDNS; + QvConfig_Connection() : enableProxy(true), bypassCN(true), bypassBT(false), v2rayFreedomDNS(false), withLocalDNS(false){}; + JSONSTRUCT_REGISTER(QvConfig_Connection, F(bypassCN, bypassBT, enableProxy, v2rayFreedomDNS, withLocalDNS)) + }; + + struct QvConfig_SystemProxy + { + bool setSystemProxy; + QvConfig_SystemProxy() : setSystemProxy(true){}; + JSONSTRUCT_REGISTER(QvConfig_SystemProxy, F(setSystemProxy)) + }; + + struct __Qv2rayConfig_ProtocolInboundBase + { + int port; + bool useAuth; + bool sniffing; + objects::AccountObject account; + __Qv2rayConfig_ProtocolInboundBase(int _port = 0) : port(_port), useAuth(false), sniffing(false), account(){}; + JSONSTRUCT_REGISTER(__Qv2rayConfig_ProtocolInboundBase, F(port, useAuth, sniffing, account)) + }; + + struct QvConfig_SocksInbound : __Qv2rayConfig_ProtocolInboundBase + { + bool enableUDP; + QString localIP; + QvConfig_SocksInbound() : __Qv2rayConfig_ProtocolInboundBase(1089), enableUDP(true), localIP("127.0.0.1"){}; + JSONSTRUCT_REGISTER(QvConfig_SocksInbound, B(__Qv2rayConfig_ProtocolInboundBase), F(enableUDP, localIP)) + }; + + struct QvConfig_HttpInbound : __Qv2rayConfig_ProtocolInboundBase + { + QvConfig_HttpInbound() : __Qv2rayConfig_ProtocolInboundBase(8889){}; + JSONSTRUCT_REGISTER(QvConfig_HttpInbound, B(__Qv2rayConfig_ProtocolInboundBase)) + }; + + struct QvConfig_TProxy + { + QString tProxyIP; + QString tProxyV6IP; + int port; + bool hasTCP; + bool hasUDP; + QString mode; + bool dnsIntercept; + QvConfig_TProxy() + : tProxyIP("127.0.0.1"), // + tProxyV6IP(""), // + port(12345), // + hasTCP(true), // + hasUDP(false), // + mode("tproxy"), // + dnsIntercept(true) // + {}; + JSONSTRUCT_REGISTER(QvConfig_TProxy, F(tProxyIP, tProxyV6IP, port, hasTCP, hasUDP, mode, dnsIntercept)) + }; + + struct QvConfig_Inbounds + { + QString listenip; + bool useSocks; + bool useHTTP; + bool useTPROXY; + // + QvConfig_TProxy tProxySettings; + QvConfig_HttpInbound httpSettings; + QvConfig_SocksInbound socksSettings; + QvConfig_SystemProxy systemProxySettings; + QvConfig_Inbounds() : listenip("127.0.0.1"), useSocks(true), useHTTP(true), useTPROXY(false){}; + + JSONSTRUCT_REGISTER(QvConfig_Inbounds, // + F(listenip, useSocks, useHTTP, useTPROXY), // + F(tProxySettings, httpSettings, socksSettings, systemProxySettings)) + }; +} // namespace Qv2ray::base::config diff --git a/src/base/models/QvRuntimeConfig.hpp b/src/base/models/QvRuntimeConfig.hpp index d1dd033c..f572f88b 100644 --- a/src/base/models/QvRuntimeConfig.hpp +++ b/src/base/models/QvRuntimeConfig.hpp @@ -8,6 +8,7 @@ namespace Qv2ray::base struct Qv2rayRuntimeConfig { bool screenShotHideQv2ray = false; + bool deepinHorribleProxyHint = false; }; inline base::Qv2rayRuntimeConfig RuntimeConfig = base::Qv2rayRuntimeConfig(); } // namespace Qv2ray::base diff --git a/src/base/models/QvSafeType.hpp b/src/base/models/QvSafeType.hpp index adf17f6d..4298e503 100644 --- a/src/base/models/QvSafeType.hpp +++ b/src/base/models/QvSafeType.hpp @@ -21,7 +21,6 @@ #define nothing #define SAFE_TYPEDEF(Base, name) SAFE_TYPEDEF_EXTRA(Base, name, nothing) -using namespace std; namespace Qv2ray::base::safetype { // To prevent anonying QJsonObject misuse diff --git a/src/base/models/QvSettingsObject.hpp b/src/base/models/QvSettingsObject.hpp index 03dc86a5..4ebbe421 100644 --- a/src/base/models/QvSettingsObject.hpp +++ b/src/base/models/QvSettingsObject.hpp @@ -1,210 +1,54 @@ #pragma once -#include "3rdparty/x2struct/x2struct.hpp" -#include "base/models/CoreObjectModels.hpp" #include "base/models/QvConfigIdentifier.hpp" +#include "base/models/QvCoreSettings.hpp" #include -const int QV2RAY_CONFIG_VERSION = 11; +constexpr int QV2RAY_CONFIG_VERSION = 14; namespace Qv2ray::base::config { - struct QvBarLine - { - QString Family; - bool Bold, Italic; - int ColorA, ColorR, ColorG, ColorB; - int ContentType; - double Size; - QString Message; - QvBarLine() - : Family("Consolas"), Bold(true), Italic(false), ColorA(255), ColorR(255), ColorG(255), ColorB(255), ContentType(0), Size(9), - Message("") - { - } - XTOSTRUCT(O(Bold, Italic, ColorA, ColorR, ColorG, ColorB, Size, Family, Message, ContentType)) - }; - - struct QvBarPage - { - int OffsetYpx; - QList Lines; - QvBarPage() : OffsetYpx(5) - { - } - XTOSTRUCT(O(OffsetYpx, Lines)) - }; - - struct Qv2rayToolBarConfig - { - QList Pages; - XTOSTRUCT(O(Pages)) - }; - - struct Qv2rayForwardProxyConfig - { - bool enableForwardProxy; - QString type; - QString serverAddress; - int port; - bool useAuth; - QString username; - QString password; - Qv2rayForwardProxyConfig() - : enableForwardProxy(false), type("http"), serverAddress("127.0.0.1"), port(8008), useAuth(false), username(), password() - { - } - XTOSTRUCT(O(enableForwardProxy, type, serverAddress, port, useAuth, username, password)) - }; - - struct Qv2rayInboundsConfig - { - QString listenip; - bool setSystemProxy; - - // SOCKS - bool useSocks; - int socks_port; - bool socks_useAuth; - bool socksUDP; - QString socksLocalIP; - objects::AccountObject socksAccount; - // HTTP - bool useHTTP; - int http_port; - bool http_useAuth; - objects::AccountObject httpAccount; - - // dokodemo-door transparent proxy - bool useTPROXY; - QString tproxy_ip; - int tproxy_port; - bool tproxy_use_tcp; - bool tproxy_use_udp; - bool tproxy_followRedirect; - /*redirect or tproxy way, and tproxy need cap_net_admin*/ - QString tproxy_mode; - bool dnsIntercept; - - Qv2rayInboundsConfig() - : listenip("127.0.0.1"), setSystemProxy(true), useSocks(true), socks_port(1088), socks_useAuth(false), socksUDP(true), - socksLocalIP("127.0.0.1"), socksAccount(), useHTTP(true), http_port(8888), http_useAuth(false), httpAccount(), useTPROXY(false), - tproxy_ip("127.0.0.1"), tproxy_port(12345), tproxy_use_tcp(true), tproxy_use_udp(false), tproxy_followRedirect(true), - tproxy_mode("tproxy"), dnsIntercept(true) - { - } - - XTOSTRUCT(O(setSystemProxy, listenip, useSocks, useHTTP, socks_port, socks_useAuth, socksAccount, socksUDP, socksLocalIP, http_port, - http_useAuth, httpAccount, useTPROXY, tproxy_ip, tproxy_port, tproxy_use_tcp, tproxy_use_udp, tproxy_followRedirect, - tproxy_mode, dnsIntercept)) - }; - - struct Qv2rayOutboundsConfig - { - int mark; - Qv2rayOutboundsConfig() : mark(255) - { - } - XTOSTRUCT(O(mark)) - }; - - struct Qv2rayUIConfig + struct Qv2rayConfig_UI { QString theme; QString language; - QList recentConnections; + QList recentConnections; bool quietMode; bool useDarkTheme; bool useDarkTrayIcon; int maximumLogLines; int maxJumpListCount; - Qv2rayUIConfig() - : theme("Fusion"), language("en_US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500), maxJumpListCount(20) - { - } - XTOSTRUCT(O(theme, language, quietMode, useDarkTheme, useDarkTrayIcon, maximumLogLines, maxJumpListCount, recentConnections)) + Qv2rayConfig_UI() + : theme("Fusion"), language("en_US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500), maxJumpListCount(20){}; + JSONSTRUCT_REGISTER(Qv2rayConfig_UI, + F(theme, language, quietMode, useDarkTheme, useDarkTrayIcon, maximumLogLines, maxJumpListCount, recentConnections)) }; - - struct Qv2rayRouteConfig_Impl - { - QList direct; - QList block; - QList proxy; - Qv2rayRouteConfig_Impl(){}; - friend bool operator==(const Qv2rayRouteConfig_Impl &left, const Qv2rayRouteConfig_Impl &right) - { - return left.direct == right.direct && left.block == right.block && left.proxy == left.proxy; - } - Qv2rayRouteConfig_Impl(const QList &_direct, const QList &_block, const QList &_proxy) - : direct(_direct), block(_block), proxy(_proxy){}; - XTOSTRUCT(O(proxy, block, direct)) - }; - - struct Qv2rayRouteConfig - { - QString domainStrategy; - Qv2rayRouteConfig_Impl domains; - Qv2rayRouteConfig_Impl ips; - friend bool operator==(const Qv2rayRouteConfig &left, const Qv2rayRouteConfig &right) - { - return left.domainStrategy == right.domainStrategy && left.domains == right.domains && left.ips == right.ips; - } - Qv2rayRouteConfig(){}; - Qv2rayRouteConfig(const Qv2rayRouteConfig_Impl &_domains, const Qv2rayRouteConfig_Impl &_ips, const QString &ds) - : domainStrategy(ds), domains(_domains), ips(_ips){}; - XTOSTRUCT(O(domainStrategy, domains, ips)) - }; - - struct Qv2rayPluginConfig + struct Qv2rayConfig_Plugin { QMap pluginStates; bool v2rayIntegration; int portAllocationStart; - Qv2rayPluginConfig() : pluginStates(), v2rayIntegration(true), portAllocationStart(15000){}; - XTOSTRUCT(O(pluginStates, v2rayIntegration)) + Qv2rayConfig_Plugin() : pluginStates(), v2rayIntegration(true), portAllocationStart(15000){}; + JSONSTRUCT_REGISTER(Qv2rayConfig_Plugin, F(pluginStates, v2rayIntegration, portAllocationStart)) }; - struct Qv2rayConnectionConfig - { - bool bypassCN; - bool enableProxy; - bool v2rayFreedomDNS; - bool withLocalDNS; - Qv2rayRouteConfig routeConfig; - QList dnsList; - Qv2rayForwardProxyConfig forwardProxyConfig; - Qv2rayConnectionConfig() - : bypassCN(true), enableProxy(true), v2rayFreedomDNS(false), withLocalDNS(false), routeConfig(), - dnsList(QStringList{ "8.8.4.4", "1.1.1.1" }) - { - } - XTOSTRUCT(O(bypassCN, enableProxy, v2rayFreedomDNS, withLocalDNS, dnsList, forwardProxyConfig, routeConfig)) - }; - - struct Qv2rayAPIConfig + struct Qv2rayConfig_Kernel { bool enableAPI; int statsPort; - Qv2rayAPIConfig() : enableAPI(true), statsPort(15490) - { - } - XTOSTRUCT(O(enableAPI, statsPort)) - }; - - struct Qv2rayKernelConfig - { + // QString v2CorePath_linux; QString v2AssetsPath_linux; QString v2CorePath_macx; QString v2AssetsPath_macx; QString v2CorePath_win; - QString v2AssetsPath_win; // - Qv2rayKernelConfig() - : v2CorePath_linux(), v2AssetsPath_linux(), // + QString v2AssetsPath_win; + Qv2rayConfig_Kernel() + : enableAPI(true), statsPort(15490), // + v2CorePath_linux(), v2AssetsPath_linux(), // v2CorePath_macx(), v2AssetsPath_macx(), // v2CorePath_win(), v2AssetsPath_win() // - { - } + {}; // #ifdef Q_OS_LINUX #define _VARNAME_VCOREPATH_ v2CorePath_linux @@ -229,10 +73,14 @@ namespace Qv2ray::base::config #undef _VARNAME_VCOREPATH_ #undef _VARNAME_VASSETSPATH_ - XTOSTRUCT(O(v2CorePath_linux, v2AssetsPath_linux, v2CorePath_macx, v2AssetsPath_macx, v2CorePath_win, v2AssetsPath_win)) + JSONSTRUCT_REGISTER(Qv2rayConfig_Kernel, // + F(enableAPI, statsPort), // + F(v2CorePath_linux, v2AssetsPath_linux), // + F(v2CorePath_macx, v2AssetsPath_macx), // + F(v2CorePath_win, v2AssetsPath_win)) }; - struct Qv2rayUpdateConfig + struct Qv2rayConfig_Update { QString ignoredVersion; /// @@ -240,105 +88,96 @@ namespace Qv2ray::base::config /// 0: Stable /// 1: Testing int updateChannel; - XTOSTRUCT(O(ignoredVersion, updateChannel)) + JSONSTRUCT_REGISTER(Qv2rayConfig_Update, F(ignoredVersion, updateChannel)) }; - struct Qv2rayAdvancedConfig + struct Qv2rayConfig_Advanced { bool setAllowInsecure; - bool setAllowInsecureCiphers; + bool setSessionResumption; bool testLatencyPeriodcally; - XTOSTRUCT(O(setAllowInsecure, setAllowInsecureCiphers, testLatencyPeriodcally)) + JSONSTRUCT_REGISTER(Qv2rayConfig_Advanced, F(setAllowInsecure, setSessionResumption, testLatencyPeriodcally)) }; - struct Qv2rayNetworkConfig + enum Qv2rayLatencyTestingMethod { - enum Qv2rayProxyType + TCPING, + ICMPING + }; + + struct Qv2rayConfig_Network + { + Qv2rayLatencyTestingMethod latencyTestingMethod; + enum Qv2rayProxyType : int { - QVPROXY_NONE, - QVPROXY_SYSTEM, - QVPROXY_CUSTOM + QVPROXY_NONE = 0, + QVPROXY_SYSTEM = 1, + QVPROXY_CUSTOM = 2 } proxyType; QString address; QString type; int port; QString userAgent; - Qv2rayNetworkConfig() + Qv2rayConfig_Network() : proxyType(QVPROXY_NONE), // address("127.0.0.1"), // type("http"), // port(8000), // userAgent("Qv2ray/$VERSION WebRequestHelper"){}; - XTOSTRUCT(O(proxyType, type, address, port, userAgent)) + JSONSTRUCT_REGISTER(Qv2rayConfig_Network, F(latencyTestingMethod, proxyType, type, address, port, userAgent)) }; - struct Qv2rayConfig + enum Qv2rayAutoConnectionBehavior + { + AUTO_CONNECTION_NONE = 0, + AUTO_CONNECTION_FIXED = 1, + AUTO_CONNECTION_LAST_CONNECTED = 2 + }; + + struct Qv2rayConfigObject { int config_version; bool tProxySupport; int logLevel; // - QString autoStartId; + ConnectionGroupPair autoStartId; + ConnectionGroupPair lastConnectedId; + Qv2rayAutoConnectionBehavior autoStartBehavior; // // Key = groupId, connectionId - QMap groups; - QMap subscriptions; - /// Connections are used privately. - QMap connections; + // QList groups; + // QList connections; // - Qv2rayUIConfig uiConfig; - Qv2rayAPIConfig apiConfig; - Qv2rayPluginConfig pluginConfig; - Qv2rayKernelConfig kernelConfig; - Qv2rayUpdateConfig updateConfig; - Qv2rayNetworkConfig networkConfig; - Qv2rayToolBarConfig toolBarConfig; - Qv2rayInboundsConfig inboundConfig; - Qv2rayOutboundsConfig outboundConfig; - Qv2rayAdvancedConfig advancedConfig; - Qv2rayConnectionConfig connectionConfig; + Qv2rayConfig_UI uiConfig; + Qv2rayConfig_Plugin pluginConfig; + Qv2rayConfig_Kernel kernelConfig; + Qv2rayConfig_Update updateConfig; + Qv2rayConfig_Network networkConfig; + QvConfig_Inbounds inboundConfig; + QvConfig_Outbounds outboundConfig; + Qv2rayConfig_Advanced advancedConfig; + GroupRoutingConfig defaultRouteConfig; - Qv2rayConfig() + Qv2rayConfigObject() : config_version(QV2RAY_CONFIG_VERSION), // tProxySupport(false), // logLevel(), // - autoStartId("null"), // - groups(), // - subscriptions(), // - connections(), // + autoStartId(), // + autoStartBehavior(), // uiConfig(), // - apiConfig(), // pluginConfig(), // kernelConfig(), // updateConfig(), // networkConfig(), // - toolBarConfig(), // inboundConfig(), // outboundConfig(), // advancedConfig(), // - connectionConfig() - { - } + defaultRouteConfig(){}; - XTOSTRUCT(O(config_version, // - tProxySupport, // - logLevel, // - uiConfig, // - pluginConfig, // - updateConfig, // - kernelConfig, // - networkConfig, // - groups, // - connections, // - subscriptions, // - autoStartId, // - inboundConfig, // - outboundConfig, // - connectionConfig, // - toolBarConfig, // - advancedConfig, // - apiConfig // - )) + JSONSTRUCT_REGISTER(Qv2rayConfigObject, // + F(config_version, tProxySupport, autoStartId, lastConnectedId, autoStartBehavior, logLevel), // + F(uiConfig, advancedConfig, pluginConfig, updateConfig, kernelConfig, networkConfig), // + F(inboundConfig, outboundConfig, defaultRouteConfig)) }; } // namespace Qv2ray::base::config diff --git a/src/base/models/QvStartupConfig.hpp b/src/base/models/QvStartupConfig.hpp index 8f88f79f..6c0e4839 100644 --- a/src/base/models/QvStartupConfig.hpp +++ b/src/base/models/QvStartupConfig.hpp @@ -1,24 +1,26 @@ #pragma once -namespace Qv2ray +namespace Qv2ray::base { - namespace base + struct QvStartupOptions { - struct QvStartupOptions - { - /// No API subsystem - bool noAPI; - /// Explicitly run as root user. - bool forceRunAsRootUser; - /// Enable Debug Log. - bool debugLog; - /// Enable Network toolbar plugin. - bool enableToolbarPlguin; - /// Disable Qt scale factors support. - bool noScaleFactors; - /// Disable all plugin features. - bool noPlugins; - }; - } // namespace base - inline base::QvStartupOptions StartupOption = base::QvStartupOptions(); -} // namespace Qv2ray + /// No API subsystem + bool noAPI; + + /// Enable Debug Log. + bool debugLog; + + /// Disable Qt scale factors support. + bool noScaleFactor; + + /// Disable all plugin features. + bool noPlugins; + + /// Exit existing Qv2ray instance + bool exitQv2ray; + + /// + }; +} // namespace Qv2ray::base + +inline Qv2ray::base::QvStartupOptions StartupOption = Qv2ray::base::QvStartupOptions(); diff --git a/src/common/CommandArgs.cpp b/src/common/CommandArgs.cpp deleted file mode 100644 index 51f24736..00000000 --- a/src/common/CommandArgs.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "CommandArgs.hpp" - -#include "base/Qv2rayBase.hpp" - -namespace Qv2ray::common -{ - QvCommandArgParser::QvCommandArgParser() - : QObject(), noAPIOption("noAPI", tr("Disable gRPC API subsystems.")), // - runAsRootOption("I-just-wanna-run-with-root", tr("Explicitly run Qv2ray as root.")), // - debugOption("debug", tr("Enable Debug Output")), // - noScaleFactorOption("noScaleFactor", tr("Disable manually set QT_SCALE_FACTOR")), // - noPluginsOption("noPlugin", tr("Disable plugin feature")), // - withToolbarOption("withToolbarPlugin", tr("Enable Qv2ray network toolbar plugin")), // - // - helpOption("FAKE"), versionOption("FAKE") - { - parser.setApplicationDescription(QObject::tr("Qv2ray - A cross-platform Qt frontend for V2ray.")); - parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); - // - parser.addOption(noAPIOption); - parser.addOption(runAsRootOption); - parser.addOption(debugOption); - parser.addOption(noScaleFactorOption); - parser.addOption(noPluginsOption); - parser.addOption(withToolbarOption); - helpOption = parser.addHelpOption(); - versionOption = parser.addVersionOption(); - } - - CommandLineParseResult QvCommandArgParser::ParseCommandLine(QString *errorMessage) - { - if (!parser.parse(QCoreApplication::arguments())) - { - *errorMessage = parser.errorText(); - return CommandLineError; - } - - if (parser.isSet(versionOption)) - return CommandLineVersionRequested; - - if (parser.isSet(helpOption)) - return CommandLineHelpRequested; - - if (parser.isSet(noAPIOption)) - { - DEBUG(MODULE_INIT, "noAPIOption is set.") - StartupOption.noAPI = true; - } - - if (parser.isSet(runAsRootOption)) - { - DEBUG(MODULE_INIT, "runAsRootOption is set.") - StartupOption.forceRunAsRootUser = true; - } - - if (parser.isSet(debugOption)) - { - DEBUG(MODULE_INIT, "debugOption is set.") - StartupOption.debugLog = true; - } - - if (parser.isSet(noScaleFactorOption)) - { - DEBUG(MODULE_INIT, "noScaleFactorOption is set.") - StartupOption.noScaleFactors = true; - } - - if (parser.isSet(noPluginsOption)) - { - DEBUG(MODULE_INIT, "noPluginOption is set.") - StartupOption.noPlugins = true; - } - - if (parser.isSet(withToolbarOption)) - { - DEBUG(MODULE_INIT, "withToolbarOption is set.") - StartupOption.enableToolbarPlguin = true; - } - - return CommandLineOk; - } - -} // namespace Qv2ray::common diff --git a/src/common/CommandArgs.hpp b/src/common/CommandArgs.hpp deleted file mode 100644 index dbab70cc..00000000 --- a/src/common/CommandArgs.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include - -namespace Qv2ray::common -{ - enum CommandLineParseResult - { - CommandLineOk, - CommandLineError, - CommandLineVersionRequested, - CommandLineHelpRequested - }; - class QvCommandArgParser : public QObject - { - Q_OBJECT - public: - QvCommandArgParser(); - CommandLineParseResult ParseCommandLine(QString *errorMessage); - const QCommandLineParser *Parser() - { - return &parser; - } - - private: - QCommandLineParser parser; - QCommandLineOption noAPIOption; - QCommandLineOption runAsRootOption; - QCommandLineOption debugOption; - QCommandLineOption noScaleFactorOption; - QCommandLineOption noPluginsOption; - QCommandLineOption withToolbarOption; - QCommandLineOption helpOption; - QCommandLineOption versionOption; - }; -} // namespace Qv2ray::common - -using namespace Qv2ray::common; diff --git a/src/common/HTTPRequestHelper.cpp b/src/common/HTTPRequestHelper.cpp index bb37f48a..42f103c2 100644 --- a/src/common/HTTPRequestHelper.cpp +++ b/src/common/HTTPRequestHelper.cpp @@ -5,39 +5,30 @@ #include #include -namespace Qv2ray::common +namespace Qv2ray::common::network { - QvHttpRequestHelper::QvHttpRequestHelper(QObject *parent) : QObject(parent), reply() - { - } - - QvHttpRequestHelper::~QvHttpRequestHelper() - { - accessManager.disconnect(); - } - - void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value) + void NetworkRequestHelper::setHeader(QNetworkRequest &request, const QByteArray &key, const QByteArray &value) { DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value) request.setRawHeader(key, value); } - void QvHttpRequestHelper::setAccessManagerAttributes(QNetworkAccessManager &accessManager) + void NetworkRequestHelper::setAccessManagerAttributes(QNetworkRequest &request, QNetworkAccessManager &accessManager) { switch (GlobalConfig.networkConfig.proxyType) { - case Qv2rayNetworkConfig::QVPROXY_NONE: + case Qv2rayConfig_Network::QVPROXY_NONE: { DEBUG(MODULE_NETWORK, "Get without proxy.") accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy)); break; } - case Qv2rayNetworkConfig::QVPROXY_SYSTEM: + case Qv2rayConfig_Network::QVPROXY_SYSTEM: { accessManager.setProxy(QNetworkProxyFactory::systemProxyForQuery().first()); break; } - case Qv2rayNetworkConfig::QVPROXY_CUSTOM: + case Qv2rayConfig_Network::QVPROXY_CUSTOM: { QNetworkProxy p{ GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, // @@ -47,6 +38,7 @@ namespace Qv2ray::common accessManager.setProxy(p); break; } + default: Q_UNREACHABLE(); } if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) @@ -56,59 +48,60 @@ namespace Qv2ray::common } request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); - request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + // request.setAttribute(QNetworkRequest::Http2AllowedAttribute, true); +#else + // request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); +#endif + auto ua = GlobalConfig.networkConfig.userAgent; ua.replace("$VERSION", QV2RAY_VERSION_STRING); request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, ua); } - QByteArray QvHttpRequestHelper::Get(const QString &url) + QByteArray NetworkRequestHelper::HttpGet(const QUrl &url) { - request.setUrl({ url }); - setAccessManagerAttributes(accessManager); + QNetworkRequest request; + QNetworkAccessManager accessManager; + request.setUrl(url); + setAccessManagerAttributes(request, accessManager); auto _reply = accessManager.get(request); // - QEventLoop loop; - connect(_reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); - loop.exec(); + { + QEventLoop loop; + QObject::connect(&accessManager, &QNetworkAccessManager::finished, &loop, &QEventLoop::quit); + loop.exec(); + } // // Data or timeout? + LOG(MODULE_NETWORK, _reply->errorString()); auto data = _reply->readAll(); return data; } - void QvHttpRequestHelper::AsyncGet(const QString &url) + void NetworkRequestHelper::AsyncHttpGet(const QString &url, std::function funcPtr) { - request.setUrl({ url }); - setAccessManagerAttributes(accessManager); - reply = accessManager.get(request); - connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p); - connect(reply, &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead_p); + QNetworkRequest request; + request.setUrl(url); + setAccessManagerAttributes(request, accessManager); + auto reply = accessManager.get(request); + QObject::connect(reply, &QNetworkReply::finished, [=]() { + { +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + bool h2Used = reply->attribute(QNetworkRequest::Http2WasUsedAttribute).toBool(); +#else + bool h2Used = reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool(); +#endif + if (h2Used) + DEBUG(MODULE_NETWORK, "HTTP/2 was used.") + + if (reply->error() != QNetworkReply::NoError) + LOG(MODULE_NETWORK, "Network error: " + QString(QMetaEnum::fromType().key(reply->error()))) + + funcPtr(reply->readAll()); + } + }); } - void QvHttpRequestHelper::onRequestFinished_p() - { - if (reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool()) - { - DEBUG(MODULE_NETWORK, "HTTP/2 was used.") - } - - if (reply->error() != QNetworkReply::NoError) - { - QString error = QMetaEnum::fromType().key(reply->error()); - LOG(MODULE_NETWORK, "Network request error string: " + error) - QByteArray empty; - emit OnRequestFinished(empty); - } - else - { - emit OnRequestFinished(this->data); - } - } - - void QvHttpRequestHelper::onReadyRead_p() - { - DEBUG(MODULE_NETWORK, "A request is now ready read") - this->data += reply->readAll(); - } -} // namespace Qv2ray::common +} // namespace Qv2ray::common::network diff --git a/src/common/HTTPRequestHelper.hpp b/src/common/HTTPRequestHelper.hpp index ff2d675f..f9d2f2a4 100644 --- a/src/common/HTTPRequestHelper.hpp +++ b/src/common/HTTPRequestHelper.hpp @@ -1,55 +1,27 @@ -/* - Copyright (C) 2019 SoneWinstone (jianwenzhen@qq.com) - Copyright (C) 2019 Leroy.H.Y - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -*/ - #pragma once #include #include #include #include +#include -namespace Qv2ray::common +namespace Qv2ray::common::network { - class QvHttpRequestHelper : public QObject + class NetworkRequestHelper : QObject { Q_OBJECT public: - explicit QvHttpRequestHelper(QObject *parent = nullptr); - ~QvHttpRequestHelper(); - // get - void AsyncGet(const QString &url); - QByteArray Get(const QString &url); - signals: - void OnRequestFinished(QByteArray &data); - - private slots: - void onRequestFinished_p(); - void onReadyRead_p(); + explicit NetworkRequestHelper(QObject *parent) : QObject(parent){}; + ~NetworkRequestHelper(){}; + void AsyncHttpGet(const QString &url, std::function funcPtr); + static QByteArray HttpGet(const QUrl &url); private: - void setAccessManagerAttributes(QNetworkAccessManager &accessManager); - void setHeader(const QByteArray &key, const QByteArray &value); - QByteArray data; - QUrl url; - QNetworkReply *reply; - QNetworkRequest request; + static void setAccessManagerAttributes(QNetworkRequest &request, QNetworkAccessManager &accessManager); + static void setHeader(QNetworkRequest &request, const QByteArray &key, const QByteArray &value); QNetworkAccessManager accessManager; }; -} // namespace Qv2ray::common +} // namespace Qv2ray::common::network -using namespace Qv2ray::common; +using namespace Qv2ray::common::network; diff --git a/src/common/QJsonModel.cpp b/src/common/QJsonModel.cpp index 33bbcd75..c633b8cd 100644 --- a/src/common/QJsonModel.cpp +++ b/src/common/QJsonModel.cpp @@ -26,7 +26,6 @@ #include #include -#include QJsonTreeItem::QJsonTreeItem(QJsonTreeItem *parent) { diff --git a/src/common/QJsonModel.hpp b/src/common/QJsonModel.hpp index 69bddec8..3b07e7cf 100644 --- a/src/common/QJsonModel.hpp +++ b/src/common/QJsonModel.hpp @@ -25,12 +25,12 @@ #pragma once #include -#include +#include +#include #include #include #include #include - class QJsonModel; class QJsonItem; diff --git a/src/common/QvHelpers.cpp b/src/common/QvHelpers.cpp index b4a7dffb..58a3426f 100644 --- a/src/common/QvHelpers.cpp +++ b/src/common/QvHelpers.cpp @@ -1,18 +1,13 @@ #include "common/QvHelpers.hpp" +#include "base/Qv2rayBase.hpp" #include "libs/puresource/src/PureJson.hpp" -#include -#include -#include -#include -#include - namespace Qv2ray::common { const QString GenerateRandomString(int len) { - const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + const QString possibleCharacters("abcdefghijklmnopqrstuvwxyz"); QString randomString; for (int i = 0; i < len; ++i) @@ -41,39 +36,31 @@ namespace Qv2ray::common if (!wasOpened) source.close(); // - QTextCodec::ConverterState state; QTextCodec *codec = QTextCodec::codecForName("UTF-8"); + QTextCodec::ConverterState state; const QString text = codec->toUnicode(byteArray.constData(), byteArray.size(), &state); if (state.invalidChars > 0) { LOG(MODULE_FILEIO, "Not a valid UTF-8 sequence: " + source.fileName()) - return byteArray; - } - else - { - return text; } + return state.invalidChars > 0 ? byteArray : text; } bool StringToFile(const QString &text, const QString &targetpath) { - auto file = QFile(targetpath); - return StringToFile(text, file); - } - bool StringToFile(const QString &text, QFile &targetFile) - { - QFileInfo info(targetFile); - if (!info.dir().exists()) + bool override = false; { - info.dir().mkpath(info.dir().path()); + QFileInfo info(targetpath); + override = info.exists(); + if (!override && !info.dir().exists()) + info.dir().mkpath(info.dir().path()); } - bool override = targetFile.exists(); - targetFile.open(QFile::WriteOnly); - targetFile.write(text.toUtf8()); - targetFile.close(); + QSaveFile f{ targetpath }; + f.open(QIODevice::WriteOnly); + f.write(text.toUtf8()); + f.commit(); return override; } - QString JsonToString(const QJsonObject &json, QJsonDocument::JsonFormat format) { QJsonDocument doc; @@ -116,6 +103,31 @@ namespace Qv2ray::common return doc.object(); } + // backported from QvPlugin-SSR. + QString SafeBase64Decode(QString string) + { + QByteArray ba = string.replace(QChar('-'), QChar('+')).replace(QChar('_'), QChar('/')).toUtf8(); + return QByteArray::fromBase64(ba, QByteArray::Base64Option::OmitTrailingEquals); + } + + // backported from QvPlugin-SSR. + QString SafeBase64Encode(const QString &string, bool trim) + { + QString base64 = string.toUtf8().toBase64(); + if (trim) + { + auto tmp = base64.replace(QChar('+'), QChar('-')).replace(QChar('/'), QChar('_')); + auto crbedin = tmp.crbegin(); + auto idx = tmp.length(); + while (crbedin != tmp.crend() && (*crbedin) == '=') idx -= 1, crbedin++; + return idx != tmp.length() ? tmp.remove(idx, tmp.length() - idx) : tmp; + } + else + { + return base64.replace(QChar('+'), QChar('-')).replace(QChar('/'), QChar('_')); + } + } + QString Base64Encode(const QString &string) { QByteArray ba = string.toUtf8(); @@ -130,19 +142,11 @@ namespace Qv2ray::common QStringList SplitLines(const QString &_string) { +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + return _string.split(QRegExp("[\r\n]"), Qt::SkipEmptyParts); +#else return _string.split(QRegExp("[\r\n]"), QString::SkipEmptyParts); - } - - list SplitLines_std(const QString &_string) - { - list list; - - for (auto line : _string.split(QRegExp("[\r\n]"), QString::SkipEmptyParts)) - { - list.push_back(line.toStdString()); - } - - return list; +#endif } QStringList GetFileList(const QDir &dir) @@ -155,22 +159,6 @@ namespace Qv2ray::common return GetFileList(dir).contains(fileName); } - void QvMessageBoxWarn(QWidget *parent, const QString &title, const QString &text) - { - QMessageBox::warning(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); - } - - void QvMessageBoxInfo(QWidget *parent, const QString &title, const QString &text) - { - QMessageBox::information(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); - } - - QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, const QString &title, const QString &text, - QMessageBox::StandardButton extraButtons) - { - return QMessageBox::question(parent, title, text, QMessageBox::Yes | QMessageBox::No | extraButtons); - } - QString FormatBytes(const int64_t b) { auto _bytes = b; @@ -195,7 +183,7 @@ namespace Qv2ray::common { std::string _name = fileName.toStdString(); std::replace_if( - _name.begin(), _name.end(), [](char c) { return std::string::npos != string(R"("/\?%&^*;:|><)").find(c); }, '_'); + _name.begin(), _name.end(), [](char c) { return std::string::npos != std::string(R"("/\?%&^*;:|><)").find(c); }, '_'); return QString::fromStdString(_name); } @@ -225,38 +213,20 @@ namespace Qv2ray::common i++; } } - QPixmap ApplyEffectToImage(QPixmap src, QGraphicsEffect *effect, int extent) + + void QvMessageBoxWarn(QWidget *parent, const QString &title, const QString &text) { - constexpr int extent2 = 0; - if (src.isNull()) - return QPixmap(); // No need to do anything else! - if (!effect) - return src; // No need to do anything else! - QGraphicsScene scene; - auto p = scene.addPixmap(src); - p->setGraphicsEffect(effect); - // - QImage res(src.size() + QSize(extent2, extent2), QImage::Format_ARGB32); - res.fill(Qt::transparent); - QPainter ptr(&res); - // - scene.render(&ptr, QRectF(), QRectF(-extent, -extent, src.width() + extent2, src.height() + extent * 2)); - // - scene.removeItem(p); - return QPixmap::fromImage(res); - } - QPixmap BlurImage(const QPixmap &pixmap, const double rad) - { - QGraphicsBlurEffect pBlur; - pBlur.setBlurRadius(rad); - return ApplyEffectToImage(pixmap, &pBlur, 0); + QMessageBox::warning(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); } - QPixmap ColorizeImage(const QPixmap &pixmap, const QColor &color, const qreal factor) + void QvMessageBoxInfo(QWidget *parent, const QString &title, const QString &text) { - QGraphicsColorizeEffect pColor; - pColor.setColor(color); - pColor.setStrength(factor); - return ApplyEffectToImage(pixmap, &pColor, 0); + QMessageBox::information(parent, title, text, QMessageBox::Ok | QMessageBox::Default, 0); } + QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, const QString &title, const QString &text, + QMessageBox::StandardButton extraButtons) + { + return QMessageBox::question(parent, title, text, QMessageBox::Yes | QMessageBox::No | extraButtons); + } + } // namespace Qv2ray::common diff --git a/src/common/QvHelpers.hpp b/src/common/QvHelpers.hpp index 14c61e30..616a4f57 100644 --- a/src/common/QvHelpers.hpp +++ b/src/common/QvHelpers.hpp @@ -1,8 +1,12 @@ #pragma once -#include "base/Qv2rayBase.hpp" - +#include +#include +#include +#include +#include #include +#include #define REGEX_IPV6_ADDR \ R"(\[\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*\])" @@ -13,21 +17,20 @@ namespace Qv2ray::common { QStringList GetFileList(const QDir &dir); + + QString SafeBase64Decode(QString string); + QString SafeBase64Encode(const QString &string, bool trim); + QString Base64Encode(const QString &string); QString Base64Decode(const QString &string); QStringList SplitLines(const QString &str); - list SplitLines_std(const QString &_string); + // list SplitLines_std(const QString &_string); bool FileExistsIn(const QDir &dir, const QString &fileName); const QString GenerateRandomString(int len = 12); // - void QvMessageBoxWarn(QWidget *parent, const QString &title, const QString &text); - void QvMessageBoxInfo(QWidget *parent, const QString &title, const QString &text); - QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, const QString &title, const QString &text, - QMessageBox::StandardButton extraButtons = QMessageBox::NoButton); - // QString StringFromFile(const QString &filePath); QString StringFromFile(QFile &source); - bool StringToFile(const QString &text, QFile &target); + // bool StringToFile(const QString &text, QFile &target); bool StringToFile(const QString &text, const QString &targetpath); // QJsonObject JsonFromString(const QString &string); @@ -38,9 +41,6 @@ namespace Qv2ray::common QString FormatBytes(const int64_t bytes); void DeducePossibleFileName(const QString &baseDir, QString *fileName, const QString &extension); // - QPixmap ApplyEffectToImage(QPixmap src, QGraphicsEffect *effect, int extent); - QPixmap BlurImage(const QPixmap &pixmap, const double rad = 50); - QPixmap ColorizeImage(const QPixmap &pixmap, const QColor &color, const qreal factor); // This function cannot be marked as inline. QString RemoveInvalidFileName(const QString &fileName); bool IsValidFileName(const QString &fileName); @@ -49,27 +49,6 @@ namespace Qv2ray::common return GenerateRandomString().toLower(); // return QUuid::createUuid().toString(QUuid::WithoutBraces); } - // - template - QString StructToJsonString(const TYPE &t) - { - return QString::fromStdString(x2struct::X::tojson(t, "", 4, ' ')); - } - // - template - TYPE StructFromJsonString(const QString &str) - { - TYPE v; - x2struct::X::loadjson(str.toStdString(), v, false); - return v; - } - // Misc - template - QJsonObject GetRootObject(const T &t) - { - auto json = StructToJsonString(t); - return JsonFromString(json); - } inline QString TruncateString(const QString &str, int limit = -1, const QString &suffix = "...") { @@ -81,7 +60,6 @@ namespace Qv2ray::common namespace validation { const inline QRegularExpression __regex_ipv4_full(REGEX_IPV4_ADDR "$"); - const inline QRegularExpression __regex_ipv6_full(REGEX_IPV6_ADDR "$"); inline bool IsIPv4Address(const QString &addr) { @@ -90,12 +68,22 @@ namespace Qv2ray::common inline bool IsIPv6Address(const QString &addr) { - return __regex_ipv6_full.match(addr).hasMatch(); + QHostAddress address(addr); + return QAbstractSocket::IPv6Protocol == address.protocol(); } inline bool IsValidIPAddress(const QString &addr) { - return IsIPv4Address(addr) || IsIPv6Address(addr); + return !addr.isEmpty() && (IsIPv4Address(addr) || IsIPv6Address(addr)); + } + + inline bool IsValidDNSServer(const QString &addr) + { + return IsIPv4Address(addr) // + || IsIPv6Address(addr) // + || addr.startsWith("https://") // + || addr.startsWith("https+local://") // + || addr == "localhost"; } } // namespace validation @@ -103,18 +91,12 @@ namespace Qv2ray::common { QDateTime timestamp; timestamp.setSecsSinceEpoch(t); - return timestamp.toString(Qt::SystemLocaleShortDate); - } - - inline void FastAppendTextDocument(const QString &message, QTextDocument *doc) - { - QTextCursor cursor(doc); - cursor.movePosition(QTextCursor::End); - cursor.beginEditBlock(); - cursor.insertBlock(); - cursor.insertText(message); - cursor.endEditBlock(); + return timestamp.toString(); } + void QvMessageBoxWarn(QWidget *parent, const QString &title, const QString &text); + void QvMessageBoxInfo(QWidget *parent, const QString &title, const QString &text); + QMessageBox::StandardButton QvMessageBoxAsk(QWidget *parent, const QString &title, const QString &text, + QMessageBox::StandardButton extraButtons = QMessageBox::StandardButton::NoButton); } // namespace Qv2ray::common using namespace Qv2ray::common; diff --git a/src/common/QvTranslator.cpp b/src/common/QvTranslator.cpp index 2c5f5c63..0542fb21 100644 --- a/src/common/QvTranslator.cpp +++ b/src/common/QvTranslator.cpp @@ -1,16 +1,8 @@ #include "QvTranslator.hpp" -#include "base/Qv2rayLog.hpp" +#include "base/Qv2rayBase.hpp" #include "common/QvHelpers.hpp" -#include -#include -#include -#include -#include -#include -#include - using namespace Qv2ray::base; // path searching list. @@ -27,20 +19,19 @@ QStringList getLanguageSearchPaths() list << QString(QV2RAY_TRANSLATION_PATH); #endif return list; -}; +} namespace Qv2ray::common { QvTranslator::QvTranslator() { - DEBUG(MODULE_UI, "QvTranslator constructor.") GetAvailableLanguages(); } QStringList QvTranslator::GetAvailableLanguages() { languages.clear(); - for (auto path : getLanguageSearchPaths()) + for (const auto &path : getLanguageSearchPaths()) { languages << QDir(path).entryList(QStringList{ "*.qm" }, QDir::Hidden | QDir::Files); } @@ -52,20 +43,21 @@ namespace Qv2ray::common bool QvTranslator::InstallTranslation(const QString &code) { - for (auto path : getLanguageSearchPaths()) + for (const auto &path : getLanguageSearchPaths()) { if (FileExistsIn(QDir(path), code + ".qm")) { - LOG(MODULE_UI, "Found " + code + " in folder: " + path) + DEBUG(MODULE_UI, "Found " + code + " in folder: " + path) QTranslator *translatorNew = new QTranslator(); translatorNew->load(code + ".qm", path); if (pTranslator) { - LOG(MODULE_INIT, "Removed translations") + LOG(MODULE_UI, "Removed translations") qApp->removeTranslator(pTranslator.get()); } this->pTranslator.reset(translatorNew); qApp->installTranslator(pTranslator.get()); + LOG(MODULE_UI, "Successfully installed a translator for " + code) return true; } } diff --git a/src/components/autolaunch/QvAutoLaunch.cpp b/src/components/autolaunch/QvAutoLaunch.cpp index cdf2f006..de3457fa 100644 --- a/src/components/autolaunch/QvAutoLaunch.cpp +++ b/src/components/autolaunch/QvAutoLaunch.cpp @@ -1,6 +1,8 @@ #include "QvAutoLaunch.hpp" -#include +#include "base/Qv2rayBase.hpp" + +#include #include #include #include @@ -29,7 +31,7 @@ namespace Qv2ray::components::autolaunch bool GetLaunchAtLoginStatus() { #ifdef Q_OS_WIN - QString appName = QApplication::applicationName(); + QString appName = QCoreApplication::applicationName(); QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); return reg.contains(appName); } @@ -80,7 +82,7 @@ namespace Qv2ray::components::autolaunch } #elif defined Q_OS_LINUX - QString appName = QApplication::applicationName(); + QString appName = QCoreApplication::applicationName(); QString desktopFileLocation = getUserAutostartDir_private() + appName + QLatin1String(".desktop"); return QFile::exists(desktopFileLocation); } @@ -89,7 +91,7 @@ namespace Qv2ray::components::autolaunch void SetLaunchAtLoginStatus(bool enable) { #ifdef Q_OS_WIN - QString appName = QApplication::applicationName(); + QString appName = QCoreApplication::applicationName(); QSettings reg("HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); if (enable) @@ -160,7 +162,7 @@ namespace Qv2ray::components::autolaunch auto binPath = qEnvironmentVariableIsSet("APPIMAGE") ? qEnvironmentVariable("APPIMAGE") : QCoreApplication::applicationFilePath(); // // From https://github.com/nextcloud/desktop/blob/master/src/common/utility_unix.cpp - QString appName = QApplication::applicationName(); + QString appName = QCoreApplication::applicationName(); QString userAutoStartPath = getUserAutostartDir_private(); QString desktopFileLocation = userAutoStartPath + appName + QLatin1String(".desktop"); @@ -184,24 +186,23 @@ namespace Qv2ray::components::autolaunch QTextStream ts(&iniFile); ts.setCodec("UTF-8"); - ts << QLatin1String("[Desktop Entry]") << endl - << QLatin1String("Name=") << QApplication::applicationName() << endl - << QLatin1String("GenericName=") << QLatin1String("V2ray Frontend") << endl - << QLatin1String("Exec=") << binPath << endl - << QLatin1String("Terminal=") << "false" << endl - << QLatin1String("Icon=") << "qv2ray" << endl // always use lowercase for icons - << QLatin1String("Categories=") << "Network" << endl - << QLatin1String("Type=") << "Application" << endl - << QLatin1String("StartupNotify=") << "false" << endl - << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << endl; + ts << QLatin1String("[Desktop Entry]") << NEWLINE // + << QLatin1String("Name=") << QCoreApplication::applicationName() + QCoreApplication::arguments().join(" ") << NEWLINE // + << QLatin1String("GenericName=") << QLatin1String("V2ray Frontend") << NEWLINE // + << QLatin1String("Exec=") << binPath << NEWLINE // + << QLatin1String("Terminal=") << "false" << NEWLINE // + << QLatin1String("Icon=") << "qv2ray" << NEWLINE // + << QLatin1String("Categories=") << "Network" << NEWLINE // + << QLatin1String("Type=") << "Application" << NEWLINE // + << QLatin1String("StartupNotify=") << "false" << NEWLINE // + << QLatin1String("X-GNOME-Autostart-enabled=") << "true" << NEWLINE; + ts.flush(); + iniFile.close(); } else { - if (!QFile::remove(desktopFileLocation)) - { - // qCWarning(lcUtility) << "Could not remove autostart desktop - // file"; - } + QFile::remove(desktopFileLocation); + QFile::remove(desktopFileLocation.replace("qv2ray", "Qv2ray")); } } #endif diff --git a/src/components/darkmode/DarkmodeDetector.cpp b/src/components/darkmode/DarkmodeDetector.cpp index 33e7c089..f13a497d 100644 --- a/src/components/darkmode/DarkmodeDetector.cpp +++ b/src/components/darkmode/DarkmodeDetector.cpp @@ -1,9 +1,10 @@ #include "DarkmodeDetector.hpp" -#include +#include "base/Qv2rayBase.hpp" + +#include +#include #ifdef Q_OS_LINUX - #include - #include #elif defined(Q_OS_WIN32) #include #else @@ -16,7 +17,7 @@ namespace Qv2ray::components::darkmode // Copyright (C) 2020 KeePassXC Team bool isDarkMode() { -#if defined(Q_OS_LINUX) +#if !QvHasFeature(NativeDarkmode) if (!qApp || !qApp->style()) { return false; @@ -25,9 +26,6 @@ namespace Qv2ray::components::darkmode #elif defined(Q_OS_WIN32) QSettings settings(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)", QSettings::NativeFormat); return settings.value("AppsUseLightTheme", 1).toInt() == 0; -#elif defined(Q_OS_DARWIN) - // TODO: expand this stub - return false; #endif } diff --git a/src/components/geosite/QvGeositeReader.cpp b/src/components/geosite/QvGeositeReader.cpp index ec4487aa..0c490786 100644 --- a/src/components/geosite/QvGeositeReader.cpp +++ b/src/components/geosite/QvGeositeReader.cpp @@ -1,41 +1,54 @@ #include "QvGeositeReader.hpp" -#include "v2ray_geosite.pb.h" +#ifndef ANDROID + #include "v2ray_geosite.pb.h" +#endif namespace Qv2ray::components::geosite { + QMap GeositeEntries; QStringList ReadGeoSiteFromFile(const QString &filepath) { - QStringList list; - LOG(MODULE_FILEIO, "Reading geosites from: " + filepath) - // - GOOGLE_PROTOBUF_VERIFY_VERSION; - // - QFile f(filepath); - bool opened = f.open(QFile::OpenModeFlag::ReadOnly); - - if (!opened) + if (GeositeEntries.contains(filepath)) { - LOG(MODULE_FILEIO, "File cannot be opened: " + filepath) + return GeositeEntries.value(filepath); + } + else + { + QStringList list; +#ifndef ANDROID + LOG(MODULE_FILEIO, "Reading geosites from: " + filepath) + // + GOOGLE_PROTOBUF_VERIFY_VERSION; + // + QFile f(filepath); + bool opened = f.open(QFile::OpenModeFlag::ReadOnly); + + if (!opened) + { + LOG(MODULE_FILEIO, "File cannot be opened: " + filepath) + return list; + } + + auto content = f.readAll(); + f.close(); + // + GeoSiteList sites; + sites.ParseFromArray(content.data(), content.size()); + + for (const auto &e : sites.entry()) + { + // We want to use lower string. + list << QString::fromStdString(e.country_code()).toLower(); + } + + LOG(MODULE_FILEIO, "Loaded " + QSTRN(list.count()) + " geosite entries from data file.") + // Optional: Delete all global objects allocated by libprotobuf. + google::protobuf::ShutdownProtobufLibrary(); +#endif + list.sort(); + GeositeEntries[filepath] = list; return list; } - - auto content = f.readAll(); - f.close(); - // - GeoSiteList sites; - sites.ParseFromArray(content.data(), content.size()); - - for (auto e : sites.entry()) - { - // We want to use lower string. - list << QString::fromStdString(e.country_code()).toLower(); - } - - LOG(MODULE_FILEIO, "Loaded " + QSTRN(list.count()) + " geosite entries from data file.") - // Optional: Delete all global objects allocated by libprotobuf. - google::protobuf::ShutdownProtobufLibrary(); - list.sort(); - return list; } } // namespace Qv2ray::components::geosite diff --git a/src/components/geosite/QvGeositeReader.hpp b/src/components/geosite/QvGeositeReader.hpp index 2eaf93ef..f7a91945 100644 --- a/src/components/geosite/QvGeositeReader.hpp +++ b/src/components/geosite/QvGeositeReader.hpp @@ -4,7 +4,7 @@ namespace Qv2ray::components::geosite { QStringList ReadGeoSiteFromFile(const QString &filepath); -} +} // namespace Qv2ray::components::geosite using namespace Qv2ray::components; using namespace Qv2ray::components::geosite; diff --git a/src/components/latency/LatencyTest.cpp b/src/components/latency/LatencyTest.cpp new file mode 100644 index 00000000..fdeebac0 --- /dev/null +++ b/src/components/latency/LatencyTest.cpp @@ -0,0 +1,50 @@ +#include "LatencyTest.hpp" + +#include "LatencyTestThread.hpp" +#include "core/handler/ConfigHandler.hpp" + +constexpr auto LATENCY_PROPERTY_KEY = "__QvLatencyTest__"; + +namespace Qv2ray::components::latency +{ + LatencyTestHost::LatencyTestHost(const int defaultCount, QObject *parent) : QObject(parent) + { + totalTestCount = defaultCount; + } + + void LatencyTestHost::StopAllLatencyTest() + { + for (const auto &thread : latencyThreads) + { + thread->terminate(); + thread->deleteLater(); + } + } + void LatencyTestHost::TestLatency(const ConnectionId &id, Qv2rayLatencyTestingMethod method) + { + const auto &[protocol, host, port] = GetConnectionInfo(id); + auto thread = new LatencyTestThread(host, port, method, totalTestCount, this); + connect(thread, &QThread::finished, this, &LatencyTestHost::OnLatencyThreadProcessCompleted); + thread->setProperty(LATENCY_PROPERTY_KEY, QVariant::fromValue(id)); + latencyThreads.push_back(thread); + thread->start(); + } + + void LatencyTestHost::OnLatencyThreadProcessCompleted() + { + const auto senderThread = qobject_cast(sender()); + latencyThreads.removeOne(senderThread); + auto result = senderThread->GetResult(); + + if (!result.errorMessage.isEmpty()) + { + LOG(MODULE_NETWORK, "Ping --> " + result.errorMessage) + result.avg = LATENCY_TEST_VALUE_ERROR; + result.best = LATENCY_TEST_VALUE_ERROR; + result.worst = LATENCY_TEST_VALUE_ERROR; + } + + emit OnLatencyTestCompleted(senderThread->property(LATENCY_PROPERTY_KEY).value(), result); + } + +} // namespace Qv2ray::components::latency diff --git a/src/components/latency/LatencyTest.hpp b/src/components/latency/LatencyTest.hpp new file mode 100644 index 00000000..e0e66318 --- /dev/null +++ b/src/components/latency/LatencyTest.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "base/Qv2rayBase.hpp" + +namespace Qv2ray::components::latency +{ + class LatencyTestThread; + struct LatencyTestResult + { + QString errorMessage; + int totalCount; + int failedCount; + long worst = LATENCY_TEST_VALUE_ERROR; + long best = LATENCY_TEST_VALUE_ERROR; + long avg = LATENCY_TEST_VALUE_ERROR; + Qv2rayLatencyTestingMethod method; + }; + + class LatencyTestHost : public QObject + { + Q_OBJECT + public: + explicit LatencyTestHost(const int defaultCount = 3, QObject *parent = nullptr); + void TestLatency(const ConnectionId &connectionId, Qv2rayLatencyTestingMethod); + void StopAllLatencyTest(); + signals: + void OnLatencyTestCompleted(const ConnectionId &id, const LatencyTestResult &data); + + private slots: + void OnLatencyThreadProcessCompleted(); + + private: + int totalTestCount; + QList latencyThreads; + }; +} // namespace Qv2ray::components::latency + +using namespace Qv2ray::components::latency; diff --git a/src/components/latency/LatencyTestThread.cpp b/src/components/latency/LatencyTestThread.cpp new file mode 100644 index 00000000..6aeb2cf8 --- /dev/null +++ b/src/components/latency/LatencyTestThread.cpp @@ -0,0 +1,79 @@ +#include "LatencyTestThread.hpp" + +#include "TCPing.hpp" + +#ifdef Q_OS_UNIX + #include "unix/ICMPPing.hpp" +#else + #include "win/ICMPPing.hpp" +#endif + +namespace Qv2ray::components::latency +{ + + LatencyTestThread::LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod method, int count, QObject *parent) + : QThread(parent) + { + this->count = count; + this->host = host; + this->port = port; + this->method = method; + this->resultData = {}; + this->resultData.method = method; + } + void LatencyTestThread::run() + { + resultData.avg = 0; + resultData.best = 0; + resultData.worst = 0; + switch (method) + { + case ICMPING: + { + icmping::ICMPPing ping(30); + for (auto i = 0; i < count; i++) + { + resultData.totalCount++; + const auto pair = ping.ping(host); + const auto &errMessage = pair.second; + const long _latency = pair.first; + if (!errMessage.isEmpty()) + { + resultData.errorMessage.append(NEWLINE + errMessage); + resultData.failedCount++; + } + else + { +#ifdef Q_OS_WIN + // Is it Windows? + #undef min + #undef max +#endif + resultData.avg += _latency; + resultData.best = std::min(resultData.best, _latency); + resultData.worst = std::max(resultData.worst, _latency); + } + } + if (resultData.totalCount != 0 && resultData.failedCount != 0) + { + resultData.errorMessage.clear(); + resultData.avg = resultData.avg / (resultData.totalCount - resultData.failedCount) / 1000; + } + else + { + resultData.avg = LATENCY_TEST_VALUE_ERROR; + LOG(MODULE_NETWORK, resultData.errorMessage) + } + // + // + break; + } + case TCPING: + default: + { + this->resultData = tcping::TestTCPLatency(host, port, count); + break; + } + } + } +} // namespace Qv2ray::components::latency diff --git a/src/components/latency/LatencyTestThread.hpp b/src/components/latency/LatencyTestThread.hpp new file mode 100644 index 00000000..45593d53 --- /dev/null +++ b/src/components/latency/LatencyTestThread.hpp @@ -0,0 +1,30 @@ +#pragma once +#include "LatencyTest.hpp" + +#include +namespace Qv2ray::components::latency +{ + class LatencyTestThread : public QThread + { + Q_OBJECT + public: + explicit LatencyTestThread(const QString &host, int port, Qv2rayLatencyTestingMethod, int count, QObject *parent = nullptr); + LatencyTestResult GetResult() const + { + return resultData; + } + + protected: + void run() override; + + private: + LatencyTestResult resultData; + QString host; + int port; + int count; + Qv2rayLatencyTestingMethod method; + + // static LatencyTestResult TestLatency_p(const ConnectionId &id, const int count); + }; + +} // namespace Qv2ray::components::latency diff --git a/src/components/latency/QvTCPing.cpp b/src/components/latency/QvTCPing.cpp deleted file mode 100644 index 32970468..00000000 --- a/src/components/latency/QvTCPing.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#ifdef _WIN32 - #include - #include -#else - #include - #include - #include - #include -#endif -#include "QtConcurrent/QtConcurrent" -#include "QvTCPing.hpp" -#include "core/handler/ConfigHandler.hpp" - -namespace Qv2ray::components::tcping -{ - static int resolveHost(const std::string &host, int portnr, struct addrinfo **res); - static int testLatency(struct addrinfo *addr, std::chrono::system_clock::time_point *start, std::chrono::system_clock::time_point *end); - // - QvTCPingHelper::QvTCPingHelper(const int defaultCount, QObject *parent) : QObject(parent) - { - count = defaultCount; - } - - void QvTCPingHelper::StopAllLatenceTest() - { - while (!pingWorkingThreads.isEmpty()) - { - auto worker = pingWorkingThreads.dequeue(); - worker->future().cancel(); - worker->cancel(); - } - } - void QvTCPingHelper::TestLatency(const ConnectionId &id) - { - auto watcher = new QFutureWatcher(this); - watcher->setFuture(QtConcurrent::run(&QvTCPingHelper::TestLatency_p, id, count)); - pingWorkingThreads.enqueue(watcher); - // - connect(watcher, &QFutureWatcher::finished, this, [=]() { - auto result = watcher->result(); - this->pingWorkingThreads.removeOne(watcher); - - if (!result.errorMessage.isEmpty()) - { - LOG(MODULE_NETWORK, "Ping --> " + result.errorMessage) - result.avg = QVTCPING_VALUE_ERROR; - result.best = QVTCPING_VALUE_ERROR; - result.worst = QVTCPING_VALUE_ERROR; - } - - emit this->OnLatencyTestCompleted(result); - }); - } - - QvTCPingResultObject QvTCPingHelper::TestLatency_p(const ConnectionId &id, const int count) - { - QvTCPingResultObject data; - data.connectionId = id; - - if (isExiting) - return data; - - auto [protocol, host, port] = GetConnectionInfo(id); - Q_UNUSED(protocol) - double successCount = 0, errorCount = 0; - addrinfo *resolved; - int errcode; - - if ((errcode = resolveHost(host.toStdString(), port, &resolved)) != 0) - { -#ifdef _WIN32 - data.errorMessage = QString::fromStdWString(gai_strerror(errcode)); -#else - data.errorMessage = gai_strerror(errcode); -#endif - return data; - } - - bool noAddress = false; - int currentCount = 0; - - data.avg = 0; - data.worst = 0; - data.best = 0; - - while (currentCount < count) - { - system_clock::time_point start; - system_clock::time_point end; - - if ((errcode = testLatency(resolved, &start, &end)) != 0) - { - if (errcode != -EADDRNOTAVAIL) - { - LOG(MODULE_NETWORK, "error connecting to host: " + host + ":" + QSTRN(port) + " " + strerror(-errcode)) - errorCount++; - } - else - { - if (noAddress) - { - LOG(MODULE_NETWORK, "no address error") - } - else - { - LOG(MODULE_NETWORK, "error connecting to host: " + QSTRN(-errcode) + " " + strerror(-errcode)) - } - - noAddress = true; - } - } - else - { - noAddress = false; - successCount++; - auto milliseconds = std::chrono::duration_cast(end - start); - uint ms = milliseconds.count(); - data.avg += ms; - data.worst = min(data.worst, ms); - data.best = max(data.best, ms); - - if (ms > 1000) - { - LOG(MODULE_NETWORK, "Stop the test on the first long connect()") - break; /* Stop the test on the first long connect() */ - } - } - - currentCount++; - QThread::msleep(500); - } - - data.avg = data.avg / successCount; - freeaddrinfo(resolved); - data.avg = min(data.avg, QVTCPING_VALUE_ERROR); - data.worst = min(data.worst, QVTCPING_VALUE_ERROR); - data.best = min(data.best, QVTCPING_VALUE_ERROR); - return data; - } - - int resolveHost(const string &host, int port, addrinfo **res) - { - if (isExiting) - return 0; - - addrinfo hints; -#ifdef _WIN32 - WSADATA wsadata; - WSAStartup(0x0202, &wsadata); - memset(&hints, 0, sizeof(struct addrinfo)); -#else - hints.ai_flags = AI_NUMERICSERV; -#endif - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - hints.ai_protocol = 0; - return getaddrinfo(host.c_str(), to_string(port).c_str(), &hints, res); - } - - int testLatency(struct addrinfo *addr, std::chrono::system_clock::time_point *start, std::chrono::system_clock::time_point *end) - { -#ifdef _WIN32 - SOCKET fd; -#else - int fd; -#endif - const int on = 1; - /* int flags; */ - int rv = 0; - - /* try to connect for each of the entries: */ - while (addr != nullptr) - { - if (isExiting) - return 0; - - /* create socket */ - fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol); - - if (!fd) - { - goto next_addr0; - } - -#ifdef _WIN32 - - // Windows needs special conversion. - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0) -#else - if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) -#endif - { - goto next_addr1; - } - - *start = system_clock::now(); - - /* connect to peer */ - // Qt has its own connect() function in QObject.... - // So we add "::" here - if (::connect(fd, addr->ai_addr, addr->ai_addrlen) == 0) - { - *end = system_clock::now(); -#ifdef Q_OS_WIN - closesocket(fd); -#else - close(fd); -#endif - return 0; - } - - next_addr1: -#ifdef _WIN32 - closesocket(fd); -#else - close(fd); -#endif - next_addr0: - addr = addr->ai_next; - } - - rv = rv ? rv : -errno; - return rv; - } -} // namespace Qv2ray::core::tcping diff --git a/src/components/latency/QvTCPing.hpp b/src/components/latency/QvTCPing.hpp deleted file mode 100644 index 98bb6411..00000000 --- a/src/components/latency/QvTCPing.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once -#include "base/Qv2rayBase.hpp" -#include "core/CoreSafeTypes.hpp" - -namespace Qv2ray::components::tcping -{ - struct QvTCPingResultObject - { - ConnectionId connectionId = NullConnectionId; - QString errorMessage; - int total, succeed, failed; - uint worst = QVTCPING_VALUE_ERROR, best = QVTCPING_VALUE_ERROR, avg = QVTCPING_VALUE_ERROR; - }; - - class QvTCPingHelper : public QObject - { - Q_OBJECT - - public: - explicit QvTCPingHelper(const int defaultCount = 5, QObject *parent = nullptr); - void TestLatency(const ConnectionId &connectionId); - void StopAllLatenceTest(); - signals: - void OnLatencyTestCompleted(const QvTCPingResultObject &data); - - private: - static QvTCPingResultObject TestLatency_p(const ConnectionId &id, const int count); - int count; - QQueue *> pingWorkingThreads; - }; -} // namespace Qv2ray::components::tcping - -using namespace Qv2ray::components::tcping; diff --git a/src/components/latency/TCPing.cpp b/src/components/latency/TCPing.cpp new file mode 100644 index 00000000..789a192d --- /dev/null +++ b/src/components/latency/TCPing.cpp @@ -0,0 +1,364 @@ +#include "TCPing.hpp" + +#ifdef _WIN32 + #include + #include +#else + #include + #include + #include + #include + #include + #include +#endif + +namespace Qv2ray::components::latency::tcping +{ + +#ifdef Q_OS_WIN + using qv_socket_t = SOCKET; + struct TranslateWSAError{ + ~TranslateWSAError(){ + errno = translate_sys_error(WSAGetLastError()); + } + int translate_sys_error(int sys_errno) { + LOG(MODULE_NETWORK, "translate_sys_error:WSAGetLastError()==" + QSTRN(sys_errno)) + switch (sys_errno) { + case ERROR_NOACCESS: return EACCES; + case WSAEACCES: return EACCES; + case ERROR_ELEVATION_REQUIRED: return EACCES; + case ERROR_CANT_ACCESS_FILE: return EACCES; + case ERROR_ADDRESS_ALREADY_ASSOCIATED: return EADDRINUSE; + case WSAEADDRINUSE: return EADDRINUSE; + case WSAEADDRNOTAVAIL: return EADDRNOTAVAIL; + case WSAEAFNOSUPPORT: return EAFNOSUPPORT; + case WSAEWOULDBLOCK: return EAGAIN; + case WSAEALREADY: return EALREADY; + case ERROR_INVALID_FLAGS: return EBADF; + case ERROR_INVALID_HANDLE: return EBADF; + case ERROR_LOCK_VIOLATION: return EBUSY; + case ERROR_PIPE_BUSY: return EBUSY; + case ERROR_SHARING_VIOLATION: return EBUSY; + case ERROR_OPERATION_ABORTED: return ECANCELED; + case WSAEINTR: return ECANCELED; + /*case ERROR_NO_UNICODE_TRANSLATION: return ECHARSET;*/ + case ERROR_CONNECTION_ABORTED: return ECONNABORTED; + case WSAECONNABORTED: return ECONNABORTED; + case ERROR_CONNECTION_REFUSED: return ECONNREFUSED; + case WSAECONNREFUSED: return ECONNREFUSED; + case ERROR_NETNAME_DELETED: return ECONNRESET; + case WSAECONNRESET: return ECONNRESET; + case ERROR_ALREADY_EXISTS: return EEXIST; + case ERROR_FILE_EXISTS: return EEXIST; + case ERROR_BUFFER_OVERFLOW: return EFAULT; + case WSAEFAULT: return EFAULT; + case ERROR_HOST_UNREACHABLE: return EHOSTUNREACH; + case WSAEHOSTUNREACH: return EHOSTUNREACH; + case ERROR_INSUFFICIENT_BUFFER: return EINVAL; + case ERROR_INVALID_DATA: return EINVAL; + case ERROR_INVALID_PARAMETER: return EINVAL; + case ERROR_SYMLINK_NOT_SUPPORTED: return EINVAL; + case WSAEINVAL: return EINVAL; + case WSAEPFNOSUPPORT: return EINVAL; + case WSAESOCKTNOSUPPORT: return EINVAL; + case ERROR_BEGINNING_OF_MEDIA: return EIO; + case ERROR_BUS_RESET: return EIO; + case ERROR_CRC: return EIO; + case ERROR_DEVICE_DOOR_OPEN: return EIO; + case ERROR_DEVICE_REQUIRES_CLEANING: return EIO; + case ERROR_DISK_CORRUPT: return EIO; + case ERROR_EOM_OVERFLOW: return EIO; + case ERROR_FILEMARK_DETECTED: return EIO; + case ERROR_GEN_FAILURE: return EIO; + case ERROR_INVALID_BLOCK_LENGTH: return EIO; + case ERROR_IO_DEVICE: return EIO; + case ERROR_NO_DATA_DETECTED: return EIO; + case ERROR_NO_SIGNAL_SENT: return EIO; + case ERROR_OPEN_FAILED: return EIO; + case ERROR_SETMARK_DETECTED: return EIO; + case ERROR_SIGNAL_REFUSED: return EIO; + case WSAEISCONN: return EISCONN; + case ERROR_CANT_RESOLVE_FILENAME: return ELOOP; + case ERROR_TOO_MANY_OPEN_FILES: return EMFILE; + case WSAEMFILE: return EMFILE; + case WSAEMSGSIZE: return EMSGSIZE; + case ERROR_FILENAME_EXCED_RANGE: return ENAMETOOLONG; + case ERROR_NETWORK_UNREACHABLE: return ENETUNREACH; + case WSAENETUNREACH: return ENETUNREACH; + case WSAENOBUFS: return ENOBUFS; + case ERROR_BAD_PATHNAME: return ENOENT; + case ERROR_DIRECTORY: return ENOENT; + case ERROR_ENVVAR_NOT_FOUND: return ENOENT; + case ERROR_FILE_NOT_FOUND: return ENOENT; + case ERROR_INVALID_NAME: return ENOENT; + case ERROR_INVALID_DRIVE: return ENOENT; + case ERROR_INVALID_REPARSE_DATA: return ENOENT; + case ERROR_MOD_NOT_FOUND: return ENOENT; + case ERROR_PATH_NOT_FOUND: return ENOENT; + case WSAHOST_NOT_FOUND: return ENOENT; + case WSANO_DATA: return ENOENT; + case ERROR_NOT_ENOUGH_MEMORY: return ENOMEM; + case ERROR_OUTOFMEMORY: return ENOMEM; + case ERROR_CANNOT_MAKE: return ENOSPC; + case ERROR_DISK_FULL: return ENOSPC; + case ERROR_EA_TABLE_FULL: return ENOSPC; + case ERROR_END_OF_MEDIA: return ENOSPC; + case ERROR_HANDLE_DISK_FULL: return ENOSPC; + case ERROR_NOT_CONNECTED: return ENOTCONN; + case WSAENOTCONN: return ENOTCONN; + case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY; + case WSAENOTSOCK: return ENOTSOCK; + case ERROR_NOT_SUPPORTED: return ENOTSUP; + case ERROR_BROKEN_PIPE: return EOF; + case ERROR_ACCESS_DENIED: return EPERM; + case ERROR_PRIVILEGE_NOT_HELD: return EPERM; + case ERROR_BAD_PIPE: return EPIPE; + case ERROR_NO_DATA: return EPIPE; + case ERROR_PIPE_NOT_CONNECTED: return EPIPE; + case WSAESHUTDOWN: return EPIPE; + case WSAEPROTONOSUPPORT: return EPROTONOSUPPORT; + case ERROR_WRITE_PROTECT: return EROFS; + case ERROR_SEM_TIMEOUT: return ETIMEDOUT; + case WSAETIMEDOUT: return ETIMEDOUT; + case ERROR_NOT_SAME_DEVICE: return EXDEV; + case ERROR_INVALID_FUNCTION: return EISDIR; + case ERROR_META_EXPANSION_TOO_LONG: return E2BIG; + default: return EIO;/*unknown*/ + } + } + }; +#else + using qv_socket_t = int; +#endif + + inline int setnonblocking(qv_socket_t sockno, int &opt) + { +#ifdef _WIN32 + ULONG block = 1; + if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR) + { + return -1; + } +#else + if ((opt = fcntl(sockno, F_GETFL, NULL)) < 0) + { + // get socket flags + return -1; + } + if (fcntl(sockno, F_SETFL, opt | O_NONBLOCK) < 0) + { + // set socket non-blocking + return -1; + } +#endif + return 0; + } + + inline int setblocking(qv_socket_t sockno, int &opt) + { +#ifdef _WIN32 + ULONG block = 0; + if (ioctlsocket(sockno, FIONBIO, &block) == SOCKET_ERROR) + { + return -1; + } +#else + if (fcntl(sockno, F_SETFL, opt) < 0) + { + // reset socket flags + return -1; + } +#endif + return 0; + } + + int connect_wait(qv_socket_t sockno, struct sockaddr *addr, size_t addrlen, int timeout_sec = 5) + { + int res; + int opt; + timeval tv = { 0, 0 }; +#ifdef _WIN32 + TranslateWSAError _translateWSAError_; +#endif + tv.tv_sec = timeout_sec; + tv.tv_usec = 0; + if ((res = setnonblocking(sockno, opt)) != 0) + return -1; + if ((res = ::connect(sockno, addr, addrlen)) < 0) + { +#ifdef _WIN32 + if (WSAGetLastError() == WSAEWOULDBLOCK) + { +#else + if (errno == EINPROGRESS) + { +#endif + // connecting + fd_set wait_set; + FD_ZERO(&wait_set); + FD_SET(sockno, &wait_set); + res = select(sockno + 1, NULL, &wait_set, NULL, &tv); + } + } + else + // connect immediately + res = 1; + + if (setblocking(sockno, opt) != 0) + return -1; + + if (res < 0) + // an error occured + return -1; + else if (res == 0) + { + // timeout + errno = ETIMEDOUT; + return 1; + } + else + { + socklen_t len = sizeof(opt); + if (getsockopt(sockno, SOL_SOCKET, SO_ERROR, (char *) (&opt), &len) < 0) + return -1; + } + return 0; + } + + int resolveHost(const QString &host, int port, addrinfo **res) + { + addrinfo hints; +#ifdef Q_OS_WIN + WSADATA wsadata; + WSAStartup(0x0202, &wsadata); + memset(&hints, 0, sizeof(struct addrinfo)); +#else + hints.ai_flags = AI_NUMERICSERV; +#endif + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + return getaddrinfo(host.toStdString().c_str(), std::to_string(port).c_str(), &hints, res); + } + + int testLatency(struct addrinfo *addr, system_clock::time_point *start, system_clock::time_point *end) + { + qv_socket_t fd; + + const int on = 1; + + /* try to connect for each of the entries: */ + while (addr != nullptr) + { + if (isExiting) + return 0; + + // create socket + if (!(fd = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol))) + { + goto next_addr0; + } + + // Windows needs special conversion. +#ifdef _WIN32 + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *) &on, sizeof(on)) < 0) +#else + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) +#endif + goto next_addr1; + *start = system_clock::now(); + if (connect_wait(fd, addr->ai_addr, addr->ai_addrlen) == 0) + { + *end = system_clock::now(); +#ifdef Q_OS_WIN + closesocket(fd); +#else + close(fd); +#endif + return 0; + } + + next_addr1: +#ifdef _WIN32 + closesocket(fd); +#else + close(fd); +#endif + next_addr0: + addr = addr->ai_next; + } + + return -1; + } + + LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount) + { + LatencyTestResult data; + int successCount = 0; + addrinfo *resolved; + int errcode; + + if ((errcode = resolveHost(host, port, &resolved)) != 0) + { +#ifdef Q_OS_WIN + data.errorMessage = QString::fromStdWString(gai_strerror(errcode)); +#else + data.errorMessage = gai_strerror(errcode); +#endif + return data; + } + + int currentCount = 0; + + data.avg = 0; + data.worst = 0; + data.best = 0; + + while (currentCount < testCount) + { + system_clock::time_point start; + system_clock::time_point end; + + if ((errcode = testLatency(resolved, &start, &end)) != 0) + { + LOG(MODULE_NETWORK, "error connecting to host: " + host + ":" + QSTRN(port) + " " + strerror(errno)) + } + else + { + successCount++; + auto milliseconds = std::chrono::duration_cast(end - start); + long ms = milliseconds.count(); + data.avg += ms; +#ifdef Q_OS_WIN + // Is it Windows? + #undef min + #undef max +#endif + data.worst = std::min(data.worst, ms); + data.best = std::max(data.best, ms); + + if (ms > 1000) + { + LOG(MODULE_NETWORK, "Stop the test on the first long connect()") + break; /* Stop the test on the first long connect() */ + } + } + + currentCount++; + QThread::msleep(200); + } + freeaddrinfo(resolved); + if (successCount > 0) + { + data.avg = data.avg / successCount; + } + else + { + data.avg = LATENCY_TEST_VALUE_ERROR; + data.worst = LATENCY_TEST_VALUE_ERROR; + data.best = LATENCY_TEST_VALUE_ERROR; + data.errorMessage = QObject::tr("Timeout"); + } + return data; + } +} // namespace Qv2ray::components::latency::tcping diff --git a/src/components/latency/TCPing.hpp b/src/components/latency/TCPing.hpp new file mode 100644 index 00000000..134c8df8 --- /dev/null +++ b/src/components/latency/TCPing.hpp @@ -0,0 +1,7 @@ +#pragma once +#include "LatencyTest.hpp" +#include "base/Qv2rayBase.hpp" +namespace Qv2ray::components::latency::tcping +{ + LatencyTestResult TestTCPLatency(const QString &host, int port, int testCount); +} // namespace Qv2ray::components::latency::tcping diff --git a/src/components/latency/unix/ICMPPing.cpp b/src/components/latency/unix/ICMPPing.cpp new file mode 100644 index 00000000..7f899e37 --- /dev/null +++ b/src/components/latency/unix/ICMPPing.cpp @@ -0,0 +1,156 @@ +/* Author: Maciek Muszkowski + * + * This is a simple ping implementation for Linux. + * It will work ONLY on kernels 3.x+ and you need + * to set allowed groups in /proc/sys/net/ipv4/ping_group_range */ +#include "ICMPPing.hpp" + +#include +#ifdef Q_OS_UNIX + #include + // + #include + #include + #include + #include + #include + #include + #include + #include + #ifdef Q_OS_MAC + #define SOL_IP 0 + #endif + +namespace Qv2ray::components::latency::icmping +{ + /// 1s complementary checksum + uint16_t ping_checksum(const char *buf, size_t size) + { + size_t i; + uint64_t sum = 0; + + for (i = 0; i < size; i += 2) + { + sum += *(uint16_t *) buf; + buf += 2; + } + if (size - i > 0) + { + sum += *(uint8_t *) buf; + } + + while ((sum >> 16) != 0) + { + sum = (sum & 0xffff) + (sum >> 16); + } + + return (uint16_t) ~sum; + } + void ICMPPing::deinit() + { + if (socketId >= 0) + { + close(socketId); + socketId = -1; + } + } + + ICMPPing::ICMPPing(int ttl) + { + auto timeout_s = 5; + // create socket + if ( //((sd = socket(PF_INET, SOCK_RAW, IPPROTO_ICMP)) < 0) && + ((socketId = socket(PF_INET, SOCK_DGRAM, IPPROTO_ICMP)) < 0)) + { + initErrorMessage = "EPING_SOCK: " + QObject::tr("Socket creation failed"); + return; + } + // set TTL + if (setsockopt(socketId, SOL_IP, IP_TTL, &ttl, sizeof(ttl)) != 0) + { + deinit(); + initErrorMessage = "EPING_TTL: " + QObject::tr("Failed to setup TTL value"); + return; + } + + // set timeout in secs (do not use secs - BUGGY) + timeval timeout; + timeout.tv_sec = timeout_s; + timeout.tv_usec = 0; + if (setsockopt(socketId, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(timeout)) != 0) + { + deinit(); + initErrorMessage = "EPING_SETTO: " + QObject::tr("Setting timeout failed"); + return; + } + initialized = true; + } + + /// @return value < 0 on error, response time in ms on success + QPair ICMPPing::ping(const QString &address) + { + if (!initialized) + { + return { 0, initErrorMessage }; + } + timeval start, end; + socklen_t slen; + // not initialized + if (socketId < 0) + return { 0, "EPING_SOCK:" + QObject::tr("Socket creation failed") }; + + // resolve hostname + hostent *resolvedAddress = gethostbyname(address.toStdString().c_str()); + if (!resolvedAddress) + return { 0, "EPING_HOST: " + QObject::tr("Unresolvable hostname") }; + + // set IP address to ping + sockaddr_in targetAddress; + memset(&targetAddress, 0, sizeof(targetAddress)); + targetAddress.sin_family = resolvedAddress->h_addrtype; + targetAddress.sin_port = 0; + memcpy(&targetAddress.sin_addr, resolvedAddress->h_addr, resolvedAddress->h_length); + + // prepare echo request packet + icmp _icmp_request; + memset(&_icmp_request, 0, sizeof(_icmp_request)); + _icmp_request.icmp_type = ICMP_ECHO; + _icmp_request.icmp_hun.ih_idseq.icd_id = 0; // SOCK_DGRAM & 0 => id will be set by kernel + unsigned short sent_seq; + _icmp_request.icmp_hun.ih_idseq.icd_seq = sent_seq = seq++; + _icmp_request.icmp_cksum = ping_checksum(reinterpret_cast(&_icmp_request), sizeof(_icmp_request)); + + // send echo request + gettimeofday(&start, NULL); + if (sendto(socketId, &_icmp_request, sizeof(icmp), 0, (struct sockaddr *) &targetAddress, sizeof(targetAddress)) <= 0) + return { 0, "EPING_SEND: " + QObject::tr("Sending echo request failed") }; + + // receive response (if any) + sockaddr_in remove_addr; + slen = sizeof(remove_addr); + int rlen; + icmp resp; + while ((rlen = recvfrom(socketId, &resp, sizeof(icmp), 0, (struct sockaddr *) &remove_addr, &slen)) > 0) + { + gettimeofday(&end, NULL); + + // skip malformed + if (rlen != sizeof(icmp)) + continue; + + // skip the ones we didn't send + if (resp.icmp_hun.ih_idseq.icd_seq != sent_seq) + continue; + + switch (resp.icmp_type) + { + case ICMP_ECHOREPLY: return { 1000000 * (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec), {} }; + case ICMP_UNREACH: return { 0, "EPING_DST: " + QObject::tr("Destination unreachable") }; + case ICMP_TIMXCEED: return { 0, "EPING_TIME: " + QObject::tr("Timeout") }; + default: return { 0, "EPING_UNK: " + QObject::tr("Unknown error") }; + } + } + return { 0, "EPING_TIME: " + QObject::tr("Timeout") }; + } +} // namespace Qv2ray::components::latency::icmping +#endif diff --git a/src/components/latency/unix/ICMPPing.hpp b/src/components/latency/unix/ICMPPing.hpp new file mode 100644 index 00000000..987086fc --- /dev/null +++ b/src/components/latency/unix/ICMPPing.hpp @@ -0,0 +1,28 @@ +#pragma once +#include +#ifdef Q_OS_UNIX + #include + #include +namespace Qv2ray::components::latency::icmping +{ + class ICMPPing + { + public: + explicit ICMPPing(int ttl); + ~ICMPPing() + { + deinit(); + } + QPair ping(const QString &address); + + private: + void deinit(); + // number incremented with every echo request packet send + unsigned short seq = 1; + // socket + int socketId = -1; + bool initialized = false; + QString initErrorMessage; + }; +} // namespace Qv2ray::components::latency::icmping +#endif diff --git a/src/components/latency/win/ICMPPing.cpp b/src/components/latency/win/ICMPPing.cpp new file mode 100644 index 00000000..74342742 --- /dev/null +++ b/src/components/latency/win/ICMPPing.cpp @@ -0,0 +1,87 @@ +#include "ICMPPing.hpp" +#ifdef Q_OS_WIN +// + #include +// + #include +// + #include +// + #include +// + #include + #include +namespace Qv2ray::components::latency::icmping +{ + ICMPPing::ICMPPing(uint64_t timeout) + { + + // remember the timeout + // UNUSED + this->timeout = timeout; + } + + ICMPPing ::~ICMPPing() + { + } + QPair ICMPPing::ping(const QString &ipAddr) + { + HANDLE hIcmpFile; + // create icmp handle + if ((hIcmpFile = IcmpCreateFile()) == INVALID_HANDLE_VALUE) + { + throw "IcmpCreateFile failed"; + } + QList addresses; + { + QEventLoop loop; + QHostInfo::fromName(ipAddr).lookupHost(ipAddr, &loop, [&](const QHostInfo &h) { + for (const auto &addr : h.addresses()) + { + if (addr.protocol() == QAbstractSocket::IPv4Protocol) + { + addresses << addr; + } + } + loop.quit(); + }); + loop.exec(); + } + if (addresses.isEmpty()) + { + return { 0, QObject::tr("DNS lookup failed.") }; + } + // Parse the destination IP address. + IN_ADDR dest_ip{}; + if (1 != InetPtonA(AF_INET, addresses.first().toString().toStdString().c_str(), &dest_ip)) + { + return { 255, "Cannot convert IP address: " + addresses.first().toString() }; + } + + // Payload to send. + constexpr WORD payload_size = 1; + unsigned char payload[payload_size]{ 42 }; + + // Reply buffer for exactly 1 echo reply, payload data, and 8 bytes for ICMP error message. + constexpr DWORD reply_buf_size = sizeof(ICMP_ECHO_REPLY) + payload_size + 8; + unsigned char reply_buf[reply_buf_size]{}; + + // Make the echo request. + DWORD reply_count = IcmpSendEcho(hIcmpFile, dest_ip.S_un.S_addr, payload, payload_size, NULL, reply_buf, reply_buf_size, 10000); + + // Return value of 0 indicates failure, try to get error info. + if (reply_count == 0) + { + auto e = GetLastError(); + DWORD buf_size = 1000; + TCHAR buf[1000]; + GetIpErrorString(e, buf, &buf_size); + return { 255, "IcmpSendEcho returned error (" + QString::fromStdWString(buf) + ")" }; + } + // release the handle on destruction + IcmpCloseHandle(hIcmpFile); + const ICMP_ECHO_REPLY *r = (const ICMP_ECHO_REPLY *) reply_buf; + return QPair(r->RoundTripTime * 1000, QString{}); + } +} // namespace Qv2ray::components::latency::icmping +#endif diff --git a/src/components/latency/win/ICMPPing.hpp b/src/components/latency/win/ICMPPing.hpp new file mode 100644 index 00000000..9b9b922b --- /dev/null +++ b/src/components/latency/win/ICMPPing.hpp @@ -0,0 +1,35 @@ +#pragma once + +/** + * ICMPPinger - An Implementation of ICMPPing on Windows Platform + * Required Windows Version: 2000 / XP / 7 / Vista+ + * License: WTFPL + */ +#include +#ifdef Q_OS_WIN + + #include + #include + #include + #include + #include + +namespace Qv2ray::components::latency::icmping +{ + class ICMPPing + { + public: + ICMPPing(uint64_t timeout = DEFAULT_TIMEOUT); + ~ICMPPing(); + + public: + static const uint64_t DEFAULT_TIMEOUT = 10000U; + + public: + QPair ping(const QString &ipAddr); + + private: + uint64_t timeout = DEFAULT_TIMEOUT; + }; +} // namespace Qv2ray::components::latency::icmping +#endif diff --git a/src/components/latency/win/ICMPPinger.cpp b/src/components/latency/win/ICMPPinger.cpp deleted file mode 100644 index f57cee55..00000000 --- a/src/components/latency/win/ICMPPinger.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "ICMPPinger.hpp" -#if 0 - -ICMPPinger::ICMPPinger(UINT64 timeout = DEFAULT_TIMEOUT) -{ - // create icmp handle - if ((this->hIcmpFile = IcmpCreateFile()) == INVALID_HANDLE_VALUE) - { - throw "IcmpCreateFile failed"; - } - - // remember the timeout - this->timeout = timeout; -} - -ICMPPinger::~ICMPPinger() -{ - // release the handle on destruction - IcmpCloseHandle(this->hIcmpFile); -} - -std::pair, std::optional> ICMPPinger::ping(const std::string &ipAddr) -{ - // convert network address - const auto addr = inet_addr(ipAddr.c_str()); - if (addr == INADDR_NONE) - { - return std::pair(std::nullopt, "invalid ip address: " + ipAddr); - } - - // request buffer - const static char bufRequest[] = "echo test"; - - // response buffer - const auto responseSize = sizeof(ICMP_ECHO_REPLY) + sizeof(bufRequest); - const std::unique_ptr bufRecv(new (char[responseSize])); - - // send echo - auto ret = IcmpSendEcho(this->hIcmpFile, addr, (LPVOID) bufRequest, sizeof(bufRequest), NULL, bufRecv.get(), responseSize, this->timeout); - - // ret == 0: failed - if (ret == 0) - { - return std::pair(std::nullopt, "IcmpSendEcho returned error"); - } - - // read round-trip time - PICMP_ECHO_REPLY pReply = (PICMP_ECHO_REPLY) bufRecv.get(); - return std::pair(pReply->RoundTripTime, std::nullopt); -} - -#endif diff --git a/src/components/latency/win/ICMPPinger.hpp b/src/components/latency/win/ICMPPinger.hpp deleted file mode 100644 index 5c5ca460..00000000 --- a/src/components/latency/win/ICMPPinger.hpp +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once -#include -#include -#if 0 -/** - * ICMPPinger - An Implementation of ICMPPing on Windows Platform - * Required Windows Version: 2000 / XP / 7 / Vista+ - * License: WTFPL - */ - - #include - #include - #include - #include - #include - #include - -class ICMPPinger -{ - public: - ICMPPinger(UINT64 timeout = DEFAULT_TIMEOUT); - ~ICMPPinger(); - - public: - static const UINT64 DEFAULT_TIMEOUT = 10000U; - - public: - std::pair, std::optional> ping(const std::string &ipAddr); - - private: - HANDLE hIcmpFile; - UINT64 timeout = DEFAULT_TIMEOUT; -}; -#endif diff --git a/src/components/ntp/QvNTPClient.cpp b/src/components/ntp/QvNTPClient.cpp new file mode 100644 index 00000000..9611d4ce --- /dev/null +++ b/src/components/ntp/QvNTPClient.cpp @@ -0,0 +1,216 @@ +/* This file from part of QNtp, a library that implements NTP protocol. + * + * Copyright (C) 2011 Alexander Fokin + * + * QNtp is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * QNtp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with QNtp. If not, see . */ + +#include "QvNTPClient.hpp" + +#include + +namespace Qv2ray::components::ntp +{ + + NtpTimestamp NtpTimestamp::fromDateTime(const QDateTime &dateTime) + { + qint64 ntpMSecs = dateTime.toMSecsSinceEpoch() - january_1_1900; + + quint32 seconds = ntpMSecs / 1000; + quint32 fraction = 0x100000000ll * (ntpMSecs % 1000) / 1000; + + NtpTimestamp result; + result.seconds = qToBigEndian(seconds); + result.fraction = qToBigEndian(fraction); + return result; + } + + QDateTime NtpTimestamp::toDateTime(const NtpTimestamp &ntpTime) + { + /* Convert to local-endian. */ + quint32 seconds = qFromBigEndian(ntpTime.seconds); + quint32 fraction = qFromBigEndian(ntpTime.fraction); + + /* Convert NTP timestamp to number of milliseconds passed since Jan 1 1900. */ + qint64 ntpMSecs = seconds * 1000ll + fraction * 1000ll / 0x100000000ll; + + /* Construct Qt date time. */ + return QDateTime::fromMSecsSinceEpoch(ntpMSecs + january_1_1900); + } + + NtpReply::NtpReply() : d(new NtpReplyPrivate()) + { + /* We don't use shared null because NtpReplyPrivate isn't a POD type and + * allocation overhead is negligible here. */ + memset(&d->packet, 0, sizeof(d->packet)); + } + + NtpReply::NtpReply(NtpReplyPrivate *dd) : d(dd) + { + Q_ASSERT(dd != NULL); + } + + NtpReply::NtpReply(const NtpReply &other) : d(other.d) + { + } + + NtpReply::~NtpReply() + { + } + + NtpReply &NtpReply::operator=(const NtpReply &other) + { + d = other.d; + + return *this; + } + + NtpLeapIndicator NtpReply::leapIndicator() const + { + return static_cast(d->packet.basic.flags.leapIndicator); + } + + quint8 NtpReply::versionNumber() const + { + return d->packet.basic.flags.versionNumber; + } + + NtpMode NtpReply::mode() const + { + return static_cast(d->packet.basic.flags.mode); + } + + quint8 NtpReply::stratum() const + { + return d->packet.basic.stratum; + } + + qreal NtpReply::pollInterval() const + { + return std::pow(static_cast(2), static_cast(d->packet.basic.poll)); + } + + qreal NtpReply::precision() const + { + return std::pow(static_cast(2), static_cast(d->packet.basic.precision)); + } + + QDateTime NtpReply::referenceTime() const + { + return NtpTimestamp::toDateTime(d->packet.basic.referenceTimestamp); + } + + QDateTime NtpReply::originTime() const + { + return NtpTimestamp::toDateTime(d->packet.basic.originateTimestamp); + } + + QDateTime NtpReply::receiveTime() const + { + return NtpTimestamp::toDateTime(d->packet.basic.receiveTimestamp); + } + + QDateTime NtpReply::transmitTime() const + { + return NtpTimestamp::toDateTime(d->packet.basic.transmitTimestamp); + } + + QDateTime NtpReply::destinationTime() const + { + return d->destinationTime; + } + + qint64 NtpReply::roundTripDelay() const + { + return originTime().msecsTo(destinationTime()) - receiveTime().msecsTo(transmitTime()); + } + + qint64 NtpReply::localClockOffset() const + { + return (originTime().msecsTo(receiveTime()) + destinationTime().msecsTo(transmitTime())) / 2; + } + + bool NtpReply::isNull() const + { + return d->destinationTime.isNull(); + } + + NtpClient::NtpClient(QObject *parent) : QObject(parent) + { + init(QHostAddress::Any, 0); + } + + NtpClient::NtpClient(const QHostAddress &bindAddress, quint16 bindPort, QObject *parent) : QObject(parent) + { + init(bindAddress, bindPort); + } + + void NtpClient::init(const QHostAddress &bindAddress, quint16 bindPort) + { + mSocket = new QUdpSocket(this); + mSocket->bind(bindAddress, bindPort); + + connect(mSocket, SIGNAL(readyRead()), this, SLOT(readPendingDatagrams())); + } + + NtpClient::~NtpClient() + { + return; + } + + bool NtpClient::sendRequest(const QHostAddress &address, quint16 port) + { + if (mSocket->state() != QAbstractSocket::BoundState) + return false; + + /* Initialize the NTP packet. */ + NtpPacket packet; + memset(&packet, 0, sizeof(packet)); + packet.flags.mode = ClientMode; + packet.flags.versionNumber = 4; + packet.transmitTimestamp = NtpTimestamp::fromDateTime(QDateTime::currentDateTimeUtc()); + + /* Send it. */ + if (mSocket->writeDatagram(reinterpret_cast(&packet), sizeof(packet), address, port) < 0) + return false; + + return true; + } + + void NtpClient::readPendingDatagrams() + { + while (mSocket->hasPendingDatagrams()) + { + NtpFullPacket packet; + memset(&packet, 0, sizeof(packet)); + + QHostAddress address; + quint16 port; + + if (mSocket->readDatagram(reinterpret_cast(&packet), sizeof(packet), &address, &port) < (qint64) sizeof(NtpPacket)) + continue; + + QDateTime now = QDateTime::currentDateTime(); + + /* Prepare reply. */ + NtpReplyPrivate *replyPrivate = new NtpReplyPrivate(); + replyPrivate->packet = packet; + replyPrivate->destinationTime = now; + NtpReply reply(replyPrivate); + + /* Notify. */ + Q_EMIT replyReceived(address, port, reply); + } + } + +} // namespace Qv2ray::components::ntp diff --git a/src/components/ntp/QvNTPClient.hpp b/src/components/ntp/QvNTPClient.hpp new file mode 100644 index 00000000..b42d1827 --- /dev/null +++ b/src/components/ntp/QvNTPClient.hpp @@ -0,0 +1,165 @@ +/* This file from part of QNtp, a library that implements NTP protocol. + * + * Copyright (C) 2011 Alexander Fokin + * + * QNtp is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * QNtp is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License + * for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with QNtp. If not, see . */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Qv2ray::components::ntp +{ + const qint64 january_1_1900 = -2208988800000ll; + + enum NtpLeapIndicator + { + NoWarning = 0, /**< No warning. */ + LastMinute61Warning = 1, /**< Last minute has 61 seconds. */ + LastMinute59Warning = 2, /**< Last minute has 59 seconds. */ + UnsynchronizedWarning = 3, /**< Alarm condition (clock not synchronized). */ + }; + + enum NtpMode + { + ReservedMode = 0, /**< Reserved. */ + SymmetricActiveMode = 1, /**< Symmetric active. */ + SymmetricPassiveMode = 2, /**< Symmetric passive. */ + ClientMode = 3, /**< Client. */ + ServerMode = 4, /**< Server. */ + BroadcastMode = 5, /**< Broadcast. */ + ControlMode = 6, /**< NTP control message. */ + PrivateMode = 7, /**< Reserved for private use. */ + }; + + enum NtpStratum + { + UnspecifiedStratum = 0, /**< Unspecified or unavailable. */ + PrimaryStratum = 1, /**< Primary reference (e.g. radio-clock). */ + SecondaryStratumFirst = 2, /**< Secondary reference (via NTP or SNTP). */ + SecondaryStratumLast = 15, + UnsynchronizedStratum = 16, /**< Unsynchronized. */ + /* 17-255 are reserved. */ + }; + + struct NtpPacketFlags + { + unsigned char mode : 3; + unsigned char versionNumber : 3; + unsigned char leapIndicator : 2; + }; + +#pragma pack(push, 1) + + struct NtpTimestamp + { + quint32 seconds; + quint32 fraction; + static inline NtpTimestamp fromDateTime(const QDateTime &dateTime); + static inline QDateTime toDateTime(const NtpTimestamp &ntpTime); + }; + + struct NtpPacket + { + NtpPacketFlags flags; + quint8 stratum; + qint8 poll; + qint8 precision; + qint32 rootDelay; + qint32 rootDispersion; + qint8 referenceID[4]; + NtpTimestamp referenceTimestamp; + NtpTimestamp originateTimestamp; + NtpTimestamp receiveTimestamp; + NtpTimestamp transmitTimestamp; + }; + + struct NtpAuthenticationInfo + { + quint32 keyId; + quint8 messageDigest[16]; + }; + + struct NtpFullPacket + { + NtpPacket basic; + NtpAuthenticationInfo auth; + }; + +#pragma pack(pop) + + class NtpReplyPrivate : public QSharedData + { + public: + NtpFullPacket packet; + QDateTime destinationTime; + }; + + class NtpReply + { + public: + NtpReply(); + NtpReply(const NtpReply &other); + ~NtpReply(); + NtpReply &operator=(const NtpReply &other); + NtpLeapIndicator leapIndicator() const; + quint8 versionNumber() const; + NtpMode mode() const; + quint8 stratum() const; + qreal pollInterval() const; + qreal precision() const; + QDateTime referenceTime() const; + QDateTime originTime() const; + QDateTime receiveTime() const; + QDateTime transmitTime() const; + QDateTime destinationTime() const; + qint64 roundTripDelay() const; + qint64 localClockOffset() const; + bool isNull() const; + + protected: + friend class NtpClient; + NtpReply(NtpReplyPrivate *dd); + + private: + QSharedDataPointer d; + }; + + class NtpClient : public QObject + { + Q_OBJECT + + public: + NtpClient(QObject *parent = NULL); + NtpClient(const QHostAddress &bindAddress, quint16 bindPort, QObject *parent = NULL); + virtual ~NtpClient(); + bool sendRequest(const QHostAddress &address, quint16 port); + + Q_SIGNALS: + void replyReceived(const QHostAddress &address, quint16 port, const NtpReply &reply); + + private Q_SLOTS: + void readPendingDatagrams(); + + private: + void init(const QHostAddress &bindAddress, quint16 bindPort); + + QUdpSocket *mSocket; + }; +} // namespace Qv2ray::components::ntp diff --git a/src/components/plugins/QvPluginHost.cpp b/src/components/plugins/QvPluginHost.cpp index 2bb40117..6da577a8 100644 --- a/src/components/plugins/QvPluginHost.cpp +++ b/src/components/plugins/QvPluginHost.cpp @@ -55,23 +55,23 @@ namespace Qv2ray::components::plugins info.pluginLoader->unload(); continue; } - info.metadata = info.pluginInterface->GetMetadata(); - if (plugins.contains(info.metadata.InternalName)) - { - LOG(MODULE_PLUGINHOST, "Found another plugin with the same internal name: " + info.metadata.InternalName + ". Skipped") - continue; - } if (info.pluginInterface->QvPluginInterfaceVersion != QV2RAY_PLUGIN_INTERFACE_VERSION) { // The plugin was built for a not-compactable version of Qv2ray. Don't load the plugin by default. - LOG(MODULE_PLUGINHOST, info.metadata.InternalName + " is built with an older Interface, ignoring") + LOG(MODULE_PLUGINHOST, info.libraryPath + " is built with an older Interface, ignoring") QvMessageBoxWarn(nullptr, tr("Cannot load plugin"), - info.metadata.Name + " " + tr("cannot be loaded.") + NEWLINE NEWLINE + + tr("The plugin located here cannot be loaded: ") + NEWLINE + info.libraryPath + NEWLINE NEWLINE + tr("This plugin was built against an older/newer version of the Plugin Interface.") + NEWLINE + tr("Please contact the plugin provider or report the issue to Qv2ray Workgroup.")); continue; } + info.metadata = info.pluginInterface->GetMetadata(); + if (plugins.contains(info.metadata.InternalName)) + { + LOG(MODULE_PLUGINHOST, "Found another plugin with the same internal name: " + info.metadata.InternalName + ". Skipped") + continue; + } connect(plugin, SIGNAL(PluginLog(const QString &)), this, SLOT(QvPluginLog(const QString &))); connect(plugin, SIGNAL(PluginErrorMessageBox(const QString &)), this, SLOT(QvPluginMessageBox(const QString &))); LOG(MODULE_PLUGINHOST, "Loaded plugin: \"" + info.metadata.Name + "\" made by: \"" + info.metadata.Author + "\"") @@ -120,6 +120,7 @@ namespace Qv2ray::components::plugins { // Load plugin if it haven't been loaded. InitializePlugin(internalName); + QvMessageBoxInfo(nullptr, tr("Enabling a plugin"), tr("The plugin will become fully functional after restarting Qv2ray.")); } } @@ -241,14 +242,14 @@ namespace Qv2ray::components::plugins return data; } - const QMultiHash> QvPluginHost::TryDeserializeShareLink(const QString &sharelink, // - QString *prefix, // + const QList> QvPluginHost::TryDeserializeShareLink(const QString &sharelink, // + QString *aliasPrefix, // QString *errMessage, // QString *newGroupName, // bool *status) const { Q_UNUSED(newGroupName) - QMultiHash> data; + QList> data; *status = true; for (const auto &plugin : plugins) { @@ -262,9 +263,9 @@ namespace Qv2ray::components::plugins } if (thisPluginCanHandle) { - auto [protocol, outboundSettings] = serializer->DeserializeOutbound(sharelink, prefix, errMessage); + const auto &[protocol, outboundSettings] = serializer->DeserializeOutbound(sharelink, aliasPrefix, errMessage); *status = *status && errMessage->isEmpty(); - data.insert(*prefix, { protocol, outboundSettings }); + data << std::tuple{ *aliasPrefix, protocol, outboundSettings }; } } } @@ -278,7 +279,7 @@ namespace Qv2ray::components::plugins if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR)) { auto serializer = plugin.pluginInterface->GetSerializer(); - if (serializer->OutboundProtocols().contains(protocol)) + if (serializer && serializer->OutboundProtocols().contains(protocol)) { auto info = serializer->GetOutboundInfo(protocol, o); *status = true; @@ -300,7 +301,7 @@ namespace Qv2ray::components::plugins if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR)) { auto serializer = plugin.pluginInterface->GetSerializer(); - if (serializer->OutboundProtocols().contains(protocol)) + if (serializer && serializer->OutboundProtocols().contains(protocol)) { auto link = serializer->SerializeOutbound(protocol, alias, groupName, outboundSettings); *status = true; @@ -311,18 +312,31 @@ namespace Qv2ray::components::plugins return ""; } - const QMap> QvPluginHost::GetPluginKernels() const + const std::unique_ptr QvPluginHost::CreatePluginKernel(const QString &pluginInternalName) const { - QMap> kernels; + if (!plugins.contains(pluginInternalName)) + return nullptr; + const auto &plugin = plugins.value(pluginInternalName); + if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_KERNEL)) + { + return plugin.pluginInterface->CreateKernel(); + } + return nullptr; + } + + const QMap> QvPluginHost::GetPluginKernels() const + { + QMap> kernels; for (const auto &plugin : plugins) { if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_KERNEL)) { - auto kern = plugin.pluginInterface->GetKernel(); - for (const auto &cap : kern->KernelOutboundCapabilities()) + QStringList outbounds; + for (const auto &info : plugin.metadata.KernelOutboundCapabilities) { - kernels.insert(cap.protocol, kern); + outbounds << info.protocol; } + kernels.insert(plugin.metadata.InternalName, outbounds); } } return kernels; diff --git a/src/components/plugins/QvPluginHost.hpp b/src/components/plugins/QvPluginHost.hpp index a0c7a981..9bdc5fcb 100644 --- a/src/components/plugins/QvPluginHost.hpp +++ b/src/components/plugins/QvPluginHost.hpp @@ -1,5 +1,5 @@ #pragma once -#include "components/plugins/interface/QvPluginInterface.hpp" +#include "src/plugin-interface/QvPluginInterface.hpp" #include #include @@ -57,10 +57,11 @@ namespace Qv2ray::components::plugins { return plugins.value(internalName).metadata; } - const QMap> GetPluginKernels() const; + const QMap> GetPluginKernels() const; + const std::unique_ptr CreatePluginKernel(const QString &pluginInternalName) const; // - const QMultiHash> TryDeserializeShareLink(const QString &sharelink, // - QString *prefix, // + const QList> TryDeserializeShareLink(const QString &sharelink, // + QString *aliasPrefix, // QString *errMessage, // QString *newGroupName, // bool *status) const; diff --git a/src/components/plugins/interface b/src/components/plugins/interface deleted file mode 160000 index d37c7ea9..00000000 --- a/src/components/plugins/interface +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d37c7ea9459956dc459610e98b821d4a790cb6e8 diff --git a/src/components/plugins/toolbar/QvToolbar.cpp b/src/components/plugins/toolbar/QvToolbar.cpp deleted file mode 100644 index 967bfd22..00000000 --- a/src/components/plugins/toolbar/QvToolbar.cpp +++ /dev/null @@ -1,179 +0,0 @@ -#include "components/plugins/toolbar/QvToolbar.hpp" - -#include "common/QvHelpers.hpp" -#include "core/handler/ConfigHandler.hpp" - -#include - -namespace Qv2ray::components::plugins -{ - namespace Toolbar - { - void StopProcessingPlugins() - { -#ifdef Q_OS_LINUX - _linux::StopMessageQThread(); -#endif -#ifdef Q_OS_WIN - _win::KillNamedPipeThread(); -#endif - } - - /// Public Function - CALL ONLY ONCE - - /// To start processing plugins' command. - void StartProcessingPlugins() - { -#ifdef Q_OS_LINUX - _linux::StartMessageQThread(); -#endif -#ifdef Q_OS_WIN - _win::StartNamedPipeThread(); -#endif - } - QString GetAnswerToRequest(const QString &pchRequest) - { - auto req = pchRequest.trimmed(); - QString reply = "{}"; - - if (req == "START") - { - emit ConnectionManager->RestartConnection(); - } - else if (req == "STOP") - { - emit ConnectionManager->StopConnection(); - } - else if (req == "RESTART") - { - emit ConnectionManager->RestartConnection(); - } - - auto BarConfig = GlobalConfig.toolBarConfig; - - for (auto i = 0; i < BarConfig.Pages.size(); i++) - { - for (auto j = 0; j < BarConfig.Pages[i].Lines.size(); j++) - { -#define CL BarConfig.Pages[i].Lines[j] - - switch (CL.ContentType) - { - case 0: - { - // Custom Text - // We do nothing... - break; - } - - case 101: - { - // Current Time - CL.Message = QTime().currentTime().toString("hh:mm:ss"); - break; - } - - case 102: - { - // Current Date - CL.Message = QDate().currentDate().toString("yyyy-MM-dd"); - break; - } - - case 103: - { - // Current Qv2ray Version - CL.Message = QV2RAY_VERSION_STRING; - break; - } - - case 104: - { - // Current Connection Name - CL.Message = GetDisplayName(KernelInstance->CurrentConnection()); - break; - } - - case 105: - { - // Current Connection Status - CL.Message = KernelInstance->CurrentConnection() == NullConnectionId ? QObject::tr("Not connected") : - QObject::tr("Connected"); - break; - } - - // case 201: - //{ - // // Total upload speed; - // CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount())) + "/s"; - // break; - //} - // - // case 202: - //{ - // // Total download speed; - // CL.Message = FormatBytes(vinstance->getAllSpeedDown()) + "/s"; - // break; - //} - // - // case 203: - //{ - // // Upload speed for tag - // CL.Message = FormatBytes(vinstance->getTagSpeedUp(CL.Message)) + "/s"; - // break; - //} - // - // case 204: - //{ - // // Download speed for tag - // CL.Message = FormatBytes(vinstance->getTagSpeedDown(CL.Message)) + "/s"; - // break; - //} - - case 301: - { - // Total Upload - CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount(KernelInstance->CurrentConnection()))); - break; - } - - case 302: - { - // Total download - CL.Message = FormatBytes(get<1>(GetConnectionUsageAmount(KernelInstance->CurrentConnection()))); - break; - } - - // case 303: - //{ - // // Upload for tag - // CL.Message = FormatBytes(vinstance->getTagDataUp(CL.Message)); - // break; - //} - // - // case 304: - //{ - // // Download for tag - // CL.Message = FormatBytes(vinstance->getTagDataDown(CL.Message)); - // break; - //} - - case 305: - { - // Connection latency - CL.Message = QSTRN(GetConnectionLatency(KernelInstance->CurrentConnection())) + " ms"; - break; - } - default: - { - CL.Message = "Not Implemented"; - break; - } - } - } - } -#undef CL - reply = StructToJsonString(BarConfig); - return reply; - } - } // namespace Toolbar -} // namespace Qv2ray::components::plugins diff --git a/src/components/plugins/toolbar/QvToolbar.hpp b/src/components/plugins/toolbar/QvToolbar.hpp deleted file mode 100644 index dac1dafa..00000000 --- a/src/components/plugins/toolbar/QvToolbar.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "base/Qv2rayBase.hpp" -#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX "Qv2ray_NetSpeed_Widget_LocalSocket" -#define QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_WIN "\\\\.\\pipe\\qv2ray_desktop_netspeed_toolbar_pipe" - -namespace Qv2ray::components::plugins -{ - namespace Toolbar - { - /// NO NOT CHANGE THE ORDER - static const QMap NetSpeedPluginMessages{ - { 0, QObject::tr("Custom Text") }, - // Current Status - { 101, QObject::tr("Current Time") }, - { 102, QObject::tr("Current Date") }, - { 103, QObject::tr("Current Qv2ray Version") }, - { 104, QObject::tr("Current Connection Name") }, - { 105, QObject::tr("Current Connection Status") }, - // Speeds - { 201, QObject::tr("Total Upload Speed") }, - { 202, QObject::tr("Total Download Speed") }, - //{ 203, QObject::tr("Upload Speed for Specific Tag") }, - //{ 204, QObject::tr("Download Speed for Specific Tag") }, - // Datas - { 301, QObject::tr("Total Uploaded Data") }, - { 302, QObject::tr("Total Downloaded Data") }, - //{ 303, QObject::tr("Uploaded Data for Specific Tag") }, - //{ 304, QObject::tr("Downloaded Data for Specific Tag") } - { 305, QObject::tr("Current Connection Latency") } // - }; - void StartProcessingPlugins(); - void StopProcessingPlugins(); -#ifdef Q_OS_WIN - namespace _win - { - void StartNamedPipeThread(); - void KillNamedPipeThread(); - } // namespace _win -#endif -#ifdef Q_OS_LINUX - namespace _linux - { - // This function is called within a QThread - // Actually it should the entrypoint of a thread. - void StartMessageQThread(); - void StopMessageQThread(); - - } // namespace _linux -#endif - - QString GetAnswerToRequest(const QString &pchRequest); - } // namespace Toolbar -} // namespace Qv2ray::components::plugins - -using namespace Qv2ray::components; -using namespace Qv2ray::components::plugins::Toolbar; diff --git a/src/components/plugins/toolbar/QvToolbar_linux.cpp b/src/components/plugins/toolbar/QvToolbar_linux.cpp deleted file mode 100644 index 9fdd2a4b..00000000 --- a/src/components/plugins/toolbar/QvToolbar_linux.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#ifdef Q_OS_LINUX - #include "common/QvHelpers.hpp" - #include "components/plugins/toolbar/QvToolbar.hpp" - - #include - #include -namespace Qv2ray::components::plugins::Toolbar -{ - namespace _linux - { - static QThread *linuxWorkerThread; - static QLocalServer *server; - static volatile bool isExiting = false; - - void qobject_proxy() - { - QLocalSocket *socket = server->nextPendingConnection(); - - if (!socket->waitForConnected() || !socket->waitForReadyRead()) - return; - - try - { - while (!isExiting && socket->isOpen() && socket->isValid() && socket->waitForReadyRead()) - { - // CANNOT PROPERLY READ... - // Temp-ly fixed (but why and how?) - auto in = QString(socket->readAll()); - - if (!isExiting && !in.isEmpty()) - { - auto out = GetAnswerToRequest(in); - // - socket->write(out.toUtf8()); - socket->flush(); - } - else - { - QThread::msleep(200); - } - } - } - catch (...) - { - LOG(MODULE_PLUGINHOST, "Closing a broken socket.") - } - } - void DataMessageQThread() - { - server = new QLocalServer(); - // BUG Sometimes failed to listen due to improper close of last - // session. - bool listening = server->listen(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX); - - while (!isExiting && !listening) - { - QThread::msleep(500); - listening = server->listen(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_LINUX); - } - - bool timeOut = false; - server->setSocketOptions(QLocalServer::WorldAccessOption); - QObject::connect(server, &QLocalServer::newConnection, &qobject_proxy); - - while (!isExiting) - { - bool result = server->waitForNewConnection(5000, &timeOut); - DEBUG(MODULE_PLUGINHOST, "Plugin thread listening failed: " + server->errorString()) - DEBUG(MODULE_PLUGINHOST, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false")) - } - - server->close(); - delete server; - } - void StartMessageQThread() - { - linuxWorkerThread = QThread::create(_linux::DataMessageQThread); - linuxWorkerThread->start(); - } - - void StopMessageQThread() - { - isExiting = true; - - if (linuxWorkerThread->isRunning()) - { - LOG(MODULE_PLUGINHOST, "Waiting for linuxWorkerThread to stop.") - linuxWorkerThread->wait(); - } - - delete _linux::linuxWorkerThread; - } - } // namespace _linux -} // namespace Qv2ray::components::plugins::Toolbar -#endif diff --git a/src/components/plugins/toolbar/QvToolbar_win.cpp b/src/components/plugins/toolbar/QvToolbar_win.cpp deleted file mode 100644 index 495a8660..00000000 --- a/src/components/plugins/toolbar/QvToolbar_win.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#ifdef Q_OS_WIN - #include "common/QvHelpers.hpp" - #include "components/plugins/toolbar/QvToolbar.hpp" - - #include -namespace Qv2ray::components::plugins::Toolbar -{ - namespace _win - { - // Private Headers - constexpr int BUFSIZE = 10240; - DWORD WINAPI NamedPipeMasterThread(LPVOID lpvParam); - DWORD WINAPI InstanceThread(LPVOID); - static LPVOID ThreadHandle; - static bool isExiting = false; - - // - void KillNamedPipeThread() - { - isExiting = true; - TerminateThread(ThreadHandle, 0); - } - // - void StartNamedPipeThread() - { - auto hThread = CreateThread(nullptr, 0, NamedPipeMasterThread, nullptr, 0, nullptr); - - if (hThread == nullptr) - { - LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError())) - return; - } - else - CloseHandle(hThread); - } - - DWORD WINAPI NamedPipeMasterThread(LPVOID lpvParam) - { - Q_UNUSED(lpvParam) - BOOL fConnected = FALSE; - DWORD dwThreadId = 0; - HANDLE hPipe = INVALID_HANDLE_VALUE; - auto lpszPipename = QString(QV2RAY_NETSPEED_PLUGIN_PIPE_NAME_WIN).toStdWString(); - - while (!isExiting) - { - // printf("Pipe Server: Main thread awaiting client connection - // on %s\n", lpszPipename.c_str()); - hPipe = CreateNamedPipe(lpszPipename.c_str(), PIPE_ACCESS_DUPLEX, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, BUFSIZE, BUFSIZE, 0, nullptr); - - if (hPipe == INVALID_HANDLE_VALUE) - { - LOG(MODULE_PLUGINHOST, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError())) - return static_cast(-1); - } - - fConnected = ConnectNamedPipe(hPipe, nullptr) ? true : (GetLastError() == ERROR_PIPE_CONNECTED); - - if (fConnected) - { - LOG(MODULE_PLUGINHOST, "Client connected, creating a processing thread") - ThreadHandle = CreateThread(nullptr, 0, InstanceThread, hPipe, 0, &dwThreadId); - - if (ThreadHandle == nullptr) - { - LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError())) - return static_cast(-1); - } - else - CloseHandle(ThreadHandle); - } - else - CloseHandle(hPipe); - } - - return 0; - } - - DWORD WINAPI InstanceThread(LPVOID lpvParam) - { - DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0; - BOOL fSuccess = false; - HANDLE hPipe = static_cast(lpvParam); - TCHAR pchRequest[BUFSIZE] = { 0 }; - - while (!isExiting) - { - fSuccess = ReadFile(hPipe, pchRequest, BUFSIZE * sizeof(TCHAR), &cbBytesRead, nullptr); - - if (!fSuccess || cbBytesRead == 0) - { - if (GetLastError() == ERROR_BROKEN_PIPE) - { - LOG(MODULE_PLUGINHOST, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError())) - } - else - { - LOG(MODULE_PLUGINHOST, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError())) - } - - break; - } - - auto req = QString::fromStdWString(pchRequest); - QString replyQString = "{}"; - - if (!isExiting) - { - replyQString = GetAnswerToRequest(req); - // - // REPLY as std::string - std::string pchReply = replyQString.toUtf8().constData(); - cbReplyBytes = static_cast(pchReply.length() + 1) * sizeof(CHAR); - // cbReplyBytes = static_cast(replyQString.length() + - // 1) * sizeof(TCHAR); - // - fSuccess = WriteFile(hPipe, pchReply.c_str(), cbReplyBytes, &cbWritten, nullptr); - - if (!fSuccess || cbReplyBytes != cbWritten) - { - LOG(MODULE_PLUGINHOST, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError())) - break; - } - } - } - - FlushFileBuffers(hPipe); - DisconnectNamedPipe(hPipe); - CloseHandle(hPipe); - return 1; - } - } // namespace _win -} // namespace Qv2ray::components::plugins::Toolbar -#endif diff --git a/src/components/port/QvPortDetector.cpp b/src/components/port/QvPortDetector.cpp new file mode 100644 index 00000000..b312131a --- /dev/null +++ b/src/components/port/QvPortDetector.cpp @@ -0,0 +1,19 @@ +#include "QvPortDetector.hpp" + +#include +#include + +namespace Qv2ray::components::port +{ + bool CheckTCPPortStatus(const QString &addr, int port) + { + QTcpServer server; + QHostAddress address(addr); + if (address == QHostAddress::AnyIPv4 || address == QHostAddress::AnyIPv6) + { + address = QHostAddress::Any; + } + return server.listen(address, port); + } + +} // namespace Qv2ray::components::port diff --git a/src/components/port/QvPortDetector.hpp b/src/components/port/QvPortDetector.hpp new file mode 100644 index 00000000..0091970e --- /dev/null +++ b/src/components/port/QvPortDetector.hpp @@ -0,0 +1,11 @@ +#pragma once +#include +#include +namespace Qv2ray::components::port +{ + bool CheckTCPPortStatus(const QString &addr, int port); + inline bool CheckTCPPortStatus(QPair config) + { + return CheckTCPPortStatus(config.first, config.second); + } +} // namespace Qv2ray::components::port diff --git a/src/components/proxy/QvProxyConfigurator.cpp b/src/components/proxy/QvProxyConfigurator.cpp index 3cb96544..06da4343 100644 --- a/src/components/proxy/QvProxyConfigurator.cpp +++ b/src/components/proxy/QvProxyConfigurator.cpp @@ -1,5 +1,6 @@ #include "QvProxyConfigurator.hpp" +#include "base/Qv2rayBase.hpp" #include "common/QvHelpers.hpp" #include "components/plugins/QvPluginHost.hpp" #ifdef Q_OS_WIN @@ -10,6 +11,7 @@ namespace Qv2ray::components::proxy { + using ProcessArgument = QPair; #ifdef Q_OS_MACOS QStringList macOSgetNetworkServices() { @@ -195,7 +197,7 @@ namespace Qv2ray::components::proxy bool hasHTTP = (httpPort != 0); bool hasSOCKS = (socksPort != 0); - if (!(hasHTTP || hasSOCKS)) + if (!hasHTTP && !hasSOCKS) { LOG(MODULE_PROXY, "Nothing?") return; @@ -212,7 +214,8 @@ namespace Qv2ray::components::proxy } #ifdef Q_OS_WIN - QString __a = (hasHTTP ? "http://" : "socks5://") + address + ":" + QSTRN(hasHTTP ? httpPort : socksPort); + const auto scheme = (hasHTTP ? "" : "socks5://"); + QString __a = scheme + address + ":" + QSTRN(hasHTTP ? httpPort : socksPort); LOG(MODULE_PROXY, "Windows proxy string: " + __a) auto proxyStrW = new WCHAR[__a.length() + 1]; @@ -227,82 +230,130 @@ namespace Qv2ray::components::proxy __QueryProxyOptions(); #elif defined(Q_OS_LINUX) - QStringList actions; - actions << QString("gsettings set org.gnome.system.proxy mode '%1'").arg("manual"); + QList actions; + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy", "mode", "manual" } }; + // bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE"; + bool isDDE = !isKDE && qEnvironmentVariable("XDG_CURRENT_DESKTOP").toLower() == "deepin"; const auto configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); - if (isKDE) - { - LOG(MODULE_PROXY, "KDE detected") - actions << QString("kwriteconfig5 --file " + configPath + "/kioslaverc --group \"Proxy Settings\" --key ProxyType 1"); - } + + // + // Configure HTTP Proxies for HTTP, FTP and HTTPS if (hasHTTP) { - actions << QString("gsettings set org.gnome.system.proxy.http host '%1'").arg(address); - actions << QString("gsettings set org.gnome.system.proxy.http port %1").arg(httpPort); - // - actions << QString("gsettings set org.gnome.system.proxy.https host '%1'").arg(address); - actions << QString("gsettings set org.gnome.system.proxy.https port %1").arg(httpPort); - if (isKDE) + // iterate over protocols... + for (const auto &protocol : QStringList{ "http", "ftp", "https" }) { - // FTP here should be scheme: ftp:// - for (auto protocol : { "http", "ftp", "https" }) + // for GNOME: { - auto str = - QString("kwriteconfig5 --file " + configPath + "/kioslaverc --group \"Proxy Settings\" --key %1Proxy \"http://%2 %3\"") - .arg(protocol) - .arg(address) - .arg(QSTRN(httpPort)); - actions << str; + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy." + protocol, "host", address } }; + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy." + protocol, "port", QSTRN(httpPort) } }; + } + + // for KDE: + if (isKDE) + { + actions << ProcessArgument{ "kwriteconfig5", + { "--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", protocol + "Proxy", // + "http://" + address + " " + QSTRN(httpPort) } }; } } } + // Configure SOCKS5 Proxies if (hasSOCKS) { - actions << QString("gsettings set org.gnome.system.proxy.socks host '%1'").arg(address); - actions << QString("gsettings set org.gnome.system.proxy.socks port %1").arg(socksPort); - if (isKDE) + // for GNOME: { - actions << QString("kwriteconfig5 --file " + configPath + - "/kioslaverc --group \"Proxy Settings\" --key socksProxy \"socks://%1 %2\"") - .arg(address) - .arg(QSTRN(socksPort)); + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy.socks", "host", address } }; + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy.socks", "port", QSTRN(socksPort) } }; + + // for KDE: + if (isKDE) + { + actions << ProcessArgument{ "kwriteconfig5", + { "--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "socksProxy", // + "socks://" + address + " " + QSTRN(socksPort) } }; + } } } + // Setting Proxy Mode to Manual + { + // for GNOME: + { + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy", "mode", "manual" } }; + } + // for KDE: + if (isKDE) + { + actions << ProcessArgument{ "kwriteconfig5", + { "--file", configPath + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "ProxyType", "1" } }; + } + } + // Execute them all! + // // note: do not use std::all_of / any_of / none_of, // because those are short-circuit and cannot guarantee atomicity. - auto result = std::count_if(actions.cbegin(), actions.cend(), [](const QString &action) { - DEBUG(MODULE_PROXY, action) - return QProcess::execute(action) == QProcess::NormalExit; - }) == actions.size(); - - if (!result) + QList results; + for (const auto &action : actions) { - LOG(MODULE_PROXY, "There was something wrong when setting proxies.") - LOG(MODULE_PROXY, "It may happen if you are using KDE with no gsettings support.") + // execute and get the code + const auto returnCode = QProcess::execute(action.first, action.second); + // print out the commands and result codes + DEBUG(MODULE_PROXY, QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))) + // give the code back + results << (returnCode == QProcess::NormalExit); } - Q_UNUSED(result); + if (results.count(true) != actions.size()) + { + LOG(MODULE_PROXY, "Something wrong when setting proxies.") + } + + // Post-Actions for HTTP on Deepin Desktop Environment. + if (isDDE && hasHTTP) + { + if (!RuntimeConfig.deepinHorribleProxyHint) + { + RuntimeConfig.deepinHorribleProxyHint = true; + const auto deepinWarnTitle = QObject::tr("Deepin Detected"); + const auto deepinWarnMessage = + QObject::tr("Deepin plays smart and sets you the wrong HTTPS_PROXY, FTP_PROXY environment variable.") + NEWLINE + // + QObject::tr("The origin scheme http is wrongly replaced by https and ftp, causing the problem.") + NEWLINE + // + QObject::tr("Qv2ray cannot help you change them back. Please don't blame us if things go wrong."); // + QvMessageBoxWarn(nullptr, deepinWarnTitle, deepinWarnMessage); + } + + // set them back! - NOPE. setenv only works within your little program. + // const auto httpProxyURL = QString("http://%1:%2").arg(address, QSTRN(httpPort)).toStdString(); + // setenv("https_proxy", httpProxyURL.c_str(), true); + // setenv("ftp_proxy", httpProxyURL.c_str(), true); + } #else - for (auto service : macOSgetNetworkServices()) + for (const auto &service : macOSgetNetworkServices()) { LOG(MODULE_PROXY, "Setting proxy for interface: " + service) if (hasHTTP) { - QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " on"); - QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " on"); - QProcess::execute("/usr/sbin/networksetup -setwebproxy " + service + " " + address + " " + QSTRN(httpPort)); - QProcess::execute("/usr/sbin/networksetup -setsecurewebproxy " + service + " " + address + " " + QSTRN(httpPort)); + QProcess::execute("/usr/sbin/networksetup", { "-setwebproxystate", service, "on" }); + QProcess::execute("/usr/sbin/networksetup", { "-setsecurewebproxystate", service, "on" }); + QProcess::execute("/usr/sbin/networksetup", { "-setwebproxy", service, address, QSTRN(httpPort) }); + QProcess::execute("/usr/sbin/networksetup", { "-setsecurewebproxy", service, address, QSTRN(httpPort) }); } if (hasSOCKS) { - QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " on"); - QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort)); + QProcess::execute("/usr/sbin/networksetup", { "-setsocksfirewallproxystate", service, "on" }); + QProcess::execute("/usr/sbin/networksetup", { "-setsocksfirewallproxy", service, address, QSTRN(socksPort) }); } } @@ -314,13 +365,13 @@ namespace Qv2ray::components::proxy portSettings.insert(Events::SystemProxy::SystemProxyType::SystemProxy_HTTP, httpPort); if (hasSOCKS) portSettings.insert(Events::SystemProxy::SystemProxyType::SystemProxy_SOCKS, socksPort); - PluginHost->Send_SystemProxyEvent( - Events::SystemProxy::EventObject{ portSettings, Events::SystemProxy::SystemProxyStateType::SystemProxyState_SetProxy }); + PluginHost->Send_SystemProxyEvent({ portSettings, Events::SystemProxy::SystemProxyStateType::SetProxy }); } void ClearSystemProxy() { LOG(MODULE_PROXY, "Clearing System Proxy") + #ifdef Q_OS_WIN LOG(MODULE_PROXY, "Cleaning system proxy settings.") INTERNET_PER_CONN_OPTION_LIST list; @@ -351,35 +402,49 @@ namespace Qv2ray::components::proxy InternetSetOption(nullptr, INTERNET_OPTION_SETTINGS_CHANGED, nullptr, 0); InternetSetOption(nullptr, INTERNET_OPTION_REFRESH, nullptr, 0); #elif defined(Q_OS_LINUX) - if (qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE") + QList actions; + const bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE"; + const auto configRoot = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation); + + // Setting System Proxy Mode to: None { - QProcess::execute("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key ProxyType 0"); - for (auto protocol : { "http", "ftp", "https" }) + // for GNOME: { - auto str = QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key %1Proxy ''") - .arg(protocol); - QProcess::execute(str); + actions << ProcessArgument{ "gsettings", { "set", "org.gnome.system.proxy", "mode", "none" } }; + } + + // for KDE: + if (isKDE) + { + actions << ProcessArgument{ "kwriteconfig5", + { "--file", configRoot + "/kioslaverc", // + "--group", "Proxy Settings", // + "--key", "ProxyType", "0" } }; } - QProcess::execute("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key socksProxy ''"); } - QProcess::execute("gsettings set org.gnome.system.proxy mode 'none'"); + + // Execute the Actions + for (const auto &action : actions) + { + // execute and get the code + const auto returnCode = QProcess::execute(action.first, action.second); + // print out the commands and result codes + DEBUG(MODULE_PROXY, QString("[%1] Program: %2, Args: %3").arg(returnCode).arg(action.first).arg(action.second.join(";"))) + } + #else - for (auto service : macOSgetNetworkServices()) + for (const auto &service : macOSgetNetworkServices()) { LOG(MODULE_PROXY, "Clearing proxy for interface: " + service) - QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " off"); - QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " off"); - QProcess::execute("/usr/sbin/networksetup -setsecurewebproxystate " + service + " off"); - QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " off"); + QProcess::execute("/usr/sbin/networksetup", { "-setautoproxystate", service, "off" }); + QProcess::execute("/usr/sbin/networksetup", { "-setwebproxystate", service, "off" }); + QProcess::execute("/usr/sbin/networksetup", { "-setsecurewebproxystate", service, "off" }); + QProcess::execute("/usr/sbin/networksetup", { "-setsocksfirewallproxystate", service, "off" }); } #endif // // Trigger plugin events - PluginHost->Send_SystemProxyEvent( - Events::SystemProxy::EventObject{ {}, Events::SystemProxy::SystemProxyStateType::SystemProxyState_ClearProxy }); + PluginHost->Send_SystemProxyEvent(Events::SystemProxy::EventObject{ {}, Events::SystemProxy::SystemProxyStateType::ClearProxy }); } } // namespace Qv2ray::components::proxy diff --git a/src/components/route/RouteSchemeIO.hpp b/src/components/route/RouteSchemeIO.hpp index 3454061a..823407e4 100644 --- a/src/components/route/RouteSchemeIO.hpp +++ b/src/components/route/RouteSchemeIO.hpp @@ -1,13 +1,13 @@ #pragma once -#include "base/models/QvSettingsObject.hpp" +#include "base/Qv2rayBase.hpp" namespace Qv2ray::components::route { - const inline Qv2ray::base::config::Qv2rayRouteConfig emptyScheme; + const inline QvConfig_Route emptyScheme; /** * @brief The Qv2rayRouteScheme struct * @author DuckSoft */ - struct Qv2rayRouteScheme : config::Qv2rayRouteConfig + struct Qv2rayRouteScheme : QvConfig_Route { /** * @brief the name of the scheme. @@ -26,7 +26,7 @@ namespace Qv2ray::components::route QString description; // M: all these fields are mandatory - XTOSTRUCT(M(name, author, description, domainStrategy, domains, ips)); + JSONSTRUCT_REGISTER(Qv2rayRouteScheme, F(name, author, description), B(QvConfig_Route)); }; } // namespace Qv2ray::components::route diff --git a/src/components/route/presets/RouteScheme_V2rayN.hpp b/src/components/route/presets/RouteScheme_V2rayN.hpp index 447c4074..ef60de76 100644 --- a/src/components/route/presets/RouteScheme_V2rayN.hpp +++ b/src/components/route/presets/RouteScheme_V2rayN.hpp @@ -141,9 +141,9 @@ namespace Qv2ray::components::route::presets::v2rayN "domain:samsungdm.com" }; const inline QList DomainsBlock{ "geosite:category-ads-all" }; - const inline QList DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam", - "geosite:telegram", "geosite:tumblr", "domain:naver.com", "geosite:bbc", - "domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org" }; + const inline QList DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam", + "geosite:telegram", "geosite:tumblr", "domain:naver.com", "geosite:bbc", + "domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org" }; const inline QList IPsProxy{ "91.108.4.0/22", "91.108.8.0/22", "91.108.12.0/22", "91.108.20.0/22", "91.108.36.0/23", @@ -153,6 +153,6 @@ namespace Qv2ray::components::route::presets::v2rayN }; const inline QList IPsBlock{}; const inline QList IPsDirect{}; - const inline Qv2ray::base::config::Qv2rayRouteConfig v2rayNScheme({ DomainsDirect, DomainsBlock, DomainsProxy }, - { IPsDirect, IPsBlock, IPsProxy }, "AsIs"); + const inline Qv2ray::base::config::QvConfig_Route v2rayNScheme({ DomainsDirect, DomainsBlock, DomainsProxy }, + { IPsDirect, IPsBlock, IPsProxy }, "AsIs"); } // namespace Qv2ray::components::route::presets::v2rayN diff --git a/src/components/update/UpdateChecker.cpp b/src/components/update/UpdateChecker.cpp index 9183e1e3..a5c02d96 100644 --- a/src/components/update/UpdateChecker.cpp +++ b/src/components/update/UpdateChecker.cpp @@ -6,6 +6,7 @@ #include "common/QvHelpers.hpp" #include "core/settings/SettingsBackend.hpp" +#include #include const inline QMap UpdateChannelLink // @@ -18,39 +19,44 @@ namespace Qv2ray::components { QvUpdateChecker::QvUpdateChecker(QObject *parent) : QObject(parent) { - requestHelper = new QvHttpRequestHelper(this); - connect(requestHelper, &QvHttpRequestHelper::OnRequestFinished, this, &QvUpdateChecker::VersionUpdate); + requestHelper = new NetworkRequestHelper(this); } + QvUpdateChecker::~QvUpdateChecker() { } + void QvUpdateChecker::CheckUpdate() { #ifndef DISABLE_AUTO_UPDATE - auto updateChannel = GlobalConfig.updateConfig.updateChannel; + if (QFile(QV2RAY_CONFIG_DIR + "QV2RAY_FEATURE_DISABLE_AUTO_UPDATE").exists()) + return; + const auto &updateChannel = GlobalConfig.updateConfig.updateChannel; LOG(MODULE_NETWORK, "Start checking update for channel ID: " + QSTRN(updateChannel)) - requestHelper->AsyncGet(UpdateChannelLink[updateChannel]); + requestHelper->AsyncHttpGet(UpdateChannelLink[updateChannel], &QvUpdateChecker::VersionUpdate); #endif } - void QvUpdateChecker::VersionUpdate(QByteArray &data) + + void QvUpdateChecker::VersionUpdate(const QByteArray &data) { // Version update handler. - auto doc = QJsonDocument::fromJson(data); - QJsonObject root = doc.isArray() ? doc.array().first().toObject() : doc.object(); + const auto doc = QJsonDocument::fromJson(data); + const auto root = doc.isArray() ? doc.array().first().toObject() : doc.object(); if (root.isEmpty()) return; - // + const auto newVersionStr = root["tag_name"].toString("v").mid(1); const auto currentVersionStr = QString(QV2RAY_VERSION_STRING); const auto ignoredVersionStr = GlobalConfig.updateConfig.ignoredVersion.isEmpty() ? "0.0.0" : GlobalConfig.updateConfig.ignoredVersion; // - auto newVersion = semver::version::from_string(newVersionStr.toStdString()); - auto currentVersion = semver::version::from_string(currentVersionStr.toStdString()); - auto ignoredVersion = semver::version::from_string(ignoredVersionStr.toStdString()); + const auto newVersion = semver::version::from_string(newVersionStr.toStdString()); + const auto currentVersion = semver::version::from_string(currentVersionStr.toStdString()); + const auto ignoredVersion = semver::version::from_string(ignoredVersionStr.toStdString()); // - LOG(MODULE_UPDATE, "Received update info, Latest: " + newVersionStr + // - " Current: " + currentVersionStr + // - " Ignored: " + ignoredVersionStr) + LOG(MODULE_UPDATE, QString("Received update info:") + NEWLINE + // + " --> Latest: " + newVersionStr + NEWLINE + // + " --> Current: " + currentVersionStr + NEWLINE + // + " --> Ignored: " + ignoredVersionStr) // If the version is newer than us. // And new version is newer than the ignored version. if (newVersion > currentVersion && newVersion > ignoredVersion) @@ -62,8 +68,7 @@ namespace Qv2ray::components return; } const auto link = root["html_url"].toString(""); - auto result = QvMessageBoxAsk(nullptr, // - tr("Qv2ray Update"), + auto result = QvMessageBoxAsk(nullptr, tr("Qv2ray Update"), tr("A new version of Qv2ray has been found:") + // "v" + newVersionStr + NEWLINE + NEWLINE + // name + NEWLINE "------------" NEWLINE + // diff --git a/src/components/update/UpdateChecker.hpp b/src/components/update/UpdateChecker.hpp index 982be3e0..4efa4deb 100644 --- a/src/components/update/UpdateChecker.hpp +++ b/src/components/update/UpdateChecker.hpp @@ -1,9 +1,12 @@ #pragma once + #include -namespace Qv2ray::common + +namespace Qv2ray::common::network { - class QvHttpRequestHelper; + class NetworkRequestHelper; } + namespace Qv2ray::components { struct QvUpdateInfo @@ -26,9 +29,8 @@ namespace Qv2ray::components void OnCheckUpdateCompleted(bool hasUpdate, const QvUpdateInfo &updateInfo); private: - void VersionUpdate(QByteArray &data); - common::QvHttpRequestHelper *requestHelper; + Qv2ray::common::network::NetworkRequestHelper *requestHelper; + void static VersionUpdate(const QByteArray &data); }; - inline QvUpdateChecker UpdateChecker; } // namespace Qv2ray::components using namespace Qv2ray::components; diff --git a/src/core/CoreSafeTypes.hpp b/src/core/CoreSafeTypes.hpp deleted file mode 100644 index 283caa91..00000000 --- a/src/core/CoreSafeTypes.hpp +++ /dev/null @@ -1,130 +0,0 @@ -#pragma once - -#include "base/models/QvConfigIdentifier.hpp" - -#include -#include -#include - -namespace Qv2ray::core -{ - static const inline auto QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER = QObject::tr("(Complex config)"); - template - class IDType final - { - public: - explicit IDType() : m_id("null") - { - } - explicit IDType(const QString &id) : m_id(id) - { - } - friend bool operator==(const IDType &lhs, const IDType &rhs) - { - return lhs.m_id == rhs.m_id; - } - friend bool operator!=(const IDType &lhs, const IDType &rhs) - { - return lhs.toString() != rhs.toString(); - } - const QString &toString() const - { - return m_id; - } - uint qHash(uint seed) const - { - return ::qHash(m_id, seed); - } - - private: - QString m_id; - }; - - // Define several safetypes to prevent misuse of QString. - class __QvGroup; - class __QvConnection; - typedef IDType<__QvGroup> GroupId; - typedef IDType<__QvConnection> ConnectionId; - - inline const static auto NullConnectionId = ConnectionId("null"); - inline const static auto NullGroupId = GroupId("null"); - - template - QList StringsToIdList(const QList &strings) - { - QList list; - - for (auto str : strings) - { - list << IDType(str); - } - - return list; - } - - template - QList IdListToStrings(const QList &ids) - { - QList list; - - for (auto id : ids) - { - list << id.toString(); - } - - return list; - } - template - uint qHash(const IDType &key, uint seed = 0) - { - return key.qHash(seed); - } - // - /// Metadata object representing a connection. - struct ConnectionMetaObject final : ConnectionObject_Config - { - GroupId groupId = NullGroupId; - ConnectionMetaObject() : ConnectionObject_Config() - { - } - // Suger for down casting. - ConnectionMetaObject(const ConnectionObject_Config &base) : ConnectionMetaObject() - { - this->latency = base.latency; - this->lastConnected = base.lastConnected; - this->importDate = base.lastConnected; - this->upLinkData = base.upLinkData; - this->downLinkData = base.downLinkData; - this->displayName = base.displayName; - } - }; - - /// Metadata object representing a group. - struct GroupMetaObject final : SubscriptionObject_Config - { - // Implicit base of two types, since group object is actually the group base object. - bool isSubscription = false; - QList connections; - // Suger for down casting. - GroupMetaObject() : connections() - { - } - GroupMetaObject(const GroupObject_Config &base) : GroupMetaObject() - { - this->isSubscription = false; - this->displayName = base.displayName; - this->importDate = base.importDate; - this->connections = StringsToIdList(base.connections); - } - // Suger for down casting. - GroupMetaObject(const SubscriptionObject_Config &base) : GroupMetaObject((GroupObject_Config) base) - { - this->address = base.address; - this->lastUpdated = base.lastUpdated; - this->updateInterval = base.updateInterval; - this->isSubscription = true; - } - }; -} // namespace Qv2ray::core - -using namespace Qv2ray::core; diff --git a/src/core/CoreUtils.cpp b/src/core/CoreUtils.cpp index 282a0b7c..95a01f4c 100644 --- a/src/core/CoreUtils.cpp +++ b/src/core/CoreUtils.cpp @@ -1,6 +1,7 @@ -#include "CoreUtils.hpp" +#include "CoreUtils.hpp" #include "common/QvHelpers.hpp" +#include "core/connection/Serialization.hpp" #include "core/handler/ConfigHandler.hpp" namespace Qv2ray::core @@ -32,24 +33,21 @@ namespace Qv2ray::core if (*protocol == "vmess") { - auto Server = - StructFromJsonString(JsonToString(out["settings"].toObject()["vnext"].toArray().first().toObject())); + auto Server = VMessServerObject::fromJson(out["settings"].toObject()["vnext"].toArray().first().toObject()); *host = Server.address; *port = Server.port; return true; } else if (*protocol == "shadowsocks") { - auto x = JsonToString(out["settings"].toObject()["servers"].toArray().first().toObject()); - auto Server = StructFromJsonString(x); + auto Server = ShadowSocksServerObject::fromJson(out["settings"].toObject()["servers"].toArray().first().toObject()); *host = Server.address; *port = Server.port; return true; } else if (*protocol == "socks") { - auto x = JsonToString(out["settings"].toObject()["servers"].toArray().first().toObject()); - auto Server = StructFromJsonString(x); + auto Server = SocksServerObject::fromJson(out["settings"].toObject()["servers"].toArray().first().toObject()); *host = Server.address; *port = Server.port; return true; @@ -64,7 +62,9 @@ namespace Qv2ray::core } } - const tuple GetConnectionInfo(const ConnectionId &id, bool *status) + /// + /// [Protocol, Host, Port] + const std::tuple GetConnectionInfo(const ConnectionId &id, bool *status) { if (status != nullptr) *status = false; @@ -72,7 +72,7 @@ namespace Qv2ray::core return GetConnectionInfo(root, status); } - const tuple GetConnectionInfo(const CONFIGROOT &out, bool *status) + const std::tuple GetConnectionInfo(const CONFIGROOT &out, bool *status) { if (status != nullptr) *status = false; @@ -105,7 +105,7 @@ namespace Qv2ray::core return { QObject::tr("N/A"), QObject::tr("N/A"), 0 }; } - const tuple GetConnectionUsageAmount(const ConnectionId &id) + const std::tuple GetConnectionUsageAmount(const ConnectionId &id) { auto connection = ConnectionManager->GetConnectionMetaObject(id); return { connection.upLinkData, connection.downLinkData }; @@ -114,13 +114,13 @@ namespace Qv2ray::core uint64_t GetConnectionTotalData(const ConnectionId &id) { auto result = GetConnectionUsageAmount(id); - return get<0>(result) + get<1>(result); + return std::get<0>(result) + std::get<1>(result); } int64_t GetConnectionLatency(const ConnectionId &id) { auto connection = ConnectionManager->GetConnectionMetaObject(id); - return max(connection.latency, (int64_t) 0); + return std::max(connection.latency, {}); } const QString GetConnectionProtocolString(const ConnectionId &id) @@ -158,22 +158,15 @@ namespace Qv2ray::core return TruncateString(ConnectionManager->GetGroupMetaObject(id).displayName, limit); } - const GroupId GetConnectionGroupId(const ConnectionId &id) - { - return ConnectionManager->GetConnectionMetaObject(id).groupId; - } - const QMap GetConfigInboundPorts(const CONFIGROOT &root) { if (!root.contains("inbounds")) - { return {}; - } QMap inboundPorts; for (const auto &inboundVal : root["inbounds"].toArray()) { const auto &inbound = inboundVal.toObject(); - inboundPorts.insert(inbound["protocol"].toString(), inbound["port"].toInt()); + inboundPorts.insert(inbound["tag"].toString(), inbound["port"].toInt()); } return inboundPorts; } @@ -182,4 +175,24 @@ namespace Qv2ray::core { return GetConfigInboundPorts(ConnectionManager->GetConnectionRoot(id)); } + + const QMap GetConfigInboundHosts(const CONFIGROOT &root) + { + if (!root.contains("inbounds")) + { + return {}; + } + QMap inboundHosts; + for (const auto &inboundVal : root["inbounds"].toArray()) + { + const auto &inbound = inboundVal.toObject(); + inboundHosts.insert(inbound["tag"].toString(), inbound["listen"].toString()); + } + return inboundHosts; + } + + const QMap GetConfigInboundHosts(const ConnectionId &id) + { + return GetConfigInboundHosts(ConnectionManager->GetConnectionRoot(id)); + } } // namespace Qv2ray::core diff --git a/src/core/CoreUtils.hpp b/src/core/CoreUtils.hpp index 3a1f24ce..9507d161 100644 --- a/src/core/CoreUtils.hpp +++ b/src/core/CoreUtils.hpp @@ -1,9 +1,7 @@ -#pragma once +#pragma once #include "base/models/CoreObjectModels.hpp" +#include "base/models/QvConfigIdentifier.hpp" #include "base/models/QvSafeType.hpp" -#include "core/CoreSafeTypes.hpp" - -#include namespace Qv2ray::core { @@ -29,10 +27,10 @@ namespace Qv2ray::core // int64_t GetConnectionLatency(const ConnectionId &id); uint64_t GetConnectionTotalData(const ConnectionId &id); - const tuple GetConnectionUsageAmount(const ConnectionId &id); + const std::tuple GetConnectionUsageAmount(const ConnectionId &id); // - const tuple GetConnectionInfo(const ConnectionId &id, bool *status = nullptr); - const tuple GetConnectionInfo(const CONFIGROOT &out, bool *status = nullptr); + const std::tuple GetConnectionInfo(const ConnectionId &id, bool *status = nullptr); + const std::tuple GetConnectionInfo(const CONFIGROOT &out, bool *status = nullptr); // bool GetOutboundInfo(const OUTBOUND &out, QString *host, int *port, QString *protocol); bool IsComplexConfig(const CONFIGROOT &root); @@ -43,10 +41,12 @@ namespace Qv2ray::core const QString GetDisplayName(const ConnectionId &id, int limit = -1); const QString GetDisplayName(const GroupId &id, int limit = -1); // - const GroupId GetConnectionGroupId(const ConnectionId &id); + // const GroupId GetConnectionGroupId(const ConnectionId &id); // const QMap GetConfigInboundPorts(const CONFIGROOT &root); const QMap GetConfigInboundPorts(const ConnectionId &id); + const QMap GetConfigInboundHosts(const CONFIGROOT &root); + const QMap GetConfigInboundHosts(const ConnectionId &id); // } // namespace Qv2ray::core diff --git a/src/core/connection/ConnectionIO.cpp b/src/core/connection/ConnectionIO.cpp index 879784bd..7310e37b 100644 --- a/src/core/connection/ConnectionIO.cpp +++ b/src/core/connection/ConnectionIO.cpp @@ -1,33 +1,42 @@ #include "ConnectionIO.hpp" +#include "Serialization.hpp" #include "common/QvHelpers.hpp" -namespace Qv2ray::core::connection +namespace Qv2ray::core::connection::connectionIO { - namespace ConnectionIO + CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex) { - CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex) + auto root = CONFIGROOT(JsonFromString(StringFromFile(sourceFilePath))); + + if (!importComplex) { - QFile source(sourceFilePath); - - if (!source.exists()) - { - LOG(MODULE_FILEIO, "Trying to import from an non-existing file.") return CONFIGROOT(); - } - - auto root = CONFIGROOT(JsonFromString(StringFromFile(source))); - - if (!importComplex) - { - JSON_ROOT_TRY_REMOVE("inbounds") - JSON_ROOT_TRY_REMOVE("routing") - } - - JSON_ROOT_TRY_REMOVE("log") - JSON_ROOT_TRY_REMOVE("api") - JSON_ROOT_TRY_REMOVE("stats") - JSON_ROOT_TRY_REMOVE("dns") - return root; + root.remove("inbounds"); + root.remove("routing"); } - } // namespace ConnectionIO -} // namespace Qv2ray::core::connection + + root.remove("log"); + root.remove("api"); + root.remove("stats"); + root.remove("dns"); + return root; + } + + QList> GetConnectionConfigFromSubscription(const QByteArray &arr, const QString &groupName) + { + QList> subscriptionContent; + auto subscriptionLines = SplitLines(TryDecodeSubscriptionString(arr)); + for (const auto &line : subscriptionLines) + { + QString __alias; + QString __errMessage; + // Assign a group name, to pass the name check. + QString __groupName = groupName; + const auto connectionConfigMap = ConvertConfigFromString(line.trimmed(), &__alias, &__errMessage, &__groupName); + if (!__errMessage.isEmpty()) + LOG(MODULE_SUBSCRIPTION, "Error: " + __errMessage) + subscriptionContent << connectionConfigMap; + } + return subscriptionContent; + } +} // namespace Qv2ray::core::connection::connectionIO diff --git a/src/core/connection/ConnectionIO.hpp b/src/core/connection/ConnectionIO.hpp index 0bb01a4f..2bf2b5ad 100644 --- a/src/core/connection/ConnectionIO.hpp +++ b/src/core/connection/ConnectionIO.hpp @@ -1,14 +1,10 @@ #pragma once #include "base/Qv2rayBase.hpp" -#include "core/CoreSafeTypes.hpp" -namespace Qv2ray::core::connection +namespace Qv2ray::core::connection::connectionIO { - namespace ConnectionIO - { - // File Protocol - CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex); - } // namespace ConnectionIO -} // namespace Qv2ray::core::connection + CONFIGROOT ConvertConfigFromFile(const QString &sourceFilePath, bool importComplex); + QList> GetConnectionConfigFromSubscription(const QByteArray &arr, const QString &groupName); +} // namespace Qv2ray::core::connection::connectionIO using namespace Qv2ray::core::connection; -using namespace Qv2ray::core::connection::ConnectionIO; +using namespace Qv2ray::core::connection::connectionIO; diff --git a/src/core/connection/Generation.cpp b/src/core/connection/Generation.cpp deleted file mode 100644 index beafd3bb..00000000 --- a/src/core/connection/Generation.cpp +++ /dev/null @@ -1,632 +0,0 @@ -#include "Generation.hpp" - -#include "common/QvHelpers.hpp" -#include "core/CoreUtils.hpp" - -namespace Qv2ray::core::connection -{ - namespace Generation - { - // -------------------------- BEGIN CONFIG GENERATIONS - ROUTING GenerateRoutes(bool enableProxy, bool bypassCN, const QString &defaultOutboundTag) - { - auto &routeConfig = GlobalConfig.connectionConfig.routeConfig; - ROUTING root; - root.insert("domainStrategy", routeConfig.domainStrategy); - // - // For Rules list - ROUTERULELIST rulesList; - if (!enableProxy) - { - // This is added to disable all proxies, as a alternative - // influence of #64 - rulesList.append(GenerateSingleRouteRule("regexp:.*", true, OUTBOUND_TAG_DIRECT)); - rulesList.append(GenerateSingleRouteRule("0.0.0.0/0", false, OUTBOUND_TAG_DIRECT)); - rulesList.append(GenerateSingleRouteRule("::/0", false, OUTBOUND_TAG_DIRECT)); - } - - // Private IPs should always NOT TO PROXY! - rulesList.append(GenerateSingleRouteRule("geoip:private", false, OUTBOUND_TAG_DIRECT)); - // - // To the route list. - if (!routeConfig.domains.block.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.domains.block, true, OUTBOUND_TAG_BLACKHOLE)); - } - if (!routeConfig.domains.proxy.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.domains.proxy, true, defaultOutboundTag)); - } - if (!routeConfig.domains.direct.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.domains.direct, true, OUTBOUND_TAG_DIRECT)); - } - - // IP list - if (!routeConfig.ips.block.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.ips.block, false, OUTBOUND_TAG_BLACKHOLE)); - } - if (!routeConfig.ips.proxy.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.ips.proxy, false, defaultOutboundTag)); - } - if (!routeConfig.ips.direct.isEmpty()) - { - rulesList.append(GenerateSingleRouteRule(routeConfig.ips.direct, false, OUTBOUND_TAG_DIRECT)); - } - // - // Check if CN needs proxy, or direct. - if (bypassCN) - { - // No proxy agains CN addresses. - rulesList.append(GenerateSingleRouteRule("geoip:cn", false, OUTBOUND_TAG_DIRECT)); - rulesList.append(GenerateSingleRouteRule("geosite:cn", true, OUTBOUND_TAG_DIRECT)); - } - // - // - // As a bug fix of #64, this default rule has been disabled. - // rulesList.append(GenerateSingleRouteRule(QStringList({"regexp:.*"}), - // true, globalProxy ? OUTBOUND_TAG_PROXY : OUTBOUND_TAG_DIRECT)); - root.insert("rules", rulesList); - RROOT - } - - ROUTERULE GenerateSingleRouteRule(const QString &str, bool isDomain, const QString &outboundTag, const QString &type) - { - return GenerateSingleRouteRule(QStringList{ str }, isDomain, outboundTag, type); - } - - ROUTERULE GenerateSingleRouteRule(const QStringList &rules, bool isDomain, const QString &outboundTag, const QString &type) - { - ROUTERULE root; - auto list = rules; - list.removeAll(""); - root.insert(isDomain ? "domain" : "ip", QJsonArray::fromStringList(rules)); - JADD(outboundTag, type) - RROOT - } - - OUTBOUNDSETTING GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect, int userLevel) - { - OUTBOUNDSETTING root; - JADD(domainStrategy, redirect, userLevel) - RROOT - } - OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP) - { - OUTBOUNDSETTING root; - QJsonObject resp; - resp.insert("type", useHTTP ? "http" : "none"); - root.insert("response", resp); - RROOT - } - - OUTBOUNDSETTING GenerateShadowSocksOUT(const QList &servers) - { - OUTBOUNDSETTING root; - QJsonArray x; - - for (auto server : servers) - { - x.append(GenerateShadowSocksServerOUT(server.email, server.address, server.port, server.method, server.password, server.ota, - server.level)); - } - - root.insert("servers", x); - RROOT - } - - OUTBOUNDSETTING GenerateShadowSocksServerOUT(const QString &email, const QString &address, int port, const QString &method, - const QString &password, bool ota, int level) - { - OUTBOUNDSETTING root; - JADD(email, address, port, method, password, level, ota) - RROOT - } - - QJsonObject GenerateDNS(bool withLocalhost, const QStringList &dnsServers) - { - QJsonObject root; - QJsonArray servers(QJsonArray::fromStringList(dnsServers)); - - if (withLocalhost) - { - // https://github.com/Qv2ray/Qv2ray/issues/64 - // The fix patch didn't touch this line below. - // - // Should we APPEND localhost or PUSH_FRONT localhost? - servers.append("localhost"); - } - - root.insert("servers", servers); - RROOT - } - - INBOUNDSETTING GenerateDokodemoIN(const QString &address, int port, const QString &network, int timeout, bool followRedirect, - int userLevel) - { - INBOUNDSETTING root; - JADD(address, port, network, timeout, followRedirect, userLevel) - RROOT - } - - INBOUNDSETTING GenerateHTTPIN(const QList &_accounts, int timeout, bool allowTransparent, int userLevel) - { - INBOUNDSETTING root; - QJsonArray accounts; - - for (auto account : _accounts) - { - if (account.user.isEmpty() && account.pass.isEmpty()) - { - continue; - } - - accounts.append(GetRootObject(account)); - } - - if (!accounts.isEmpty()) - { - JADD(accounts) - } - - JADD(timeout, allowTransparent, userLevel) - RROOT - } - - OUTBOUNDSETTING GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password) - { - OUTBOUNDSETTING root; - QJsonArray servers; - { - QJsonObject oneServer; - oneServer["address"] = address; - oneServer["port"] = port; - - if (useAuth) - { - QJsonArray users; - QJsonObject oneUser; - oneUser["user"] = username; - oneUser["pass"] = password; - users.push_back(oneUser); - oneServer["users"] = users; - } - - servers.push_back(oneServer); - } - JADD(servers) - RROOT - } - - INBOUNDSETTING GenerateSocksIN(const QString &auth, const QList &_accounts, bool udp, const QString &ip, int userLevel) - { - INBOUNDSETTING root; - QJsonArray accounts; - - foreach (auto acc, _accounts) - { - if (acc.user.isEmpty() && acc.pass.isEmpty()) - { - continue; - } - - accounts.append(GetRootObject(acc)); - } - - if (!accounts.isEmpty()) - { - JADD(accounts) - } - - if (udp) - { - JADD(auth, udp, ip, userLevel) - } - else - { - JADD(auth, userLevel) - } - - RROOT - } - - OUTBOUND GenerateOutboundEntry(const QString &protocol, const OUTBOUNDSETTING &settings, const QJsonObject &streamSettings, - const QJsonObject &mux, const QString &sendThrough, const QString &tag) - { - OUTBOUND root; - JADD(sendThrough, protocol, settings, tag, streamSettings, mux) - RROOT - } - - INBOUND GenerateInboundEntry(const QString &listen, int port, const QString &protocol, const INBOUNDSETTING &settings, - const QString &tag, const QJsonObject &sniffing, const QJsonObject &allocate) - { - INBOUND root; - DEBUG(MODULE_CONNECTION, "Allocation is not used here, Not Implemented") - Q_UNUSED(allocate) - JADD(listen, port, protocol, settings, tag, sniffing) - RROOT - } - - QJsonObject GenerateAPIEntry(const QString &tag, bool withHandler, bool withLogger, bool withStats) - { - QJsonObject root; - QJsonArray services; - - if (withHandler) - services << "HandlerService"; - - if (withLogger) - services << "LoggerService"; - - if (withStats) - services << "StatsService"; - - JADD(services, tag) - RROOT - } - - // -------------------------- END CONFIG GENERATIONS - // - // BEGIN RUNTIME CONFIG GENERATION - // We need copy construct here - CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root) - { - // See: https://github.com/Qv2ray/Qv2ray/issues/129 - // routeCountLabel in Mainwindow makes here failed to ENOUGH-ly - // check the routing tables - // - // Check if is complex BEFORE adding anything. - bool isComplex = IsComplexConfig(root); - // - // - QJsonObject logObject; - // logObject.insert("access", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ACCESS_LOG_FILENAME); - // logObject.insert("error", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ERROR_LOG_FILENAME); - logObject.insert("loglevel", vLogLevels[GlobalConfig.logLevel]); - root.insert("log", logObject); - // - // Since Qv2ray does not support settings DNS manually for now. - // These settings are being added for both complex config AND simple config. - if (root.contains("dns") && !root.value("dns").toObject().isEmpty()) - { - // We assume the users are using THEIR DNS settings. - LOG(MODULE_CONNECTION, "Found DNS settings specified manually, skipping inserting GlobalConfig") - } - else - { - auto dnsList = GlobalConfig.connectionConfig.dnsList; - auto dnsObject = GenerateDNS(GlobalConfig.connectionConfig.withLocalDNS, dnsList); - root.insert("dns", dnsObject); - } - // - // - // If inbounds list is empty we append our global configured - // inbounds to the config. - // The setting applies to BOTH complex config AND simple config. - // Just to ensure there's AT LEAST 1 possible inbound is being configured. - if (!root.contains("inbounds") || root.value("inbounds").toArray().empty()) - { - INBOUNDS inboundsList; - QJsonObject sniffingObject{ { "enabled", false } }; - // HTTP Inbound - if (GlobalConfig.inboundConfig.useHTTP) - { - INBOUND httpInBoundObject; - httpInBoundObject.insert("listen", GlobalConfig.inboundConfig.listenip); - httpInBoundObject.insert("port", GlobalConfig.inboundConfig.http_port); - httpInBoundObject.insert("protocol", "http"); - httpInBoundObject.insert("tag", "http_IN"); - httpInBoundObject.insert("sniffing", sniffingObject); - - if (GlobalConfig.inboundConfig.http_useAuth) - { - auto httpInSettings = GenerateHTTPIN(QList() << GlobalConfig.inboundConfig.httpAccount); - httpInBoundObject.insert("settings", httpInSettings); - } - - inboundsList.append(httpInBoundObject); - } - - // SOCKS Inbound - if (GlobalConfig.inboundConfig.useSocks) - { - INBOUND socksInBoundObject; - socksInBoundObject.insert("listen", GlobalConfig.inboundConfig.listenip); - socksInBoundObject.insert("port", GlobalConfig.inboundConfig.socks_port); - socksInBoundObject.insert("protocol", "socks"); - socksInBoundObject.insert("tag", "socks_IN"); - socksInBoundObject.insert("sniffing", sniffingObject); - auto socksInSettings = GenerateSocksIN(GlobalConfig.inboundConfig.socks_useAuth ? "password" : "noauth", - QList() << GlobalConfig.inboundConfig.socksAccount, - GlobalConfig.inboundConfig.socksUDP, GlobalConfig.inboundConfig.socksLocalIP); - socksInBoundObject.insert("settings", socksInSettings); - inboundsList.append(socksInBoundObject); - } - - // TPROXY - if (GlobalConfig.inboundConfig.useTPROXY) - { - INBOUND tproxyInBoundObject; - tproxyInBoundObject.insert("listen", GlobalConfig.inboundConfig.tproxy_ip); - tproxyInBoundObject.insert("port", GlobalConfig.inboundConfig.tproxy_port); - tproxyInBoundObject.insert("protocol", "dokodemo-door"); - tproxyInBoundObject.insert("tag", "tproxy_IN"); - - QList networks; - if (GlobalConfig.inboundConfig.tproxy_use_tcp) - networks << "tcp"; - if (GlobalConfig.inboundConfig.tproxy_use_udp) - networks << "udp"; - const auto tproxy_network = networks.join(","); - - auto tproxyInSettings = GenerateDokodemoIN("", 0, tproxy_network, 0, true, 0); - tproxyInBoundObject.insert("settings", tproxyInSettings); - - QJsonObject tproxy_sniff{ { "enabled", true }, { "destOverride", QJsonArray{ "http", "tls" } } }; - tproxyInBoundObject.insert("sniffing", tproxy_sniff); - // tproxyInBoundObject.insert("sniffing", sniffingObject); - - QJsonObject tproxy_streamSettings{ { "sockopt", QJsonObject{ { "tproxy", GlobalConfig.inboundConfig.tproxy_mode } } } }; - tproxyInBoundObject.insert("streamSettings", tproxy_streamSettings); - - inboundsList.append(tproxyInBoundObject); - } - - root["inbounds"] = inboundsList; - DEBUG(MODULE_CONNECTION, "Added global config inbounds to the config") - } - - // Process every inbounds to make sure a tag is configured, fixed - // API 0 speed issue when no tag is configured. - INBOUNDS newTaggedInbounds(root["inbounds"].toArray()); - - for (auto i = 0; i < newTaggedInbounds.count(); i++) - { - auto _inboundItem = newTaggedInbounds[i].toObject(); - if (!_inboundItem.contains("tag") || _inboundItem["tag"].toString().isEmpty()) - { - LOG(MODULE_SETTINGS, "Adding a tag to an inbound.") - _inboundItem["tag"] = GenerateRandomString(8); - newTaggedInbounds[i] = _inboundItem; - } - } - - root["inbounds"] = newTaggedInbounds; - // - // - // Note: The part below always makes the whole functionality in - // trouble...... BE EXTREME CAREFUL when changing these code - // below... - if (isComplex) - { - // For some config files that has routing entries already. - // We DO NOT add extra routings. - // - // HOWEVER, we need to verify the QV2RAY_RULE_ENABLED entry. - // And what's more, process (by removing unused items) from a - // rule object. - ROUTING routing(root["routing"].toObject()); - ROUTERULELIST rules; - LOG(MODULE_CONNECTION, "Processing an existing routing table.") - - for (auto _rule : routing["rules"].toArray()) - { - auto _b = _rule.toObject(); - - if (_b.contains("QV2RAY_RULE_USE_BALANCER")) - { - if (_b["QV2RAY_RULE_USE_BALANCER"].toBool(false)) - { - // We use balancer - _b.remove("outboundTag"); - } - else - { - // We only use the normal outbound - _b.remove("balancerTag"); - } - } - else - { - LOG(MODULE_SETTINGS, "We found a rule without QV2RAY_RULE_USE_BALANCER, so didn't process it.") - } - - // If this entry has been disabled. - if (_b.contains("QV2RAY_RULE_ENABLED") && _b["QV2RAY_RULE_ENABLED"].toBool() == false) - { - LOG(MODULE_SETTINGS, "Discarded a rule as it's been set DISABLED") - } - else - { - rules.append(_b); - } - } - - routing["rules"] = rules; - root["routing"] = routing; - } - else - { - LOG(MODULE_CONNECTION, "Inserting default values to simple config") - if (root["outbounds"].toArray().count() != 1) - { - // There are no ROUTING but 2 or more outbounds.... This is rare, but possible. - LOG(MODULE_CONNECTION, "WARN: This message usually indicates the config file has logic errors:") - LOG(MODULE_CONNECTION, "WARN: --> The config file has NO routing section, however more than 1 outbounds are detected.") - } - // - auto tag = getTag(OUTBOUND(root["outbounds"].toArray().first().toObject())); - auto routeObject = GenerateRoutes(GlobalConfig.connectionConfig.enableProxy, GlobalConfig.connectionConfig.bypassCN, tag); - root.insert("routing", routeObject); - // - // Process forward proxy -#define fpConf GlobalConfig.connectionConfig.forwardProxyConfig - - if (fpConf.enableForwardProxy) - { - auto outboundArray = root["outbounds"].toArray(); - auto firstOutbound = outboundArray.first().toObject(); - - if (firstOutbound[QV2RAY_USE_FPROXY_KEY].toBool(false)) - { - LOG(MODULE_CONNECTION, "Applying forward proxy to current connection.") - PROXYSETTING proxy; - proxy["tag"] = OUTBOUND_TAG_FORWARD_PROXY; - firstOutbound["proxySettings"] = proxy; - // FP Outbound. - - if (fpConf.type.toLower() == "http" || fpConf.type.toLower() == "socks") - { - auto fpOutbound = - GenerateHTTPSOCKSOut(fpConf.serverAddress, fpConf.port, fpConf.useAuth, fpConf.username, fpConf.password); - outboundArray.push_back( - GenerateOutboundEntry(fpConf.type.toLower(), fpOutbound, {}, {}, "0.0.0.0", OUTBOUND_TAG_FORWARD_PROXY)); - } - else - { - if (!fpConf.type.isEmpty()) - { - DEBUG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + fpConf.type) - } - else - { - DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") - } - } - } - else - { - // Remove proxySettings from firstOutbound - firstOutbound.remove("proxySettings"); - } - - outboundArray.replace(0, firstOutbound); - root["outbounds"] = outboundArray; - } -#undef fpConf - OUTBOUNDS outbounds(root["outbounds"].toArray()); - // - const auto freeDS = (GlobalConfig.connectionConfig.v2rayFreedomDNS) ? "UseIP" : "AsIs"; - // - outbounds.append(GenerateOutboundEntry("freedom", GenerateFreedomOUT(freeDS, ":0", 0), {}, {}, "0.0.0.0", OUTBOUND_TAG_DIRECT)); - outbounds.append(GenerateOutboundEntry("blackhole", GenerateBlackHoleOUT(false), {}, {}, "0.0.0.0", OUTBOUND_TAG_BLACKHOLE)); - // - root["outbounds"] = outbounds; - - // intercepet dns if necessary - if (GlobalConfig.inboundConfig.useTPROXY && GlobalConfig.inboundConfig.dnsIntercept) - { - DNSInterceptFilter(root); - } - - // mark outbound if necessary - if (GlobalConfig.inboundConfig.useTPROXY && GlobalConfig.outboundConfig.mark > 0) - { - OutboundMarkSettingFilter(GlobalConfig.outboundConfig.mark, root); - } - } - - // Let's process some api features. - { - // - // Stats - // - root.insert("stats", QJsonObject()); - // - // Routes - // - QJsonObject routing = root["routing"].toObject(); - QJsonArray routingRules = routing["rules"].toArray(); - QJsonObject APIRouteRoot; - APIRouteRoot["type"] = "field"; - APIRouteRoot["outboundTag"] = API_TAG_DEFAULT; - QJsonArray inboundTag; - inboundTag.append(API_TAG_INBOUND); - APIRouteRoot["inboundTag"] = inboundTag; - // Add this to root. - routingRules.push_front(APIRouteRoot); - routing["rules"] = routingRules; - root["routing"] = routing; - // - // Policy - // - QJsonObject policyRoot = root.contains("policy") ? root["policy"].toObject() : QJsonObject(); - QJsonObject systemPolicy = policyRoot.contains("system") ? policyRoot["system"].toObject() : QJsonObject(); - systemPolicy["statsInboundUplink"] = true; - systemPolicy["statsInboundDownlink"] = true; - policyRoot["system"] = systemPolicy; - // Add this to root. - root["policy"] = policyRoot; - // - // Inbounds - // - INBOUNDS inbounds(root["inbounds"].toArray()); - INBOUNDSETTING fakeDocodemoDoor; - fakeDocodemoDoor["address"] = "127.0.0.1"; - QJsonObject apiInboundsRoot = - GenerateInboundEntry("127.0.0.1", GlobalConfig.apiConfig.statsPort, "dokodemo-door", fakeDocodemoDoor, API_TAG_INBOUND); - inbounds.push_front(apiInboundsRoot); - root["inbounds"] = inbounds; - // - // API - // - root["api"] = GenerateAPIEntry(API_TAG_DEFAULT); - } - - return root; - } - - void OutboundMarkSettingFilter(const int mark, CONFIGROOT &root) - { - QJsonObject sockoptObj{ { "mark", mark } }; - QJsonObject streamSettingsObj{ { "sockopt", sockoptObj } }; - OUTBOUNDS outbounds(root["outbounds"].toArray()); - for (auto i = 0; i < outbounds.count(); i++) - { - auto _outbound = outbounds[i].toObject(); - if (_outbound.contains("streamSettings")) - { - auto _streamSetting = _outbound["streamSettings"].toObject(); - if (_streamSetting.contains("sockopt")) - { - auto _sockopt = _streamSetting["sockopt"].toObject(); - _sockopt.insert("mark", mark); - _streamSetting["sockopt"] = _sockopt; - } - else - { - _streamSetting.insert("sockopt", sockoptObj); - } - _outbound["streamSettings"] = _streamSetting; - } - else - { - _outbound.insert("streamSettings", streamSettingsObj); - } - outbounds[i] = _outbound; - } - root["outbounds"] = outbounds; - } - - void DNSInterceptFilter(CONFIGROOT &root) - { - // dns outBound - QJsonObject dnsOutboundObj{ { "protocol", "dns" }, { "tag", "dns-out" } }; - OUTBOUNDS outbounds(root["outbounds"].toArray()); - outbounds.append(dnsOutboundObj); - root["outbounds"] = outbounds; - - // dns route - QJsonObject dnsRoutingRuleObj{ { "outboundTag", "dns-out" }, { "port", "53" }, { "type", "field" } }; - ROUTING routing(root["routing"].toObject()); - QJsonArray _rules(routing["rules"].toArray()); - _rules.insert(0, dnsRoutingRuleObj); - routing["rules"] = _rules; - root["routing"] = routing; - } - - } // namespace Generation -} // namespace Qv2ray::core::connection diff --git a/src/core/connection/Generation.hpp b/src/core/connection/Generation.hpp index 78a480b1..346b5832 100644 --- a/src/core/connection/Generation.hpp +++ b/src/core/connection/Generation.hpp @@ -1,50 +1,69 @@ #pragma once #include "base/Qv2rayBase.hpp" -namespace Qv2ray::core::connection +static const inline QStringList V2rayLogLevel = { "none", "debug", "info", "warning", "error" }; + +namespace Qv2ray::core::connection::generation { - namespace Generation + namespace routing { - // Important config generation algorithms. - const QStringList vLogLevels = { "none", "debug", "info", "warning", "error" }; - ROUTING GenerateRoutes(bool enableProxy, bool bypassCN, const QString &defaultOutboundTag); ROUTERULE GenerateSingleRouteRule(const QString &str, bool isDomain, const QString &outboundTag, const QString &type = "field"); ROUTERULE GenerateSingleRouteRule(const QStringList &list, bool isDomain, const QString &outboundTag, const QString &type = "field"); - QJsonObject GenerateDNS(bool withLocalhost, const QStringList &dnsServers); + QJsonObject GenerateDNS(bool withLocalhost, const QvConfig_DNS &dnsServer); + } // namespace routing + + namespace misc + { QJsonObject GenerateAPIEntry(const QString &tag, bool withHandler = true, bool withLogger = true, bool withStats = true); - // - // Outbound Protocols + } // namespace misc + + namespace inbounds + { + INBOUNDSETTING GenerateDokodemoIN(const QString &address, int port, const QString &network, int timeout, bool followRedirect, + int userLevel); + INBOUNDSETTING GenerateHTTPIN(const QList &accounts, int timeout = 300, bool allowTransparent = true, int userLevel = 0); + INBOUNDSETTING GenerateSocksIN(const QString &auth, const QList &_accounts, bool udp = false, + const QString &ip = "127.0.0.1", int userLevel = 0); + INBOUND GenerateInboundEntry(const QString &listen, int port, const QString &protocol, const INBOUNDSETTING &settings, + const QString &tag, const QJsonObject &sniffing = QJsonObject(), + const QJsonObject &allocate = QJsonObject()); + } // namespace inbounds + + namespace outbounds + { OUTBOUNDSETTING GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect, int userLevel); OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP); OUTBOUNDSETTING GenerateShadowSocksOUT(const QList &servers); OUTBOUNDSETTING GenerateShadowSocksServerOUT(const QString &email, const QString &address, int port, const QString &method, const QString &password, bool ota, int level); OUTBOUNDSETTING GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password); - // - // Inbounds Protocols - INBOUNDSETTING GenerateDokodemoIN(const QString &address, int port, const QString &network, int timeout, bool followRedirect, - int userLevel); - INBOUNDSETTING GenerateHTTPIN(const QList &accounts, int timeout = 300, bool allowTransparent = true, int userLevel = 0); - INBOUNDSETTING GenerateSocksIN(const QString &auth, const QList &_accounts, bool udp = false, - const QString &ip = "127.0.0.1", int userLevel = 0); - // - // Generate FINAL Configs - CONFIGROOT GenerateRuntimeConfig(CONFIGROOT root); OUTBOUND GenerateOutboundEntry(const QString &protocol, const OUTBOUNDSETTING &settings, const QJsonObject &streamSettings, const QJsonObject &mux = QJsonObject(), const QString &sendThrough = "0.0.0.0", const QString &tag = OUTBOUND_TAG_PROXY); - INBOUND GenerateInboundEntry(const QString &listen, int port, const QString &protocol, const INBOUNDSETTING &settings, - const QString &tag, const QJsonObject &sniffing = QJsonObject(), - const QJsonObject &allocate = QJsonObject()); + } // namespace outbounds + // namespace final + // { + // CONFIGROOT GenerateFinalConfig(CONFIGROOT root); + // } + + namespace filters + { // mark all outbound void OutboundMarkSettingFilter(const int mark, CONFIGROOT &root); + void RemoveEmptyMuxFilter(CONFIGROOT &root); + void DNSInterceptFilter(CONFIGROOT &root, const bool have_ipv6); + void BypassBTFilter(CONFIGROOT &root); + void mKCPSeedFilter(CONFIGROOT &root); + } // namespace filters - void DNSInterceptFilter(CONFIGROOT &root); - - } // namespace Generation -} // namespace Qv2ray::core::connection +} // namespace Qv2ray::core::connection::generation using namespace Qv2ray::core; using namespace Qv2ray::core::connection; -using namespace Qv2ray::core::connection::Generation; +using namespace Qv2ray::core::connection::generation; +using namespace Qv2ray::core::connection::generation::filters; +using namespace Qv2ray::core::connection::generation::inbounds; +using namespace Qv2ray::core::connection::generation::outbounds; +using namespace Qv2ray::core::connection::generation::routing; +using namespace Qv2ray::core::connection::generation::misc; diff --git a/src/core/connection/Serialization.cpp b/src/core/connection/Serialization.cpp index b9aa8908..78797901 100644 --- a/src/core/connection/Serialization.cpp +++ b/src/core/connection/Serialization.cpp @@ -1,64 +1,61 @@ #include "Serialization.hpp" #include "Generation.hpp" -#include "common/QvHelpers.hpp" #include "components/plugins/QvPluginHost.hpp" #include "core/CoreUtils.hpp" #include "core/handler/ConfigHandler.hpp" +#include "libs/QJsonStruct/QJsonIO.hpp" namespace Qv2ray::core::connection { - namespace Serialization + namespace serialization { - QMultiHash ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName) + QList> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, + QString *newGroupName) { - QMultiHash connectionConf; - if (link.startsWith("vmess://")) - { - auto conf = ConvertConfigFromVMessString(link, prefix, errMessage); - // - if (GlobalConfig.advancedConfig.setAllowInsecureCiphers || GlobalConfig.advancedConfig.setAllowInsecure) + QList> connectionConf; + const auto mkAllowInsecure = [](QJsonObject &conf) { + auto allowI = GlobalConfig.advancedConfig.setAllowInsecure; + auto allowSR = GlobalConfig.advancedConfig.setSessionResumption; + if (allowI || allowSR) { - auto outbound = conf["outbounds"].toArray().first().toObject(); - auto streamSettings = outbound["streamSettings"].toObject(); - auto tlsSettings = streamSettings["tlsSettings"].toObject(); - tlsSettings["allowInsecure"] = GlobalConfig.advancedConfig.setAllowInsecure; - tlsSettings["allowInsecureCiphers"] = GlobalConfig.advancedConfig.setAllowInsecureCiphers; - streamSettings["tlsSettings"] = tlsSettings; - outbound["streamSettings"] = streamSettings; - // - auto outbounds = conf["outbounds"].toArray(); - outbounds[0] = outbound; - conf["outbounds"] = outbounds; + QJsonIO::SetValue(conf, allowI, "outbounds", 0, "streamSettings", "tlsSettings", "allowInsecure"); + QJsonIO::SetValue(conf, !allowSR, "outbounds", 0, "streamSettings", "tlsSettings", "disableSessionResumption"); } - // - connectionConf.insert(*prefix, conf); + }; + if (link.startsWith("vmess://") && link.contains("@")) + { + auto conf = vmess_new::Deserialize(link, aliasPrefix, errMessage); + mkAllowInsecure(conf); + connectionConf << QPair{ *aliasPrefix, conf }; + } + else if (link.startsWith("vmess://")) + { + auto conf = vmess::Deserialize(link, aliasPrefix, errMessage); + mkAllowInsecure(conf); + connectionConf << QPair{ *aliasPrefix, conf }; } else if (link.startsWith("ss://")) { - auto conf = ConvertConfigFromSSString(link, prefix, errMessage); - connectionConf.insert(*prefix, conf); + auto conf = ss::Deserialize(link, aliasPrefix, errMessage); + connectionConf << QPair{ *aliasPrefix, conf }; } else if (link.startsWith("ssd://")) { QStringList errMessageList; - connectionConf = ConvertConfigFromSSDString(link, newGroupName, &errMessageList); + connectionConf = ssd::Deserialize(link, newGroupName, &errMessageList); *errMessage = errMessageList.join(NEWLINE); } else { bool ok = false; - auto configs = PluginHost->TryDeserializeShareLink(link, prefix, errMessage, newGroupName, &ok); - for (const auto &key : configs.keys()) + const auto configs = PluginHost->TryDeserializeShareLink(link, aliasPrefix, errMessage, newGroupName, &ok); + for (const auto &[_alias, _protocol, _outbound] : configs) { - auto vals = configs.values(key); - for (const auto &val : vals) - { - CONFIGROOT root; - auto outbound = GenerateOutboundEntry(val.first, OUTBOUNDSETTING(val.second), {}); - root.insert("outbounds", QJsonArray{ outbound }); - connectionConf.insert(key, root); - } + CONFIGROOT root; + auto outbound = GenerateOutboundEntry(_protocol, OUTBOUNDSETTING(_outbound), {}); + QJsonIO::SetValue(root, outbound, "outbounds", 0); + connectionConf << QPair{ _alias, root }; } if (!ok) { @@ -69,16 +66,16 @@ namespace Qv2ray::core::connection return connectionConf; } - const QString ConvertConfigToString(const ConnectionId &id, bool isSip002) + const QString ConvertConfigToString(const ConnectionGroupPair &identifier, bool isSip002) { - auto alias = GetDisplayName(id); - if (IsComplexConfig(id)) + auto alias = GetDisplayName(identifier.connectionId); + if (IsComplexConfig(identifier.connectionId)) { DEBUG(MODULE_CONNECTION, "Ignored an complex config: " + alias) return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER; } - auto server = ConnectionManager->GetConnectionRoot(id); - return ConvertConfigToString(alias, GetDisplayName(GetConnectionGroupId(id)), server, isSip002); + auto server = ConnectionManager->GetConnectionRoot(identifier.connectionId); + return ConvertConfigToString(alias, GetDisplayName(identifier.groupId), server, isSip002); } const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002) @@ -89,14 +86,14 @@ namespace Qv2ray::core::connection QString sharelink = ""; if (type == "vmess") { - auto vmessServer = StructFromJsonString(JsonToString(settings["vnext"].toArray().first().toObject())); - auto transport = StructFromJsonString(JsonToString(outbound["streamSettings"].toObject())); - sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias); + auto vmessServer = VMessServerObject::fromJson(settings["vnext"].toArray().first().toObject()); + auto transport = StreamSettingsObject::fromJson(outbound["streamSettings"].toObject()); + sharelink = vmess::Serialize(transport, vmessServer, alias); } else if (type == "shadowsocks") { - auto ssServer = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); - sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002); + auto ssServer = ShadowSocksServerObject::fromJson(settings["servers"].toArray().first().toObject()); + sharelink = ss::Serialize(ssServer, alias, isSip002); } else { @@ -115,14 +112,5 @@ namespace Qv2ray::core::connection return sharelink; } - QString DecodeSubscriptionString(const QByteArray &arr) - { - // String may start with: vmess:// and ss:// - // We only process vmess:// here - // Some subscription providers may use plain vmess:// saperated by - // lines But others may use base64 of above. - auto result = QString::fromUtf8(arr).trimmed(); - return result.contains("://") ? result : Base64Decode(result); - } - } // namespace Serialization + } // namespace serialization } // namespace Qv2ray::core::connection diff --git a/src/core/connection/Serialization.hpp b/src/core/connection/Serialization.hpp index 9db716ac..5dbb5740 100644 --- a/src/core/connection/Serialization.hpp +++ b/src/core/connection/Serialization.hpp @@ -1,64 +1,54 @@ #pragma once #include "base/Qv2rayBase.hpp" -#include "core/CoreSafeTypes.hpp" +#include "common/QvHelpers.hpp" -namespace Qv2ray::core::connection +namespace Qv2ray::core::connection::serialization { - namespace Serialization + const inline auto QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER = QObject::tr("(Complex config)"); + /** + * pattern for the nodes in ssd links. + * %1: airport name + * %2: node name + * %3: rate + */ + const inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); + // + // General + inline const QString TryDecodeSubscriptionString(const QByteArray &arr) { - /** - * pattern for the nodes in ssd links. - * %1: airport name - * %2: node name - * %3: rate - */ - inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); - // - // General - QString DecodeSubscriptionString(const QByteArray &arr); - QMultiHash ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, - QString *newGroupName = nullptr); - const QString ConvertConfigToString(const ConnectionId &id, bool isSip002 = false); - const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002); - // - // VMess URI Protocol - namespace vmess - { - CONFIGROOT ConvertConfigFromVMessString(const QString &vmess, QString *alias, QString *errMessage); - const QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &server, - const QString &alias); - } // namespace vmess - // - // SS URI Protocol - namespace ss - { - CONFIGROOT ConvertConfigFromSSString(const QString &ss, QString *alias, QString *errMessage); - const QString ConvertConfigToSSString(const ShadowSocksServerObject &server, const QString &alias, bool isSip002); - } // namespace ss - // - // SSD URI Protocol - namespace ssd - { - /** - * @brief decodeSSD - * @param uri the uri of ssd link. - * @param pattern the pattern for node names. - * @return tuple of: - * - tuple of: - * - airport name (for the sake of grouping) - * - shadowsocks configuration list - * - log list - * in case of error, no objects will be returned. - */ - QMultiHash ConvertConfigFromSSDString(const QString &uri, QString *groupName, QStringList *logList); - } // namespace ssd - // - } // namespace Serialization -} // namespace Qv2ray::core::connection + auto result = QString::fromUtf8(arr).trimmed(); + return result.contains("://") ? result : SafeBase64Decode(result); + } + QList> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, + QString *newGroupName = nullptr); + const QString ConvertConfigToString(const ConnectionGroupPair &id, bool isSip002 = false); + const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002); + + namespace vmess + { + CONFIGROOT Deserialize(const QString &vmess, QString *alias, QString *errMessage); + const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias); + } // namespace vmess + + namespace vmess_new + { + CONFIGROOT Deserialize(const QString &vmess, QString *alias, QString *errMessage); + const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias); + } // namespace vmess_new + + namespace ss + { + CONFIGROOT Deserialize(const QString &ss, QString *alias, QString *errMessage); + const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002); + } // namespace ss + + namespace ssd + { + QList> Deserialize(const QString &uri, QString *groupName, QStringList *logList); + } // namespace ssd + +} // namespace Qv2ray::core::connection::serialization using namespace Qv2ray::core; using namespace Qv2ray::core::connection; -using namespace Qv2ray::core::connection::Serialization; -using namespace Qv2ray::core::connection::Serialization::ss; -using namespace Qv2ray::core::connection::Serialization::ssd; -using namespace Qv2ray::core::connection::Serialization::vmess; +using namespace Qv2ray::core::connection::serialization; diff --git a/src/core/connection/generation/filters.cpp b/src/core/connection/generation/filters.cpp new file mode 100644 index 00000000..3c6c58d7 --- /dev/null +++ b/src/core/connection/generation/filters.cpp @@ -0,0 +1,67 @@ +#include "core/connection/Generation.hpp" +namespace Qv2ray::core::connection::generation::filters +{ + void OutboundMarkSettingFilter(const int mark, CONFIGROOT &root) + { + for (auto i = 0; i < root["outbounds"].toArray().count(); i++) + { + QJsonIO::SetValue(root, mark, "outbounds", i, "streamSettings", "sockopt", "mark"); + } + } + + void RemoveEmptyMuxFilter(CONFIGROOT &root) + { + for (auto i = 0; i < root["outbounds"].toArray().count(); i++) + { + if (!QJsonIO::GetValue(root, "outbounds", i, "mux", "enabled").toBool(false)) + { + QJsonIO::SetValue(root, QJsonIO::Undefined, "outbounds", i, "mux"); + } + } + } + void DNSInterceptFilter(CONFIGROOT &root, const bool have_ipv6) + { + // Static DNS Objects + static const QJsonObject dnsOutboundObj{ { "protocol", "dns" }, { "tag", "dns-out" } }; + QJsonArray dnsRouteInTag; + if (have_ipv6) + { + dnsRouteInTag = QJsonArray{ "tproxy_IN", "tproxy_IN_V6" }; + } + else + { + dnsRouteInTag = QJsonArray{ "tproxy_IN" }; + } + static const QJsonObject dnsRoutingRuleObj{ { "outboundTag", "dns-out" }, + { "port", "53" }, + { "type", "field" }, + { "inboundTag", dnsRouteInTag } }; + // DNS Outbound + QJsonIO::SetValue(root, dnsOutboundObj, "outbounds", root["outbounds"].toArray().count()); + // DNS Route + auto _rules = QJsonIO::GetValue(root, "routing", "rules").toArray(); + _rules.insert(0, dnsRoutingRuleObj); + QJsonIO::SetValue(root, _rules, "routing", "rules"); + } + + void BypassBTFilter(CONFIGROOT &root) + { + static const QJsonObject bypassBTRuleObj{ { "protocol", QJsonArray{ "bittorrent" } }, + { "outboundTag", OUTBOUND_TAG_DIRECT }, + { "type", "field" } }; + auto _rules = QJsonIO::GetValue(root, "routing", "rules").toArray(); + _rules.insert(0, bypassBTRuleObj); + QJsonIO::SetValue(root, _rules, "routing", "rules"); + } + + void mKCPSeedFilter(CONFIGROOT &root) + { + for (auto i = 0; i < root["outbounds"].toArray().count(); i++) + { + bool isEmptySeed = QJsonIO::GetValue(root, "outbounds", i, "streamSettings", "kcpSettings", "seed").toString().isEmpty(); + if (isEmptySeed) + QJsonIO::SetValue(root, QJsonIO::Undefined, "outbounds", i, "streamSettings", "kcpSettings", "seed"); + } + } + +} // namespace Qv2ray::core::connection::generation::filters diff --git a/src/core/connection/generation/final.cpp b/src/core/connection/generation/final.cpp new file mode 100644 index 00000000..d10d38e0 --- /dev/null +++ b/src/core/connection/generation/final.cpp @@ -0,0 +1,8 @@ +#include "common/QvHelpers.hpp" +#include "core/CoreUtils.hpp" +#include "core/connection/Generation.hpp" + +namespace Qv2ray::core::connection::generation::final +{ + +} // namespace Qv2ray::core::connection::generation::final diff --git a/src/core/connection/generation/inbounds.cpp b/src/core/connection/generation/inbounds.cpp new file mode 100644 index 00000000..37f36b32 --- /dev/null +++ b/src/core/connection/generation/inbounds.cpp @@ -0,0 +1,65 @@ +#include "core/connection/Generation.hpp" +namespace Qv2ray::core::connection::generation::inbounds +{ + + INBOUNDSETTING GenerateDokodemoIN(const QString &address, int port, const QString &network, int timeout, bool followRedirect, int userLevel) + { + INBOUNDSETTING root; + JADD(address, port, network, timeout, followRedirect, userLevel) + return root; + } + + INBOUNDSETTING GenerateHTTPIN(const QList &_accounts, int timeout, bool allowTransparent, int userLevel) + { + INBOUNDSETTING root; + QJsonArray accounts; + + for (const auto &account : _accounts) + { + if (account.user.isEmpty() && account.pass.isEmpty()) + continue; + accounts.append(account.toJson()); + } + + if (!accounts.isEmpty()) + JADD(accounts) + + JADD(timeout, allowTransparent, userLevel) + return root; + } + + INBOUNDSETTING GenerateSocksIN(const QString &auth, const QList &_accounts, bool udp, const QString &ip, int userLevel) + { + INBOUNDSETTING root; + QJsonArray accounts; + for (const auto &acc : _accounts) + { + if (acc.user.isEmpty() && acc.pass.isEmpty()) + continue; + accounts.append(acc.toJson()); + } + + if (!accounts.isEmpty()) + JADD(accounts) + + if (udp) + { + JADD(auth, udp, ip, userLevel) + } + else + { + JADD(auth, userLevel) + } + return root; + } + + INBOUND GenerateInboundEntry(const QString &listen, int port, const QString &protocol, const INBOUNDSETTING &settings, const QString &tag, + const QJsonObject &sniffing, const QJsonObject &allocate) + { + INBOUND root; + DEBUG(MODULE_CONNECTION, "Allocation is not used here, Not Implemented") + Q_UNUSED(allocate) + JADD(listen, port, protocol, settings, tag, sniffing) + return root; + } +} // namespace Qv2ray::core::connection::generation::inbounds diff --git a/src/core/connection/generation/misc.cpp b/src/core/connection/generation/misc.cpp new file mode 100644 index 00000000..3991f82c --- /dev/null +++ b/src/core/connection/generation/misc.cpp @@ -0,0 +1,22 @@ +#include "core/connection/Generation.hpp" + +namespace Qv2ray::core::connection::generation::misc +{ + QJsonObject GenerateAPIEntry(const QString &tag, bool withHandler, bool withLogger, bool withStats) + { + QJsonObject root; + QJsonArray services; + + if (withHandler) + services << "HandlerService"; + + if (withLogger) + services << "LoggerService"; + + if (withStats) + services << "StatsService"; + + JADD(services, tag) + return root; + } +} // namespace Qv2ray::core::connection::generation::misc diff --git a/src/core/connection/generation/outbounds.cpp b/src/core/connection/generation/outbounds.cpp new file mode 100644 index 00000000..4ee60f79 --- /dev/null +++ b/src/core/connection/generation/outbounds.cpp @@ -0,0 +1,63 @@ +#include "core/connection/Generation.hpp" +namespace Qv2ray::core::connection::generation::outbounds +{ + + OUTBOUNDSETTING GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect, int userLevel) + { + OUTBOUNDSETTING root; + JADD(domainStrategy, redirect, userLevel) + return root; + } + OUTBOUNDSETTING GenerateBlackHoleOUT(bool useHTTP) + { + OUTBOUNDSETTING root; + QJsonObject resp; + resp.insert("type", useHTTP ? "http" : "none"); + root.insert("response", resp); + return root; + } + + OUTBOUNDSETTING GenerateShadowSocksOUT(const QList &_servers) + { + OUTBOUNDSETTING root; + QJsonArray x; + + for (const auto &server : _servers) + { + x.append(GenerateShadowSocksServerOUT(server.email, server.address, server.port, server.method, // + server.password, server.ota, server.level)); + } + + root.insert("servers", x); + return root; + } + + OUTBOUNDSETTING GenerateShadowSocksServerOUT(const QString &email, const QString &address, int port, const QString &method, + const QString &password, bool ota, int level) + { + OUTBOUNDSETTING root; + JADD(email, address, port, method, password, level, ota) + return root; + } + + OUTBOUNDSETTING GenerateHTTPSOCKSOut(const QString &addr, int port, bool useAuth, const QString &username, const QString &password) + { + OUTBOUNDSETTING root; + QJsonIO::SetValue(root, addr, "servers", 0, "address"); + QJsonIO::SetValue(root, port, "servers", 0, "port"); + if (useAuth) + { + QJsonIO::SetValue(root, username, "servers", 0, "users", 0, "user"); + QJsonIO::SetValue(root, password, "servers", 0, "users", 0, "pass"); + } + return root; + } + + OUTBOUND GenerateOutboundEntry(const QString &protocol, const OUTBOUNDSETTING &settings, const QJsonObject &streamSettings, + const QJsonObject &mux, const QString &sendThrough, const QString &tag) + { + OUTBOUND root; + JADD(sendThrough, protocol, settings, tag, streamSettings, mux) + return root; + } +} // namespace Qv2ray::core::connection::generation::outbounds diff --git a/src/core/connection/generation/routing.cpp b/src/core/connection/generation/routing.cpp new file mode 100644 index 00000000..f4726fff --- /dev/null +++ b/src/core/connection/generation/routing.cpp @@ -0,0 +1,36 @@ +#include "core/connection/Generation.hpp" +namespace Qv2ray::core::connection::generation::routing +{ + QJsonObject GenerateDNS(bool withLocalhost, const QvConfig_DNS &dnsServer) + { + QJsonObject root = dnsServer.toJson(); + QJsonArray servers; + for (const auto &serv : dnsServer.servers) + { + servers << (serv.QV2RAY_DNS_IS_COMPLEX_DNS ? serv.toJson() : QJsonValue(serv.address)); + } + if (withLocalhost) + servers.push_front("localhost"); + root["servers"] = servers; + JAUTOREMOVE(root, "clientIp"); + JAUTOREMOVE(root, "hosts"); + JAUTOREMOVE(root, "tag"); + return root; + } + + ROUTERULE GenerateSingleRouteRule(const QString &str, bool isDomain, const QString &outboundTag, const QString &type) + { + return GenerateSingleRouteRule(QStringList{ str }, isDomain, outboundTag, type); + } + + ROUTERULE GenerateSingleRouteRule(const QStringList &rules, bool isDomain, const QString &outboundTag, const QString &type) + { + ROUTERULE root; + auto list = rules; + list.removeAll(""); + root.insert(isDomain ? "domain" : "ip", QJsonArray::fromStringList(rules)); + JADD(outboundTag, type) + return root; + } + +} // namespace Qv2ray::core::connection::generation::routing diff --git a/src/core/connection/Serialization_ss.cpp b/src/core/connection/serialization/ss.cpp similarity index 80% rename from src/core/connection/Serialization_ss.cpp rename to src/core/connection/serialization/ss.cpp index 86259b59..50eefa7d 100644 --- a/src/core/connection/Serialization_ss.cpp +++ b/src/core/connection/serialization/ss.cpp @@ -1,13 +1,13 @@ -#include "Generation.hpp" -#include "Serialization.hpp" #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" +#include "core/connection/Generation.hpp" +#include "core/connection/Serialization.hpp" namespace Qv2ray::core::connection { - namespace Serialization::ss + namespace serialization::ss { - CONFIGROOT ConvertConfigFromSSString(const QString &ssUri, QString *alias, QString *errMessage) + CONFIGROOT Deserialize(const QString &ssUri, QString *alias, QString *errMessage) { ShadowSocksServerObject server; QString d_name; @@ -81,17 +81,18 @@ namespace Qv2ray::core::connection auto x = QUrl::fromUserInput(uri); server.address = x.host(); server.port = x.port(); - QString userInfo = Base64Decode(x.userName()); - auto userInfoSp = userInfo.indexOf(':'); + const auto userInfo = SafeBase64Decode(x.userName()); + const auto userInfoSp = userInfo.indexOf(':'); // DEBUG(MODULE_CONNECTION, "Userinfo splitter position: " + QSTRN(userInfoSp)) if (userInfoSp < 0) { *errMessage = QObject::tr("Can't find the colon separator between method and password"); + return CONFIGROOT{}; } - QString method = userInfo.mid(0, userInfoSp); + const auto method = userInfo.mid(0, userInfoSp); server.method = method; server.password = userInfo.mid(userInfoSp + 1); } @@ -99,31 +100,30 @@ namespace Qv2ray::core::connection d_name = QUrl::fromPercentEncoding(d_name.toUtf8()); CONFIGROOT root; OUTBOUNDS outbounds; - outbounds.append( - GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT(QList{ server }), QJsonObject())); + outbounds.append(GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT({ server }), {})); JADD(outbounds) *alias = alias->isEmpty() ? d_name : *alias + "_" + d_name; LOG(MODULE_CONNECTION, "Deduced alias: " + *alias) return root; } - const QString ConvertConfigToSSString(const ShadowSocksServerObject &server, const QString &alias, bool isSip002) + const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002) { auto myAlias = QUrl::toPercentEncoding(alias); if (isSip002) { LOG(MODULE_CONNECTION, "Converting an ss-server config to Sip002 ss:// format") - QString plainUserInfo = server.method + ":" + server.password; - QString userinfo(plainUserInfo.toUtf8().toBase64(QByteArray::Base64Option::Base64UrlEncoding).data()); - return "ss://" + userinfo + "@" + server.address + ":" + QSTRN(server.port) + "#" + myAlias; + const auto plainUserInfo = server.method + ":" + server.password; + const auto userinfo = plainUserInfo.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals); + return "ss://" + userinfo + "@" + server.address + ":" + QSTRN(server.port) + "/#" + myAlias; } else { LOG(MODULE_CONNECTION, "Converting an ss-server config to old ss:// string format") QString ssUri = server.method + ":" + server.password + "@" + server.address + ":" + QSTRN(server.port); - return "ss://" + ssUri.toUtf8().toBase64(QByteArray::Base64Option::OmitTrailingEquals) + "#" + myAlias; + return "ss://" + ssUri.toUtf8().toBase64(QByteArray::Base64Option::OmitTrailingEquals) + "/#" + myAlias; } } - } // namespace Serialization::ss + } // namespace serialization::ss } // namespace Qv2ray::core::connection diff --git a/src/core/connection/Serialization_ssd.cpp b/src/core/connection/serialization/ssd.cpp similarity index 96% rename from src/core/connection/Serialization_ssd.cpp rename to src/core/connection/serialization/ssd.cpp index f207c3d4..1025471e 100644 --- a/src/core/connection/Serialization_ssd.cpp +++ b/src/core/connection/serialization/ssd.cpp @@ -10,7 +10,7 @@ #include "core/connection/Generation.hpp" #include "core/connection/Serialization.hpp" -namespace Qv2ray::core::connection::Serialization +namespace Qv2ray::core::connection::serialization { namespace ssd @@ -73,7 +73,7 @@ namespace Qv2ray::core::connection::Serialization // A pair of an error string list, together with some optionally existed pair, which contains a QString for airport name and a List of // pairs that contains a QString for connection name and finally, our ShadowSocksServerObject // - QMultiHash ConvertConfigFromSSDString(const QString &uri, QString *groupName, QStringList *logList) + QList> Deserialize(const QString &uri, QString *groupName, QStringList *logList) { // ssd links should begin with "ssd://" if (!uri.startsWith("ssd://")) @@ -84,7 +84,7 @@ namespace Qv2ray::core::connection::Serialization // decode base64 const auto ssdURIBody = QStringRef(&uri, 6, uri.length() - 6); - const auto decodedJSON = QByteArray::fromBase64(ssdURIBody.toUtf8()); + const auto decodedJSON = SafeBase64Decode(ssdURIBody.toString()).toUtf8(); if (decodedJSON.length() == 0) { @@ -136,7 +136,7 @@ namespace Qv2ray::core::connection::Serialization // obj.servers MUST_ARRAY("servers"); // - QMultiHash serverList; + QList> serverList; // // iterate through the servers @@ -209,7 +209,7 @@ namespace Qv2ray::core::connection::Serialization OUTBOUNDS outbounds; outbounds.append(GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT({ ssObject }), {})); JADD(outbounds) - serverList.insertMulti(totalName, root); + serverList.append({ totalName, root }); } // returns the current result @@ -223,4 +223,4 @@ namespace Qv2ray::core::connection::Serialization #undef SHOULD_EXIST #undef SHOULD_STRING } // namespace ssd -} // namespace Qv2ray::core::connection::Serialization +} // namespace Qv2ray::core::connection::serialization diff --git a/src/core/connection/Serialization_vmess.cpp b/src/core/connection/serialization/vmess.cpp similarity index 85% rename from src/core/connection/Serialization_vmess.cpp rename to src/core/connection/serialization/vmess.cpp index b7bbf44f..17f83d71 100644 --- a/src/core/connection/Serialization_vmess.cpp +++ b/src/core/connection/serialization/vmess.cpp @@ -1,16 +1,15 @@ -#include "Generation.hpp" -#include "Serialization.hpp" #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" -#include "core/handler/ConfigHandler.hpp" +#include "core/connection/Generation.hpp" +#include "core/connection/Serialization.hpp" namespace Qv2ray::core::connection { - namespace Serialization::vmess + namespace serialization::vmess { // From https://github.com/2dust/v2rayN/wiki/分享链接格式说明(ver-2) - const QString ConvertConfigToVMessString(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias) + const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias) { QJsonObject vmessUriRoot; // Constant @@ -53,15 +52,20 @@ namespace Qv2ray::core::connection vmessUriRoot["path"] = transfer.httpSettings.path; } + if (!vmessUriRoot.contains("type") || vmessUriRoot["type"].toString().isEmpty()) + { + vmessUriRoot["type"] = "none"; + } + // auto vmessPart = Base64Encode(JsonToString(vmessUriRoot, QJsonDocument::JsonFormat::Compact)); return "vmess://" + vmessPart; } + // This generates global config containing only one outbound.... - CONFIGROOT ConvertConfigFromVMessString(const QString &vmessStr, QString *alias, QString *errMessage) + CONFIGROOT Deserialize(const QString &vmessStr, QString *alias, QString *errMessage) { #define default CONFIGROOT() - LOG(MODULE_SETTINGS, "Trying to convert from a vmess string.") QString vmess = vmessStr; if (vmess.trimmed() != vmess) @@ -88,7 +92,7 @@ namespace Qv2ray::core::connection return default; } - auto vmessString = Base64Decode(b64Str); + auto vmessString = SafeBase64Decode(b64Str); auto jsonErr = VerifyJsonString(vmessString); if (!jsonErr.isEmpty()) @@ -105,33 +109,9 @@ namespace Qv2ray::core::connection return default; } - // Explicitly don't support v1 vmess links. - if (!vmessConf.contains("v")) { - *errMessage = QObject::tr("seems like a v1 vmess, we don't support it"); - return default; - } - bool flag = true; - // C is a quick hack... -#define C(k) (flag = (vmessConf.contains(k) ? (errMessage->clear(), true) : (*errMessage += (k " does not exist"), false))) - // id, aid, port and add are mandatory fields of a vmess:// - // link. - flag = flag && C("id") && (C("aid") || C("alterId")) && C("port") && C("add"); - // Stream Settings - auto net = vmessConf["net"].toString(); - - if (net == "http" || net == "ws") - flag = flag && C("host") && C("path"); - else if (net == "domainsocket") - flag = flag && C("path"); - else if (net == "quic") - flag = flag && C("host") && C("type") && C("path"); - -#undef C - // return flag ? 0 : 1; - // -------------------------------------------------------------------------------------- CONFIGROOT root; - QString ps, add, id, /*net,*/ type, host, path, tls; + QString ps, add, id, net, type, host, path, tls; int port, aid; // // __vmess_checker__func(key, values) @@ -167,6 +147,23 @@ namespace Qv2ray::core::connection LOG(MODULE_IMPORT, " --> PS: " + ps) \ } \ } + + // vmess v1 upgrader + if (!vmessConf.contains("v")) + { + LOG(MODULE_IMPORT, "Detected deprecated vmess v1. Trying to upgrade...") + if (const auto network = vmessConf["net"].toString(); network == "ws" || network == "h2") + { + const QStringList hostComponents = vmessConf["host"].toString().replace(" ", "").split(";"); + if (const auto nParts = hostComponents.length(); nParts == 1) + vmessConf["path"] = hostComponents[0], vmessConf["host"] = ""; + else if (nParts == 2) + vmessConf["path"] = hostComponents[0], vmessConf["host"] = hostComponents[1]; + else + vmessConf["path"] = "/", vmessConf["host"] = ""; + } + } + // Strict check of VMess protocol, to check if the specified value // is in the correct range. // @@ -193,11 +190,13 @@ namespace Qv2ray::core::connection << "domainsocket" // << "quic"); // // - __vmess_checker__func(path, << ""); // - __vmess_checker__func(host, << ""); // - __vmess_checker__func(tls, << ""); // + __vmess_checker__func(tls, << "none" // + << "tls"); // + path = vmessConf.contains("path") ? vmessConf["path"].toVariant().toString() : (net == "quic" ? "" : "/"); + host = vmessConf.contains("host") ? vmessConf["host"].toVariant().toString() : (net == "quic" ? "none" : ""); } - // Repect connection type rather than obfs type // + + // Repect connection type rather than obfs type if (QStringList{ "srtp", "utp", "wechat-video" }.contains(type)) // { // if (net != "quic" && net != "kcp") // @@ -206,6 +205,7 @@ namespace Qv2ray::core::connection type = "none"; // } // } + port = vmessConf["port"].toVariant().toInt(); aid = vmessConf["aid"].toVariant().toInt(); // @@ -224,7 +224,7 @@ namespace Qv2ray::core::connection // VMess root config OUTBOUNDSETTING vConf; QJsonArray vnextArray; - vnextArray.append(JsonFromString(StructToJsonString(serv))); + vnextArray.append(serv.toJson()); vConf["vnext"] = vnextArray; // // Stream Settings @@ -237,11 +237,11 @@ namespace Qv2ray::core::connection else if (net == "http" || net == "h2") { // Fill hosts for HTTP - for (auto _host : host.split(',')) + for (const auto &_host : host.split(',')) { if (!_host.isEmpty()) { - streaming.httpSettings.host.push_back(_host.trimmed()); + streaming.httpSettings.host << _host.trimmed(); } } @@ -249,7 +249,8 @@ namespace Qv2ray::core::connection } else if (net == "ws") { - streaming.wsSettings.headers["Host"] = host; + if (!host.isEmpty()) + streaming.wsSettings.headers["Host"] = host; streaming.wsSettings.path = path; } else if (net == "kcp") @@ -284,7 +285,7 @@ namespace Qv2ray::core::connection streaming.network = net; // // WARN Mux is missing here. - auto outbound = GenerateOutboundEntry("vmess", vConf, GetRootObject(streaming), QJsonObject(), "0.0.0.0", OUTBOUND_TAG_PROXY); + auto outbound = GenerateOutboundEntry("vmess", vConf, streaming.toJson(), {}, "0.0.0.0", OUTBOUND_TAG_PROXY); // root["outbounds"] = QJsonArray() << outbound; // If previous alias is empty, just the PS is needed, else, append a "_" @@ -292,5 +293,5 @@ namespace Qv2ray::core::connection return root; #undef default } - } // namespace Serialization::vmess + } // namespace serialization::vmess } // namespace Qv2ray::core::connection diff --git a/src/core/connection/serialization/vmess_new.cpp b/src/core/connection/serialization/vmess_new.cpp new file mode 100644 index 00000000..05963e4e --- /dev/null +++ b/src/core/connection/serialization/vmess_new.cpp @@ -0,0 +1,69 @@ +#include "common/QvHelpers.hpp" +#include "core/CoreUtils.hpp" +#include "core/connection/Generation.hpp" +#include "core/connection/Serialization.hpp" + +#include +#include + +namespace Qv2ray::core::connection +{ + namespace serialization::vmess_new + { + CONFIGROOT Deserialize(const QString &vmessStr, QString *alias, QString *errMessage) + { + Q_UNUSED(vmessStr) + Q_UNUSED(alias) + Q_UNUSED(errMessage) +#define default CONFIGROOT() + LOG(MODULE_CONNECTION, "咕咕") + return default; +#if 0 + QUrl url{ vmessStr }; + QUrlQuery query{ url }; + // + if (!url.isValid()) + { + *errMessage = QObject::tr("vmess:// url is invalid"); + return default; + } + + QString net; + bool tls; + + for (const auto &_protocol : url.userName().split("+")) + { + tls = tls || _protocol == "tls"; + net = _protocol == "tls" ? net : _protocol; + } + + if (!QStringList{ "tcp", "http", "ws", "kcp", "quic" }.contains(net)) + { + *errMessage = QObject::tr("Invalid streamSettings protocol: ") + net; + return default; + } + + QString uuid; + int aid; + { + const auto pswd = url.password(); + const auto index = pswd.lastIndexOf("-"); + uuid = pswd.mid(0, index); + aid = pswd.right(pswd.length() - index - 1).toInt(); + } + const auto host = url.host(); + int port = url.port(); +#endif +#undef default + } + + const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias) + { + Q_UNUSED(transfer) + Q_UNUSED(server) + Q_UNUSED(alias) + LOG(MODULE_CONNECTION, "咕咕") + return ""; + } + } // namespace serialization::vmess_new +} // namespace Qv2ray::core::connection diff --git a/src/core/handler/ConfigHandler.cpp b/src/core/handler/ConfigHandler.cpp index 04335a8a..2d58006c 100644 --- a/src/core/handler/ConfigHandler.cpp +++ b/src/core/handler/ConfigHandler.cpp @@ -1,80 +1,69 @@ #include "ConfigHandler.hpp" +#include "common/HTTPRequestHelper.hpp" #include "common/QvHelpers.hpp" #include "components/plugins/QvPluginHost.hpp" #include "core/connection/Serialization.hpp" +#include "core/handler/RouteHandler.hpp" #include "core/settings/SettingsBackend.hpp" -namespace Qv2ray::core::handlers +namespace Qv2ray::core::handler { - - QvConfigHandler::QvConfigHandler() + QvConfigHandler::QvConfigHandler(QObject *parent) : QObject(parent) { + asyncRequestHelper = new NetworkRequestHelper(this); DEBUG(MODULE_CORE_HANDLER, "ConnectionHandler Constructor.") - - // Do we need to check how many of them are loaded? - // Do not use: for (const auto &key : connections), why? - for (auto i = 0; i < GlobalConfig.connections.count(); i++) + const auto connectionJson = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + "connections.json")); + const auto groupJson = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + "groups.json")); + // + for (const auto &connectionId : connectionJson.keys()) { - auto const &id = ConnectionId(GlobalConfig.connections.keys().at(i)); - connections[id] = GlobalConfig.connections.values().at(i); + connections.insert(ConnectionId{ connectionId }, ConnectionObject::fromJson(connectionJson.value(connectionId).toObject())); } - - for (const auto &key : GlobalConfig.subscriptions.keys()) + // + for (const auto &groupId : groupJson.keys()) { - GroupId gkey(key); - if (gkey == NullGroupId) + auto groupObject = GroupObject::fromJson(groupJson.value(groupId).toObject()); + if (groupObject.displayName.isEmpty()) { - LOG(MODULE_CORE_HANDLER, "Removed a null subscription id") - continue; + groupObject.displayName = tr("Group: %1").arg(GenerateRandomString(5)); } - auto const &val = GlobalConfig.subscriptions[key]; - groups[gkey] = val; - - for (auto conn : val.connections) + groups.insert(GroupId{ groupId }, groupObject); + for (const auto &connId : groupObject.connections) { - connections[ConnectionId(conn)].groupId = GroupId(key); - } - } - - for (const auto &key : GlobalConfig.groups.keys()) - { - GroupId gkey(key); - if (gkey == NullGroupId) - { - LOG(MODULE_CORE_HANDLER, "Removed a null group id") - continue; - } - auto const &val = GlobalConfig.groups.value(key); - groups[gkey] = val; - - for (auto conn : val.connections) - { - connections[ConnectionId(conn)].groupId = GroupId(key); - } - } - - for (const auto &id : connections.keys()) - { - DEBUG(MODULE_CORE_HANDLER, "Loading connection: " + connections.value(id).displayName + " to cache.") - auto const &group = connections.value(id).groupId; - if (group != NullGroupId) - { - auto path = group.toString() + "/" + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; - path.prepend(groups[group].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR); - // - connectionRootCache[id] = CONFIGROOT(JsonFromString(StringFromFile(path))); - } - else - { - connections.remove(id); - LOG(MODULE_CORE_HANDLER, "Dropped connection id: " + id.toString() + " since it's not in a group") + connections[connId].__qvConnectionRefCount++; } } // + for (const auto &id : connections.keys()) + { + auto const &connectionObject = connections.value(id); + if (connectionObject.__qvConnectionRefCount == 0) + { + QFile connectionFile(QV2RAY_CONNECTIONS_DIR + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION); + if (connectionFile.exists()) + { + if (!connectionFile.remove()) + LOG(MODULE_CONNECTION, "Failed to remove connection config file") + } + connections.remove(id); + LOG(MODULE_CORE_HANDLER, "Dropped connection id: " + id.toString() + " since it's not in a group") + } + else + { + const auto connectionFilePath = QV2RAY_CONNECTIONS_DIR + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; + connectionRootCache[id] = CONFIGROOT(JsonFromString(StringFromFile(connectionFilePath))); + DEBUG(MODULE_CORE_HANDLER, "Loaded connection id: " + id.toString() + " into cache.") + } + } + // Force default group name. - groups[DefaultGroupId].displayName = tr("Default Group"); - groups[DefaultGroupId].isSubscription = false; + if (!groups.contains(DefaultGroupId)) + { + groups.insert(DefaultGroupId, {}); + groups[DefaultGroupId].displayName = tr("Default Group"); + groups[DefaultGroupId].isSubscription = false; + } // kernelHandler = new KernelInstanceHandler(this); connect(kernelHandler, &KernelInstanceHandler::OnCrashed, this, &QvConfigHandler::OnKernelCrashed_p); @@ -83,9 +72,8 @@ namespace Qv2ray::core::handlers connect(kernelHandler, &KernelInstanceHandler::OnConnected, this, &QvConfigHandler::OnConnected); connect(kernelHandler, &KernelInstanceHandler::OnDisconnected, this, &QvConfigHandler::OnDisconnected); // - tcpingHelper = new QvTCPingHelper(5, this); - httpHelper = new QvHttpRequestHelper(this); - connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p); + tcpingHelper = new LatencyTestHost(5, this); + connect(tcpingHelper, &LatencyTestHost::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p); // // Save per 1 minutes. saveTimerId = startTimer(1 * 60 * 1000); @@ -93,45 +81,30 @@ namespace Qv2ray::core::handlers pingConnectionTimerId = startTimer(60 * 1000); } - void QvConfigHandler::CHSaveConfigData() + void QvConfigHandler::SaveConnectionConfig() { - // Do not copy construct. - auto &newGlobalConfig = GlobalConfig; - newGlobalConfig.connections.clear(); - newGlobalConfig.groups.clear(); - newGlobalConfig.subscriptions.clear(); - - for (auto i = 0; i < connections.count(); i++) + QJsonObject connectionsObject; + for (const auto &key : connections.keys()) { - newGlobalConfig.connections[connections.keys()[i].toString()] = connections.values()[i]; + connectionsObject[key.toString()] = connections[key].toJson(); } - - for (auto i = 0; i < groups.count(); i++) + StringToFile(JsonToString(connectionsObject), QV2RAY_CONFIG_DIR + "connections.json"); + // + QJsonObject groupObject; + for (const auto &key : groups.keys()) { - QStringList connections = IdListToStrings(groups.values()[i].connections); - - if (groups.values()[i].isSubscription) - { - SubscriptionObject_Config o = groups.values()[i]; - o.connections = connections; - newGlobalConfig.subscriptions[groups.keys()[i].toString()] = o; - } - else - { - GroupObject_Config o = groups.values()[i]; - o.connections = connections; - newGlobalConfig.groups[groups.keys()[i].toString()] = o; - } + groupObject[key.toString()] = groups[key].toJson(); } - - SaveGlobalSettings(newGlobalConfig); + StringToFile(JsonToString(groupObject), QV2RAY_CONFIG_DIR + "groups.json"); + RouteManager->SaveRoutes(); + SaveGlobalSettings(); } void QvConfigHandler::timerEvent(QTimerEvent *event) { if (event->timerId() == saveTimerId) { - CHSaveConfigData(); + SaveConnectionConfig(); } else if (event->timerId() == pingAllTimerId) { @@ -140,16 +113,16 @@ namespace Qv2ray::core::handlers else if (event->timerId() == pingConnectionTimerId) { auto id = kernelHandler->CurrentConnection(); - if (id != NullConnectionId && GlobalConfig.advancedConfig.testLatencyPeriodcally) + if (!id.isEmpty() && GlobalConfig.advancedConfig.testLatencyPeriodcally) { - StartLatencyTest(id); + StartLatencyTest(id.connectionId); } } } void QvConfigHandler::StartLatencyTest() { - for (auto connection : connections.keys()) + for (const auto &connection : connections.keys()) { StartLatencyTest(connection); } @@ -157,7 +130,7 @@ namespace Qv2ray::core::handlers void QvConfigHandler::StartLatencyTest(const GroupId &id) { - for (auto connection : groups[id].connections) + for (const auto &connection : groups[id].connections) { StartLatencyTest(connection); } @@ -166,14 +139,14 @@ namespace Qv2ray::core::handlers void QvConfigHandler::StartLatencyTest(const ConnectionId &id) { emit OnLatencyTestStarted(id); - tcpingHelper->TestLatency(id); + tcpingHelper->TestLatency(id, GlobalConfig.networkConfig.latencyTestingMethod); } const QList QvConfigHandler::Subscriptions() const { QList subsList; - for (auto group : groups.keys()) + for (const auto &group : groups.keys()) { if (groups[group].isSubscription) { @@ -184,122 +157,141 @@ namespace Qv2ray::core::handlers return subsList; } - const ConnectionId QvConfigHandler::GetConnectionIdByDisplayName(const QString &displayName, const GroupId &group) const - { - CheckGroupExistanceEx(group, NullConnectionId); - for (auto conn : groups[group].connections) - { - if (connections[conn].displayName == displayName) - { - return conn; - } - } - - return NullConnectionId; - } - const GroupId QvConfigHandler::GetGroupIdByDisplayName(const QString &displayName) const - { - for (auto group : groups.keys()) - { - if (groups[group].displayName == displayName) - { - return group; - } - } - - return NullGroupId; - } void QvConfigHandler::ClearGroupUsage(const GroupId &id) { for (const auto &conn : groups[id].connections) { - ClearConnectionUsage(conn); + ClearConnectionUsage({ conn, id }); } } - void QvConfigHandler::ClearConnectionUsage(const ConnectionId &id) + void QvConfigHandler::ClearConnectionUsage(const ConnectionGroupPair &id) { - CheckConnectionExistanceEx(id, nothing); - connections[id].upLinkData = 0; - connections[id].downLinkData = 0; + CheckValidId(id.connectionId, nothing); + connections[id.connectionId].upLinkData = 0; + connections[id.connectionId].downLinkData = 0; emit OnStatsAvailable(id, 0, 0, 0, 0); - PluginHost->Send_ConnectionStatsEvent({ GetDisplayName(id), 0, 0, 0, 0 }); + PluginHost->Send_ConnectionStatsEvent({ GetDisplayName(id.connectionId), 0, 0, 0, 0 }); return; } - const optional QvConfigHandler::RenameConnection(const ConnectionId &id, const QString &newName) + const QList QvConfigHandler::GetGroupId(const ConnectionId &connId) const { - CheckConnectionExistance(id); + CheckValidId(connId, {}); + QList grps; + for (const auto &groupId : groups.keys()) + { + const auto &group = groups[groupId]; + if (group.connections.contains(connId)) + { + grps.push_back(groupId); + } + } + return grps; + } + + const std::optional QvConfigHandler::RenameConnection(const ConnectionId &id, const QString &newName) + { + CheckValidId(id, {}); OnConnectionRenamed(id, connections[id].displayName, newName); - PluginHost->Send_ConnectionEvent({ newName, connections[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed }); + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::Renamed, newName, connections[id].displayName }); connections[id].displayName = newName; - CHSaveConfigData(); + SaveConnectionConfig(); return {}; } - const optional QvConfigHandler::DeleteConnection(const ConnectionId &id) + + bool QvConfigHandler::RemoveConnectionFromGroup(const ConnectionId &id, const GroupId &gid) { - CheckConnectionExistance(id); - auto groupId = connections[id].groupId; - QFile connectionFile((groups[groupId].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + groupId.toString() + "/" + - id.toString() + QV2RAY_CONFIG_FILE_EXTENSION); - // - PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Deleted }); - connections.remove(id); - groups[groupId].connections.removeAll(id); - // - if (GlobalConfig.autoStartId == id.toString()) + CheckValidId(id, false); + LOG(MODULE_CONNECTION, "Removing connection : " + id.toString()) + if (groups[gid].connections.contains(id)) + { + auto removedEntries = groups[gid].connections.removeAll(id); + if (removedEntries > 1) + { + LOG(MODULE_CONNECTION, "Found same connection occured multiple times in a group.") + } + // Decrease reference count. + connections[id].__qvConnectionRefCount -= removedEntries; + } + + if (GlobalConfig.autoStartId == ConnectionGroupPair{ id, gid }) { GlobalConfig.autoStartId.clear(); } // - emit OnConnectionDeleted(id, groupId); + // Emit everything first then clear the connection map. + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::RemovedFromGroup, GetDisplayName(id), "" }); + + emit OnConnectionRemovedFromGroup({ id, gid }); + // - bool exists = connectionFile.exists(); - if (exists) + if (connections[id].__qvConnectionRefCount <= 0) { - bool removed = connectionFile.remove(); - if (removed) + LOG(MODULE_CONNECTION, "Fully removing a connection from cache.") + connectionRootCache.remove(id); + // + QFile connectionFile(QV2RAY_CONNECTIONS_DIR + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION); + if (connectionFile.exists()) { - return {}; + if (!connectionFile.remove()) + LOG(MODULE_CONNECTION, "Failed to remove connection config file") } - return "Failed to remove file"; + connections.remove(id); } - return tr("File does not exist."); + return true; } - const optional QvConfigHandler::MoveConnectionGroup(const ConnectionId &id, const GroupId &newGroupId) + bool QvConfigHandler::LinkConnectionWithGroup(const ConnectionId &id, const GroupId &newGroupId) { - CheckConnectionExistance(id); - auto const oldgid = connections[id].groupId; - // - QString oldPath = (groups[oldgid].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + oldgid.toString() + "/" + - id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; - // - auto newDir = (groups[newGroupId].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + newGroupId.toString() + "/"; - QString newPath = newDir + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; - // - if (!QDir(newDir).exists()) + CheckValidId(id, false); + if (groups[newGroupId].connections.contains(id)) { - QDir().mkpath(newDir); + LOG(MODULE_CONNECTION, "Connection not linked since " + id.toString() + " is already in the group " + newGroupId.toString()) + return false; } - // - if (!QFile(oldPath).rename(newPath)) - { - LOG(MODULE_FILEIO, "Cannot rename") - } - groups[oldgid].connections.removeAll(id); groups[newGroupId].connections.append(id); - connections[id].groupId = newGroupId; - // - PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Updated }); - // - emit OnConnectionGroupChanged(id, oldgid, newGroupId); - // - return {}; + connections[id].__qvConnectionRefCount++; + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::LinkedWithGroup, connections[id].displayName, "" }); + emit OnConnectionLinkedWithGroup({ id, newGroupId }); + return true; } - const optional QvConfigHandler::DeleteGroup(const GroupId &id) + bool QvConfigHandler::MoveConnectionFromToGroup(const ConnectionId &id, const GroupId &sourceGid, const GroupId &targetGid) { - CheckGroupExistance(id); + CheckValidId(id, false); + CheckValidId(targetGid, false); + CheckValidId(sourceGid, false); + // + if (!groups[sourceGid].connections.contains(id)) + { + LOG(MODULE_CONNECTION, "Trying to move a connection away from a group it does not belong to.") + return false; + } + if (groups[targetGid].connections.contains(id)) + { + LOG(MODULE_CONNECTION, "The connection: " + id.toString() + " has already been in the target group: " + targetGid.toString()) + auto removedCount = groups[sourceGid].connections.removeAll(id); + connections[id].__qvConnectionRefCount -= removedCount; + } + else + { + // If the target group does not contain this connection. + auto removedCount = groups[sourceGid].connections.removeAll(id); + connections[id].__qvConnectionRefCount -= removedCount; + // + groups[targetGid].connections.append(id); + connections[id].__qvConnectionRefCount++; + } + + emit OnConnectionRemovedFromGroup({ id, sourceGid }); + emit OnConnectionLinkedWithGroup({ id, targetGid }); + + return true; + } + + const std::optional QvConfigHandler::DeleteGroup(const GroupId &id) + { + CheckValidId(id, {}); if (!groups.contains(id) || id == NullGroupId) { return tr("Group does not exist"); @@ -307,24 +299,15 @@ namespace Qv2ray::core::handlers // Copy construct auto list = groups[id].connections; - for (auto conn : list) + for (const auto &conn : list) { - MoveConnectionGroup(conn, DefaultGroupId); + MoveConnectionFromToGroup(conn, id, DefaultGroupId); } // - if (groups[id].isSubscription) - { - QDir(QV2RAY_SUBSCRIPTION_DIR + id.toString()).removeRecursively(); - } - else - { - QDir(QV2RAY_CONNECTIONS_DIR + id.toString()).removeRecursively(); - } - // - PluginHost->Send_ConnectionEvent({ groups[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Deleted }); + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::FullyRemoved, groups[id].displayName, "" }); // groups.remove(id); - CHSaveConfigData(); + SaveConnectionConfig(); emit OnGroupDeleted(id, list); if (id == DefaultGroupId) { @@ -333,288 +316,402 @@ namespace Qv2ray::core::handlers return {}; } - const optional QvConfigHandler::StartConnection(const ConnectionId &id) + bool QvConfigHandler::StartConnection(const ConnectionGroupPair &identifier) { - CheckConnectionExistance(id); - connections[id].lastConnected = system_clock::to_time_t(system_clock::now()); - CONFIGROOT root = GetConnectionRoot(id); - return kernelHandler->StartConnection(id, root); + CheckValidId(identifier, false); + connections[identifier.connectionId].lastConnected = system_clock::to_time_t(system_clock::now()); + // + CONFIGROOT root = GetConnectionRoot(identifier.connectionId); + const auto fullConfig = RouteManager->GenerateFinalConfig(root, groups[identifier.groupId].routeConfigId); + // + auto errMsg = kernelHandler->StartConnection(identifier, fullConfig); + if (errMsg) + { + QvMessageBoxWarn(nullptr, tr("Failed to start connection"), *errMsg); + return false; + } + GlobalConfig.lastConnectedId = identifier; + return true; } void QvConfigHandler::RestartConnection() // const ConnectionId &id { - kernelHandler->RestartConnection(); + StopConnection(); + StartConnection(GlobalConfig.lastConnectedId); } void QvConfigHandler::StopConnection() // const ConnectionId &id { kernelHandler->StopConnection(); - CHSaveConfigData(); + SaveConnectionConfig(); } - bool QvConfigHandler::IsConnected(const ConnectionId &id) const - { - return kernelHandler->isConnected(id); - } - - void QvConfigHandler::OnKernelCrashed_p(const ConnectionId &id, const QString &errMessage) + void QvConfigHandler::OnKernelCrashed_p(const ConnectionGroupPair &id, const QString &errMessage) { LOG(MODULE_CORE_HANDLER, "Kernel crashed: " + errMessage) emit OnDisconnected(id); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected }); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), {}, Events::Connectivity::Disconnected }); emit OnKernelCrashed(id, errMessage); } QvConfigHandler::~QvConfigHandler() { LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor") + tcpingHelper->StopAllLatencyTest(); delete kernelHandler; - delete httpHelper; - CHSaveConfigData(); + SaveConnectionConfig(); } const CONFIGROOT QvConfigHandler::GetConnectionRoot(const ConnectionId &id) const { - CheckConnectionExistanceEx(id, CONFIGROOT()); + CheckValidId(id, CONFIGROOT()); return connectionRootCache.value(id); } - void QvConfigHandler::OnLatencyDataArrived_p(const QvTCPingResultObject &result) + void QvConfigHandler::OnLatencyDataArrived_p(const ConnectionId &id, const LatencyTestResult &result) { - CheckConnectionExistanceEx(result.connectionId, nothing); - connections[result.connectionId].latency = result.avg; - emit OnLatencyTestFinished(result.connectionId, result.avg); + CheckValidId(id, nothing); + connections[id].latency = result.avg; + emit OnLatencyTestFinished(id, result.avg); } bool QvConfigHandler::UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart) { - CheckConnectionExistanceEx(id, false); - auto const &groupId = connections[id].groupId; - CheckGroupExistanceEx(groupId, false); + CheckValidId(id, false); // - auto path = (groups[groupId].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + groupId.toString() + "/" + - id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; + auto path = QV2RAY_CONNECTIONS_DIR + "/" + id.toString() + QV2RAY_CONFIG_FILE_EXTENSION; auto content = JsonToString(root); bool result = StringToFile(content, path); // connectionRootCache[id] = root; // emit OnConnectionModified(id); - PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Updated }); - if (!skipRestart && kernelHandler->isConnected(id)) + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::Edited, connections[id].displayName, "" }); + if (!skipRestart && kernelHandler->CurrentConnection().connectionId == id) { emit RestartConnection(); } return result; } - const GroupId QvConfigHandler::CreateGroup(const QString displayName, bool isSubscription) + const GroupId QvConfigHandler::CreateGroup(const QString &displayName, bool isSubscription) { GroupId id(GenerateRandomString()); groups[id].displayName = displayName; groups[id].isSubscription = isSubscription; - groups[id].importDate = system_clock::to_time_t(system_clock::now()); - PluginHost->Send_ConnectionEvent({ displayName, "", Events::ConnectionEntry::ConnectionEvent_Created }); + groups[id].creationDate = system_clock::to_time_t(system_clock::now()); + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::Created, displayName, "" }); emit OnGroupCreated(id, displayName); - CHSaveConfigData(); + SaveConnectionConfig(); return id; } - const optional QvConfigHandler::RenameGroup(const GroupId &id, const QString &newName) + const GroupRoutingId QvConfigHandler::GetGroupRoutingId(const GroupId &id) { - CheckGroupExistance(id); + if (groups[id].routeConfigId == NullRoutingId) + { + groups[id].routeConfigId = GroupRoutingId{ GenerateRandomString() }; + } + return groups[id].routeConfigId; + } + + const std::optional QvConfigHandler::RenameGroup(const GroupId &id, const QString &newName) + { + CheckValidId(id, {}); if (!groups.contains(id)) { return tr("Group does not exist"); } OnGroupRenamed(id, groups[id].displayName, newName); - PluginHost->Send_ConnectionEvent({ newName, groups[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed }); + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::Renamed, newName, groups[id].displayName }); groups[id].displayName = newName; return {}; } - const tuple QvConfigHandler::GetSubscriptionData(const GroupId &id) const + bool QvConfigHandler::SetSubscriptionData(const GroupId &id, std::optional isSubscription, const std::optional &address, + std::optional updateInterval) { - CheckGroupExistanceEx(id, {}); - tuple result; + CheckValidId(id, false); - if (!groups[id].isSubscription) - { - return result; - } + if (isSubscription.has_value()) + groups[id].isSubscription = ACCESS_OPTIONAL_VALUE(isSubscription); - return { groups[id].address, groups[id].lastUpdated, groups[id].updateInterval }; + if (address.has_value()) + groups[id].subscriptionOption.address = ACCESS_OPTIONAL_VALUE(address); + + if (updateInterval.has_value()) + groups[id].subscriptionOption.updateInterval = ACCESS_OPTIONAL_VALUE(updateInterval); + + return true; } - bool QvConfigHandler::SetSubscriptionData(const GroupId &id, const QString &address, float updateInterval) + bool QvConfigHandler::SetSubscriptionIncludeKeywords(const GroupId &id, const QStringList &Keywords) { - CheckGroupExistanceEx(id, false); - if (!groups.contains(id)) + CheckValidId(id, false); + groups[id].subscriptionOption.IncludeKeywords.clear(); + + for (const auto &keyword : Keywords) { - return false; - } - if (!address.isEmpty()) - { - groups[id].address = address; - } - if (updateInterval != -1) - { - groups[id].updateInterval = updateInterval; + if (!keyword.trimmed().isEmpty()) + { + groups[id].subscriptionOption.IncludeKeywords.push_back(keyword); + } } return true; } - bool QvConfigHandler::UpdateSubscription(const GroupId &id) + bool QvConfigHandler::SetSubscriptionIncludeRelation(const GroupId &id, SubscriptionFilterRelation relation) { - CheckGroupExistanceEx(id, false); - if (isHttpRequestInProgress) - { - return false; - } - isHttpRequestInProgress = true; - auto data = httpHelper->Get(groups[id].address); - isHttpRequestInProgress = false; - return CHUpdateSubscription_p(id, data); - } - - bool QvConfigHandler::CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData) - { - CheckGroupExistanceEx(id, false); + CheckValidId(id, false); if (!groups.contains(id)) { return false; } - // List that is holding connection IDs to be updated. - auto subsList = SplitLines(DecodeSubscriptionString(subscriptionData)); - // - if (subsList.count() < 5) + groups[id].subscriptionOption.IncludeRelation = relation; + return true; + } + + bool QvConfigHandler::SetSubscriptionExcludeKeywords(const GroupId &id, const QStringList &Keywords) + { + CheckValidId(id, false); + groups[id].subscriptionOption.ExcludeKeywords.clear(); + for (const auto &keyword : Keywords) { - auto yes = QvMessageBoxAsk( - nullptr, tr("Update Subscription"), - tr("%1 entrie(s) have been found from the subscription source, do you want to continue?").arg(subsList.count())) == - QMessageBox::Yes; - if (!yes) + if (!keyword.trimmed().isEmpty()) { - return false; + groups[id].subscriptionOption.ExcludeKeywords.push_back(keyword); } } + return true; + } + + bool QvConfigHandler::SetSubscriptionExcludeRelation(const GroupId &id, SubscriptionFilterRelation relation) + { + CheckValidId(id, false); + groups[id].subscriptionOption.ExcludeRelation = relation; + return true; + } + + void QvConfigHandler::UpdateSubscriptionAsync(const GroupId &id) + { + CheckValidId(id, nothing); + if (!groups[id].isSubscription) + return; + asyncRequestHelper->AsyncHttpGet(groups[id].subscriptionOption.address, [=](const QByteArray &d) { + CHUpdateSubscription_p(id, d); + emit OnSubscriptionAsyncUpdateFinished(id); + }); + } + + bool QvConfigHandler::UpdateSubscription(const GroupId &id) + { + if (!groups[id].isSubscription) + return false; + const auto data = NetworkRequestHelper::HttpGet(groups[id].subscriptionOption.address); + return CHUpdateSubscription_p(id, data); + } + + bool QvConfigHandler::CHUpdateSubscription_p(const GroupId &id, const QByteArray &data) + { + CheckValidId(id, false); + if (!groups.contains(id)) + { + return false; + } + // + // ====================================================================================== Begin reading subscription + + auto _newConnections = GetConnectionConfigFromSubscription(data, GetDisplayName(id)); + if (_newConnections.count() < 5) + { + LOG(MODULE_SUBSCRIPTION, "Found a subscription with less than 5 connections.") + if (QvMessageBoxAsk(nullptr, tr("Update Subscription"), + tr("%n entrie(s) have been found from the subscription source, do you want to continue?", "", + _newConnections.count())) != QMessageBox::Yes) + + return false; + } + // + // ====================================================================================== Begin Connection Data Storage // Anyway, we try our best to preserve the connection id. QMultiMap nameMap; - QMultiMap, ConnectionId> typeMap; - for (const auto &conn : groups[id].connections) + QMultiMap, ConnectionId> typeMap; { - nameMap.insertMulti(GetDisplayName(conn), conn); - auto [protocol, host, port] = GetConnectionInfo(conn); - if (port != 0) + // Store connection type metadata into map. + for (const auto &conn : groups[id].connections) { - typeMap.insertMulti({ protocol, host, port }, conn); - } - } - QDir().mkpath(QV2RAY_SUBSCRIPTION_DIR + id.toString()); - bool hasErrorOccured = false; - // Copy construct here. - auto connectionsOrig = groups[id].connections; - groups[id].connections.clear(); - // - for (auto vmess : subsList) - { - QString errMessage; - auto ssdGroupName = GetDisplayName(id); - QString __alias; - auto conf = ConvertConfigFromString(vmess.trimmed(), &__alias, &errMessage, &ssdGroupName); - Q_UNUSED(ssdGroupName) - // Things may go wrong when updating a subscription with ssd:// link - for (auto _alias : conf.keys()) - { - for (const auto &config : conf.values(_alias)) + nameMap.insert(GetDisplayName(conn), conn); + const auto &&[protocol, host, port] = GetConnectionInfo(conn); + if (port != 0) { - if (!errMessage.isEmpty()) - { - LOG(MODULE_SUBSCRIPTION, "Processing a subscription with following error: " + errMessage) - hasErrorOccured = true; - continue; - } - bool canGetOutboundData = false; - // Should not have complex connection we assume. - auto outboundData = GetConnectionInfo(config, &canGetOutboundData); - // - // Begin guessing new ConnectionId - if (nameMap.contains(_alias)) - { - // Just go and save the connection... - LOG(MODULE_CORE_HANDLER, "Reused connection id from name: " + _alias) - auto _conn = nameMap.take(_alias); - groups[id].connections << _conn; - UpdateConnection(_conn, config); - // Remove Connection Id from the list. - connectionsOrig.removeAll(_conn); - typeMap.remove(typeMap.key(_conn)); - } - else if (canGetOutboundData && typeMap.contains(outboundData)) - { - LOG(MODULE_CORE_HANDLER, "Reused connection id from protocol/host/port pair for connection: " + _alias) - auto _conn = typeMap.take(outboundData); - groups[id].connections << _conn; - // Update Connection Properties - UpdateConnection(_conn, config); - RenameConnection(_conn, _alias); - // Remove Connection Id from the list. - connectionsOrig.removeAll(_conn); - nameMap.remove(nameMap.key(_conn)); - } - else - { - // New connection id is required since nothing matched found... - LOG(MODULE_CORE_HANDLER, "Generated new connection id for connection: " + _alias) - CreateConnection(_alias, id, config); - } - // End guessing connectionId + typeMap.insert({ protocol, host, port }, conn); } } } + // ====================================================================================== End Connection Data Storage + // + bool hasErrorOccured = false; + // Copy construct here. + auto originalConnectionIdList = groups[id].connections; + groups[id].connections.clear(); + // + decltype(_newConnections) filteredConnections; + // + for (const auto &config : _newConnections) + { + // filter connections + const bool isIncludeOperationAND = groups[id].subscriptionOption.IncludeRelation == RELATION_AND; + const bool isExcludeOperationOR = groups[id].subscriptionOption.ExcludeRelation == RELATION_OR; + // + // Initial includeConfig value + bool includeconfig = isIncludeOperationAND; + { + bool hasIncludeItemMatched = false; + for (const auto &key : groups[id].subscriptionOption.IncludeKeywords) + { + if (!key.trimmed().isEmpty()) + { + hasIncludeItemMatched = true; + // WARN: MAGIC, DO NOT TOUCH + if (!isIncludeOperationAND == config.first.contains(key.trimmed())) + { + includeconfig = !isIncludeOperationAND; + break; + } + } + } + // If includekeywords is empty then include all configs. + if (!hasIncludeItemMatched) + includeconfig = true; + } + if (includeconfig) + { + bool hasExcludeItemMatched = false; + includeconfig = isExcludeOperationOR; + for (const auto &key : groups[id].subscriptionOption.ExcludeKeywords) + { + if (!key.trimmed().isEmpty()) + { + hasExcludeItemMatched = true; + // WARN: MAGIC, DO NOT TOUCH + if (isExcludeOperationOR == config.first.contains(key.trimmed())) + { + includeconfig = !isExcludeOperationOR; + break; + } + } + } + // If excludekeywords is empty then don't exclude any configs. + if (!hasExcludeItemMatched) + includeconfig = true; + } + + if (includeconfig) + { + filteredConnections << config; + } + } + + LOG(MODULE_SUBSCRIPTION, "Filtered out less than 5 connections.") + const auto useFilteredConnections = + filteredConnections.count() > 5 || + QvMessageBoxAsk(nullptr, tr("Update Subscription"), + tr("%1 out of %n entrie(s) have been filtered out, do you want to continue?", "", _newConnections.count()) + .arg(filteredConnections.count())) == QMessageBox::Yes; + + for (const auto &config : useFilteredConnections ? filteredConnections : _newConnections) + { + const auto &_alias = config.first; + // Should not have complex connection we assume. + bool canGetOutboundData = false; + auto outboundData = GetConnectionInfo(config.second, &canGetOutboundData); + // + // ====================================================================================== Begin guessing new ConnectionId + if (nameMap.contains(_alias)) + { + // Just go and save the connection... + LOG(MODULE_CORE_HANDLER, "Reused connection id from name: " + _alias) + const auto _conn = nameMap.take(_alias); + groups[id].connections << _conn; + UpdateConnection(_conn, config.second, true); + // Remove Connection Id from the list. + originalConnectionIdList.removeAll(_conn); + typeMap.remove(typeMap.key(_conn)); + } + else if (canGetOutboundData && typeMap.contains(outboundData)) + { + LOG(MODULE_CORE_HANDLER, "Reused connection id from protocol/host/port pair for connection: " + _alias) + const auto _conn = typeMap.take(outboundData); + groups[id].connections << _conn; + // Update Connection Properties + UpdateConnection(_conn, config.second, true); + RenameConnection(_conn, _alias); + // Remove Connection Id from the list. + originalConnectionIdList.removeAll(_conn); + nameMap.remove(nameMap.key(_conn)); + } + else + { + // New connection id is required since nothing matched found... + LOG(MODULE_CORE_HANDLER, "Generated new connection id for connection: " + _alias) + CreateConnection(config.second, _alias, id, true); + } + // ====================================================================================== End guessing new ConnectionId + } // Check if anything left behind (not being updated or changed significantly) LOG(MODULE_CORE_HANDLER, "Removed old connections not have been matched.") - for (auto conn : connectionsOrig) + for (const auto &conn : originalConnectionIdList) { - LOG(MODULE_CORE_HANDLER, "Removing: " + conn.toString()) - DeleteConnection(conn); + LOG(MODULE_CORE_HANDLER, "Removing connections not in the new subscription: " + conn.toString()) + RemoveConnectionFromGroup(conn, id); } // Update the time - groups[id].lastUpdated = system_clock::to_time_t(system_clock::now()); + groups[id].lastUpdatedDate = system_clock::to_time_t(system_clock::now()); return hasErrorOccured; } - void QvConfigHandler::OnStatsDataArrived_p(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed) + void QvConfigHandler::OnStatsDataArrived_p(const ConnectionGroupPair &id, const quint64 uploadSpeed, const quint64 downloadSpeed) { - if (id == NullConnectionId) + if (id.isEmpty()) return; - connections[id].upLinkData += uploadSpeed; - connections[id].downLinkData += downloadSpeed; - emit OnStatsAvailable(id, uploadSpeed, downloadSpeed, connections[id].upLinkData, connections[id].downLinkData); - PluginHost->Send_ConnectionStatsEvent( - { GetDisplayName(id), uploadSpeed, downloadSpeed, connections[id].upLinkData, connections[id].downLinkData }); + const auto &connectionId = id.connectionId; + connections[connectionId].upLinkData += uploadSpeed; + connections[connectionId].downLinkData += downloadSpeed; + emit OnStatsAvailable(id, uploadSpeed, downloadSpeed, // + connections[connectionId].upLinkData, // + connections[connectionId].downLinkData); + PluginHost->Send_ConnectionStatsEvent({ GetDisplayName(connectionId), // + uploadSpeed, downloadSpeed, // + connections[connectionId].upLinkData, // + connections[connectionId].downLinkData }); } - const ConnectionId QvConfigHandler::CreateConnection(const QString &displayName, const GroupId &groupId, const CONFIGROOT &root, - bool skipSaveConfig) + const ConnectionGroupPair QvConfigHandler::CreateConnection(const CONFIGROOT &root, const QString &displayName, const GroupId &groupId, + bool skipSaveConfig) { LOG(MODULE_CORE_HANDLER, "Creating new connection: " + displayName) ConnectionId newId(GenerateUuid()); groups[groupId].connections << newId; - connections[newId].groupId = groupId; - connections[newId].importDate = system_clock::to_time_t(system_clock::now()); + connections[newId].creationDate = system_clock::to_time_t(system_clock::now()); connections[newId].displayName = displayName; - emit OnConnectionCreated(newId, displayName); - PluginHost->Send_ConnectionEvent({ displayName, "", Events::ConnectionEntry::ConnectionEvent_Created }); + connections[newId].__qvConnectionRefCount = 1; + emit OnConnectionCreated({ newId, groupId }, displayName); + PluginHost->Send_ConnectionEvent({ Events::ConnectionEntry::Created, displayName, "" }); UpdateConnection(newId, root); if (!skipSaveConfig) { - CHSaveConfigData(); + SaveConnectionConfig(); } - return newId; + return { newId, groupId }; } -} // namespace Qv2ray::core::handlers +} // namespace Qv2ray::core::handler + +#undef CheckIdExistance +#undef CheckGroupExistanceEx +#undef CheckGroupExistance +#undef CheckConnectionExistanceEx +#undef CheckConnectionExistance diff --git a/src/core/handler/ConfigHandler.hpp b/src/core/handler/ConfigHandler.hpp index 4f5dc971..f0d306bf 100644 --- a/src/core/handler/ConfigHandler.hpp +++ b/src/core/handler/ConfigHandler.hpp @@ -1,31 +1,29 @@ #pragma once #include "base/Qv2rayBase.hpp" -#include "common/HTTPRequestHelper.hpp" -#include "components/latency/QvTCPing.hpp" -#include "core/CoreSafeTypes.hpp" +#include "components/latency/LatencyTest.hpp" #include "core/CoreUtils.hpp" #include "core/connection/ConnectionIO.hpp" #include "core/handler/KernelInstanceHandler.hpp" -#define CheckIdExistance(type, id, val) \ - if (!type.contains(id)) \ +namespace Qv2ray::common::network +{ + class NetworkRequestHelper; +} + +#define CheckValidId(id, returnValue) \ { \ - return val; \ + if (!IsValidId(id)) \ + return returnValue; \ } -#define CheckGroupExistanceEx(id, val) CheckIdExistance(groups, id, val) -#define CheckGroupExistance(id) CheckGroupExistanceEx(id, tr("Group does not exist")) - -#define CheckConnectionExistanceEx(id, val) CheckIdExistance(connections, id, val) -#define CheckConnectionExistance(id) CheckConnectionExistanceEx(id, tr("Connection does not exist")) -namespace Qv2ray::core::handlers +namespace Qv2ray::core::handler { class QvConfigHandler : public QObject { Q_OBJECT public: - explicit QvConfigHandler(); + explicit QvConfigHandler(QObject *parent = nullptr); ~QvConfigHandler(); public slots: @@ -35,53 +33,81 @@ namespace Qv2ray::core::handlers } inline const QList Connections(const GroupId &groupId) const { - CheckGroupExistanceEx(groupId, {}); + CheckValidId(groupId, {}); return groups[groupId].connections; } - inline const QList AllGroups() const + inline QList AllGroups() const { - return groups.keys(); + auto k = groups.keys(); + std::sort(k.begin(), k.end(), [&](const auto &idA, const auto &idB) { return groups[idA].displayName < groups[idB].displayName; }); + return k; } - inline const ConnectionMetaObject GetConnectionMetaObject(const ConnectionId &id) const + inline bool IsValidId(const ConnectionId &id) const { - CheckConnectionExistanceEx(id, {}); + return connections.contains(id); + } + inline bool IsValidId(const GroupId &id) const + { + return groups.contains(id); + } + inline bool IsValidId(const ConnectionGroupPair &id) const + { + return IsValidId(id.connectionId) && IsValidId(id.groupId); + } + inline const ConnectionObject GetConnectionMetaObject(const ConnectionId &id) const + { + CheckValidId(id, {}); return connections[id]; } - inline const GroupMetaObject GetGroupMetaObject(const GroupId &id) const + inline GroupObject GetGroupMetaObject(const GroupId &id) const { - CheckGroupExistanceEx(id, {}); + CheckValidId(id, {}); return groups[id]; } - inline bool IsSubscription(const GroupId &id) const + + bool IsConnected(const ConnectionGroupPair &id) const { - CheckGroupExistanceEx(id, {}); - return groups[id].isSubscription; + return kernelHandler->CurrentConnection() == id; + } + + Q_DECL_DEPRECATED_X("ConnectionId-Only has been deprecated since GroudId is also required.") + bool IsConnected(const ConnectionId &id) const + { + return kernelHandler->CurrentConnection().connectionId == id; + } + + inline void IgnoreSubscriptionUpdate(const GroupId &group) + { + CheckValidId(group, nothing); + if (groups[group].isSubscription) + { + groups[group].lastUpdatedDate = system_clock::to_time_t(system_clock::now()); + } } // // - void CHSaveConfigData(); + void SaveConnectionConfig(); const QList Subscriptions() const; - // - // Get Options - const GroupId GetGroupIdByDisplayName(const QString &displayName) const; - /// TRY NOT TO USE THIS FUNCTION - const ConnectionId GetConnectionIdByDisplayName(const QString &displayName, const GroupId &group) const; + const QList GetGroupId(const ConnectionId &connId) const; // // Connectivity Operationss - const optional StartConnection(const ConnectionId &identifier); - void StopConnection(); // const ConnectionId &id + bool StartConnection(const ConnectionGroupPair &identifier); + void StopConnection(); void RestartConnection(); - bool IsConnected(const ConnectionId &id) const; // // Connection Operations. - bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false); void ClearGroupUsage(const GroupId &id); - void ClearConnectionUsage(const ConnectionId &id); - const optional DeleteConnection(const ConnectionId &id); - const optional RenameConnection(const ConnectionId &id, const QString &newName); - const optional MoveConnectionGroup(const ConnectionId &id, const GroupId &newGroupId); - const ConnectionId CreateConnection(const QString &displayName, const GroupId &groupId, const CONFIGROOT &root, - bool skipSaveConfig = false); + void ClearConnectionUsage(const ConnectionGroupPair &id); + // + const ConnectionGroupPair CreateConnection(const CONFIGROOT &root, const QString &displayName, const GroupId &groupId = DefaultGroupId, + bool skipSaveConfig = false); + bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false); + const std::optional RenameConnection(const ConnectionId &id, const QString &newName); + // + // Connection - Group binding + bool RemoveConnectionFromGroup(const ConnectionId &id, const GroupId &gid); + bool MoveConnectionFromToGroup(const ConnectionId &id, const GroupId &sourceGid, const GroupId &targetGid); + bool LinkConnectionWithGroup(const ConnectionId &id, const GroupId &newGroupId); // // Get Conncetion Property const CONFIGROOT GetConnectionRoot(const ConnectionId &id) const; @@ -92,66 +118,75 @@ namespace Qv2ray::core::handlers void StartLatencyTest(const ConnectionId &id); // // Group Operations - const GroupId CreateGroup(const QString displayName, bool isSubscription); - const optional DeleteGroup(const GroupId &id); - const optional RenameGroup(const GroupId &id, const QString &newName); + const GroupId CreateGroup(const QString &displayName, bool isSubscription); + const std::optional DeleteGroup(const GroupId &id); + const std::optional RenameGroup(const GroupId &id, const QString &newName); + const GroupRoutingId GetGroupRoutingId(const GroupId &id); // const optional DuplicateGroup(const GroupId &id); // // Subscriptions - bool SetSubscriptionData(const GroupId &id, const QString &address = "", float updateInterval = -1); + void UpdateSubscriptionAsync(const GroupId &id); bool UpdateSubscription(const GroupId &id); + bool SetSubscriptionData(const GroupId &id, std::optional isSubscription = std::nullopt, + const std::optional &address = std::nullopt, std::optional updateInterval = std::nullopt); + + bool SetSubscriptionIncludeKeywords(const GroupId &id, const QStringList &Keywords); + bool SetSubscriptionExcludeKeywords(const GroupId &id, const QStringList &Keywords); + bool SetSubscriptionIncludeRelation(const GroupId &id, SubscriptionFilterRelation relation); + bool SetSubscriptionExcludeRelation(const GroupId &id, SubscriptionFilterRelation relation); + // bool UpdateSubscriptionASync(const GroupId &id, bool useSystemProxy); - const tuple GetSubscriptionData(const GroupId &id) const; + // const std::tuple GetSubscriptionData(const GroupId &id) const; signals: - void OnKernelLogAvailable(const ConnectionId &id, const QString &log); - void OnStatsAvailable(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); + void OnKernelLogAvailable(const ConnectionGroupPair &id, const QString &log); + void OnStatsAvailable(const ConnectionGroupPair &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); // - void OnConnectionCreated(const ConnectionId &id, const QString &displayName); - void OnConnectionRenamed(const ConnectionId &id, const QString &originalName, const QString &newName); - void OnConnectionDeleted(const ConnectionId &id, const GroupId &originalGroupId); + void OnConnectionCreated(const ConnectionGroupPair &Id, const QString &displayName); void OnConnectionModified(const ConnectionId &id); - void OnConnectionGroupChanged(const ConnectionId &id, const GroupId &originalGroup, const GroupId &newGroup); + void OnConnectionRenamed(const ConnectionId &Id, const QString &originalName, const QString &newName); + // + void OnConnectionLinkedWithGroup(const ConnectionGroupPair &newPair); + void OnConnectionRemovedFromGroup(const ConnectionGroupPair &pairId); // void OnLatencyTestStarted(const ConnectionId &id); - void OnLatencyTestFinished(const ConnectionId &id, const uint average); + void OnLatencyTestFinished(const ConnectionId &id, const int average); // void OnGroupCreated(const GroupId &id, const QString &displayName); void OnGroupRenamed(const GroupId &id, const QString &oldName, const QString &newName); void OnGroupDeleted(const GroupId &id, const QList &connections); // - void OnSubscriptionUpdateFinished(const GroupId &id); - void OnConnected(const ConnectionId &id); - void OnDisconnected(const ConnectionId &id); - void OnKernelCrashed(const ConnectionId &id, const QString &errMessage); + void OnSubscriptionAsyncUpdateFinished(const GroupId &id); + void OnConnected(const ConnectionGroupPair &id); + void OnDisconnected(const ConnectionGroupPair &id); + void OnKernelCrashed(const ConnectionGroupPair &id, const QString &errMessage); // private slots: - void OnKernelCrashed_p(const ConnectionId &id, const QString &errMessage); - void OnLatencyDataArrived_p(const QvTCPingResultObject &data); - void OnStatsDataArrived_p(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed); + void OnKernelCrashed_p(const ConnectionGroupPair &id, const QString &errMessage); + void OnLatencyDataArrived_p(const ConnectionId &id, const LatencyTestResult &data); + void OnStatsDataArrived_p(const ConnectionGroupPair &id, const quint64 uploadSpeed, const quint64 downloadSpeed); protected: void timerEvent(QTimerEvent *event) override; private: - bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData); + bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &data); private: int saveTimerId; int pingAllTimerId; int pingConnectionTimerId; - QHash groups; - QHash connections; + QHash groups; + QHash connections; QHash connectionRootCache; private: - QvHttpRequestHelper *httpHelper; - bool isHttpRequestInProgress = false; - QvTCPingHelper *tcpingHelper; + LatencyTestHost *tcpingHelper; KernelInstanceHandler *kernelHandler; + Qv2ray::common::network::NetworkRequestHelper *asyncRequestHelper; }; - inline ::Qv2ray::core::handlers::QvConfigHandler *ConnectionManager = nullptr; -} // namespace Qv2ray::core::handlers + inline ::Qv2ray::core::handler::QvConfigHandler *ConnectionManager = nullptr; +} // namespace Qv2ray::core::handler -using namespace Qv2ray::core::handlers; +using namespace Qv2ray::core::handler; diff --git a/src/core/handler/KernelInstanceHandler.cpp b/src/core/handler/KernelInstanceHandler.cpp index 1f5b3ac3..38aeaf2d 100644 --- a/src/core/handler/KernelInstanceHandler.cpp +++ b/src/core/handler/KernelInstanceHandler.cpp @@ -1,44 +1,88 @@ #include "KernelInstanceHandler.hpp" +#include "components/port/QvPortDetector.hpp" #include "core/CoreUtils.hpp" #include "core/connection/Generation.hpp" -namespace Qv2ray::core::handlers + +namespace Qv2ray::core::handler { -#define isConnected (vCoreInstance->KernelStarted || !activeKernels.isEmpty()) +#define isConnected (vCoreInstance->KernelStarted || !activeKernels.empty()) KernelInstanceHandler::KernelInstanceHandler(QObject *parent) : QObject(parent) { KernelInstance = this; vCoreInstance = new V2rayKernelInstance(this); - connect(vCoreInstance, &V2rayKernelInstance::OnNewStatsDataArrived, this, &KernelInstanceHandler::OnStatsDataArrived_p); - connect(vCoreInstance, &V2rayKernelInstance::OnProcessOutputReadyRead, this, &KernelInstanceHandler::OnKernelLogAvailable_p); + connect(vCoreInstance, &V2rayKernelInstance::OnNewStatsDataArrived, this, &KernelInstanceHandler::OnStatsDataRcvd_p); + connect(vCoreInstance, &V2rayKernelInstance::OnProcessOutputReadyRead, this, &KernelInstanceHandler::OnKernelLog_p); connect(vCoreInstance, &V2rayKernelInstance::OnProcessErrored, this, &KernelInstanceHandler::OnKernelCrashed_p); // auto kernelList = PluginHost->GetPluginKernels(); - for (const auto &kernelInfo : kernelList.keys()) + for (const auto &internalName : kernelList.keys()) { - auto kernel = kernelList.value(kernelInfo).get(); - kernels[kernelInfo] = kernel; - connect(kernel, &QvPluginKernel::OnKernelCrashed, this, &KernelInstanceHandler::OnKernelCrashed_p); - connect(kernel, &QvPluginKernel::OnKernelLogAvaliable, this, &KernelInstanceHandler::OnKernelLogAvailable_p); + auto kernel = kernelList.value(internalName); + for (const auto &protocol : kernel) + { + if (outboundKernelMap.contains(protocol)) + { + LOG(MODULE_PLUGINHOST, "Found multiple kernel providers for a protocol: " + protocol) + continue; + } + outboundKernelMap.insert(protocol, internalName); + } } } KernelInstanceHandler::~KernelInstanceHandler() { + StopConnection(); } - std::optional KernelInstanceHandler::StartConnection(const ConnectionId &id, const CONFIGROOT &root) + std::optional KernelInstanceHandler::CheckPort(QMap hosts, QMap ports, int plugins) { - if (isConnected) + // + // Check inbound port allocation issue. + QStringList portDetectionErrorMessage; + auto portDetectionMsg = tr("Another process is using the port required to start the connection:") + NEWLINE + NEWLINE; + for (const auto &key : ports.keys()) { - StopConnection(); + auto result = components::port::CheckTCPPortStatus(hosts[key], ports[key]); + if (!result) + { + portDetectionErrorMessage << tr("Port: %1 for listening IP: %2 for inbound tag: \"%3\"") // + .arg(ports[key]) + .arg(hosts[key]) + .arg(key); + } } - activeKernels.clear(); - this->root = root; - bool isComplex = IsComplexConfig(root); - auto fullConfig = GenerateRuntimeConfig(root); + if (GlobalConfig.pluginConfig.v2rayIntegration) + { + for (int i = 0; i < plugins; i++) + { + auto result = components::port::CheckTCPPortStatus("127.0.0.1", GlobalConfig.pluginConfig.portAllocationStart + i); + if (!result) + { + portDetectionErrorMessage << tr("Port: %1 for listening IP: 127.0.0.1 for plugin integration.") + .arg(GlobalConfig.pluginConfig.portAllocationStart + i); + } + } + } + if (!portDetectionErrorMessage.isEmpty()) + { + portDetectionMsg += portDetectionErrorMessage.join(NEWLINE); + return portDetectionMsg; + } + else + { + return std::nullopt; + } + } + + std::optional KernelInstanceHandler::StartConnection(const ConnectionGroupPair &id, CONFIGROOT fullConfig) + { + StopConnection(); inboundPorts = GetConfigInboundPorts(fullConfig); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connecting }); + inboundHosts = GetConfigInboundHosts(fullConfig); + // + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Connecting }); QList> inboundInfo; for (const auto &inbound_v : fullConfig["inbounds"].toArray()) { @@ -46,17 +90,14 @@ namespace Qv2ray::core::handlers inboundInfo.push_back({ inbound["protocol"].toString(), inbound["port"].toInt(), inbound["tag"].toString() }); } // + using _k_ = Qv2rayPlugin::QvPluginKernel; if (GlobalConfig.pluginConfig.v2rayIntegration) { - if (isComplex) - { - LOG(MODULE_CONNECTION, "WARNING: Complex connection config support of this feature has not been tested.") - } - QList> pluginProcessedOutboundList; // // Process outbounds. + // { - OUTBOUNDS new_outbounds; + OUTBOUNDS processedOutbounds; auto pluginPort = GlobalConfig.pluginConfig.portAllocationStart; // /// Key = Original Outbound Tag, Value = QStringList containing new outbound lists. @@ -65,229 +106,206 @@ namespace Qv2ray::core::handlers const auto &outbound = outbound_v.toObject(); const auto &outProtocol = outbound["protocol"].toString(); // - if (!kernels.contains(outProtocol)) + if (!outboundKernelMap.contains(outProtocol)) { // Normal outbound, or the one without a plugin supported. - new_outbounds.push_back(outbound); + // Marked as processed. + processedOutbounds.push_back(outbound); + LOG(MODULE_CONNECTION, "Outbound protocol " + outProtocol + " is not a registered plugin outbound.") continue; } - LOG(MODULE_CONNECTION, "Get kernel plugin: " + outProtocol) - auto &kernel = kernels[outProtocol]; - disconnect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p); - activeKernels.insert(outProtocol, kernel); - // - QMap pluginInboundPort; - const auto &originalOutboundTag = outbound["tag"].toString(); - for (const auto &[inProtocol, inPort, inTag] : inboundInfo) { - if (!QStringList{ "http", "socks" }.contains(inProtocol)) - continue; - pluginInboundPort.insert(inProtocol, pluginPort); - LOG(MODULE_VCORE, "Plugin Integration: " + QSTRN(pluginPort) + " = " + inProtocol + "(" + inTag + ") --> " + outProtocol) - // - const auto &freedomTag = "plugin_" + inTag + "_" + inProtocol + "-" + QSTRN(inPort) + "_" + QSTRN(pluginPort); - const auto &pluginOutSettings = GenerateHTTPSOCKSOut("127.0.0.1", pluginPort, false, "", ""); - const auto &direct = GenerateOutboundEntry(inProtocol, pluginOutSettings, {}, {}, "0.0.0.0", freedomTag); - // - // Add the integration outbound to the list. - new_outbounds.push_back(direct); - - LOG(MODULE_CONNECTION, "Appended originalOutboundTag, inTag, freedomTag into processedOutboundList") - pluginProcessedOutboundList.append({ originalOutboundTag, inTag, freedomTag }); - pluginPort++; + LOG(MODULE_CONNECTION, "Creating kernel plugin instance for protocol" + outProtocol) + auto kernel = PluginHost->CreatePluginKernel(outboundKernelMap[outProtocol]); + // New object does not need disconnect? + // disconnect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p); + activeKernels[outProtocol] = std::move(kernel); } + // + // + QMap _inboundSettings; + _inboundSettings[_k_::KERNEL_HTTP_ENABLED] = false; + _inboundSettings[_k_::KERNEL_SOCKS_ENABLED] = true; + _inboundSettings.insert(_k_::KERNEL_SOCKS_PORT, pluginPort); + LOG(MODULE_VCORE, "V2rayIntegration: " + QSTRN(pluginPort) + "=" + outProtocol) + // + const auto pluginOutSettings = GenerateHTTPSOCKSOut("127.0.0.1", pluginPort, false, "", ""); + const auto pluginOut = GenerateOutboundEntry("socks", pluginOutSettings, {}, {}, "0.0.0.0", outbound["tag"].toString()); + // + // Add the integration outbound to the list. + processedOutbounds.push_back(pluginOut); + pluginPort++; + // + _inboundSettings[_k_::KERNEL_SOCKS_UDP_ENABLED] = GlobalConfig.inboundConfig.socksSettings.enableUDP; + _inboundSettings[_k_::KERNEL_SOCKS_LOCAL_ADDRESS] = GlobalConfig.inboundConfig.socksSettings.localIP; + _inboundSettings[_k_::KERNEL_LISTEN_ADDRESS] = "127.0.0.1"; LOG(MODULE_CONNECTION, "Sending connection settings to kernel.") - kernel->SetConnectionSettings(GlobalConfig.inboundConfig.listenip, pluginInboundPort, outbound["settings"].toObject()); + activeKernels[outProtocol]->SetConnectionSettings(_inboundSettings, outbound["settings"].toObject()); } LOG(MODULE_CONNECTION, "Applying new outbound settings.") - fullConfig["outbounds"] = new_outbounds; + fullConfig["outbounds"] = processedOutbounds; } - // - // Process routing entries - { - LOG(MODULE_CONNECTION, "Started processing route tables.") - QJsonArray newRules; - auto unprocessedOutbound = pluginProcessedOutboundList; - const auto rules = fullConfig["routing"].toObject()["rules"].toArray(); - for (auto i = 0; i < rules.count(); i++) - { - const auto rule = rules.at(i).toObject(); - // - bool ruleProcessed = false; - for (const auto &[originalTag, inboundTag, newOutboundTag] : pluginProcessedOutboundList) - { - // Check if a rule corresponds to the plugin outbound. - if (rule["outboundTag"] == originalTag) - { - LOG(MODULE_CONNECTION, "Replacing existed plugin outbound rule.") - auto newRule = rule; - newRule["outboundTag"] = newOutboundTag; - newRule["inboundTag"] = QJsonArray{ inboundTag }; - newRules.push_back(newRule); - ruleProcessed = true; - unprocessedOutbound.removeOne({ originalTag, inboundTag, newOutboundTag }); - } - } - if (!ruleProcessed) - { - newRules.append(rule); - } - } - - for (const auto &[originalTag, inboundTag, newOutboundTag] : unprocessedOutbound) - { - LOG(MODULE_CONNECTION, "Adding new plugin outbound rule.") - QJsonObject integrationRule; - integrationRule["type"] = "field"; - integrationRule["outboundTag"] = newOutboundTag; - integrationRule["inboundTag"] = QJsonArray{ inboundTag }; - newRules.push_back(integrationRule); - } - auto routing = fullConfig["routing"].toObject(); - routing["rules"] = newRules; - fullConfig["routing"] = routing; - } - // ================================================================================================ - // - currentConnectionId = id; - lastConnectionId = id; - bool success = true; - for (auto &kernel : activeKernels.keys()) - { - LOG(MODULE_CONNECTION, "Starting kernel: " + kernel) - bool status = activeKernels[kernel]->StartKernel(); - success = success && status; - if (!status) - { - LOG(MODULE_CONNECTION, "Plugin Kernel: " + kernel + " failed to start.") - break; - } - } - if (!success) - { - StopConnection(); - return tr("A plugin kernel failed to start. Please check the outbound settings."); - } - // - auto result = vCoreInstance->StartConnection(fullConfig); - // - if (!result.has_value()) - { - emit OnConnected(currentConnectionId); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected }); - } - else - { - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Disconnected }); - } - return result; + RemoveEmptyMuxFilter(fullConfig); } - else + // + // ======================================================================= Start Kernels + // { - LOG(MODULE_CONNECTION, "Starting kernel without V2ray Integration") - auto firstOutbound = fullConfig["outbounds"].toArray().first().toObject(); - const auto protocol = firstOutbound["protocol"].toString(); - if (kernels.contains(protocol)) + const auto portResult = CheckPort(inboundHosts, inboundPorts, activeKernels.size()); + if (portResult) { - LOG(MODULE_CONNECTION, "Found existing kernel for: " + protocol) - auto &kernel = kernels[firstOutbound["protocol"].toString()]; - activeKernels[protocol] = kernel; - QMap pluginInboundPort; + LOG(MODULE_CONNECTION, ACCESS_OPTIONAL_VALUE(portResult)) + return portResult; + } + auto firstOutbound = fullConfig["outbounds"].toArray().first().toObject(); + const auto firstOutboundProtocol = firstOutbound["protocol"].toString(); + if (GlobalConfig.pluginConfig.v2rayIntegration) + { + LOG(MODULE_VCORE, "Starting kernels with V2rayIntegration.") + bool hasAllKernelStarted = true; + for (auto &[kernel, kernelObject] : activeKernels) + { + LOG(MODULE_CONNECTION, "Starting kernel: " + kernel) + bool status = kernelObject->StartKernel(); + connect(kernelObject.get(), &QvPluginKernel::OnKernelCrashed, this, &KernelInstanceHandler::OnKernelCrashed_p); + connect(kernelObject.get(), &QvPluginKernel::OnKernelLogAvailable, this, &KernelInstanceHandler::OnKernelLog_p); + hasAllKernelStarted = hasAllKernelStarted && status; + if (!status) + { + LOG(MODULE_CONNECTION, "Plugin Kernel: " + kernel + " failed to start.") + break; + } + } + if (!hasAllKernelStarted) + { + StopConnection(); + return tr("A plugin kernel failed to start. Please check the outbound settings."); + } + currentId = id; + // + // Also start V2ray-core. + auto result = vCoreInstance->StartConnection(fullConfig); + // + if (result.has_value()) + { + StopConnection(); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Disconnected }); + return result; + } + else + { + emit OnConnected(id); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Connected }); + } + } + else if (outboundKernelMap.contains(firstOutboundProtocol)) + { + LOG(MODULE_CONNECTION, "Starting kernel " + firstOutboundProtocol + " without V2ray Integration") + { + auto kernel = PluginHost->CreatePluginKernel(outboundKernelMap[firstOutbound["protocol"].toString()]); + activeKernels[firstOutboundProtocol] = std::move(kernel); + } +#define kernel (activeKernels[firstOutboundProtocol].get()) + connect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataRcvd_p); + connect(kernel, &QvPluginKernel::OnKernelCrashed, this, &KernelInstanceHandler::OnKernelCrashed_p); + connect(kernel, &QvPluginKernel::OnKernelLogAvailable, this, &KernelInstanceHandler::OnKernelLog_p); +#undef kernel + currentId = id; + // + QMap pluginInboundPort; + for (const auto &[_protocol, _port, _tag] : inboundInfo) { - pluginInboundPort[_protocol] = _port; + if (_protocol != "http" && _protocol != "socks") + continue; + pluginInboundPort[_k_::KERNEL_HTTP_ENABLED] = pluginInboundPort[_k_::KERNEL_HTTP_ENABLED].toBool() || _protocol == "http"; + pluginInboundPort[_k_::KERNEL_SOCKS_ENABLED] = pluginInboundPort[_k_::KERNEL_SOCKS_ENABLED].toBool() || _protocol == "socks"; + pluginInboundPort.insert(_protocol.toLower() == "http" ? _k_::KERNEL_HTTP_PORT : _k_::KERNEL_SOCKS_PORT, _port); } - connect(kernel, &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p); - currentConnectionId = id; - lastConnectionId = id; - kernel->SetConnectionSettings(GlobalConfig.inboundConfig.listenip, pluginInboundPort, firstOutbound["settings"].toObject()); - bool result = kernel->StartKernel(); - if (result) + + pluginInboundPort[_k_::KERNEL_SOCKS_UDP_ENABLED] = GlobalConfig.inboundConfig.socksSettings.enableUDP; + pluginInboundPort[_k_::KERNEL_SOCKS_LOCAL_ADDRESS] = GlobalConfig.inboundConfig.socksSettings.localIP; + pluginInboundPort[_k_::KERNEL_LISTEN_ADDRESS] = GlobalConfig.inboundConfig.listenip; + // + activeKernels[firstOutboundProtocol]->SetConnectionSettings(pluginInboundPort, firstOutbound["settings"].toObject()); + + bool kernelStarted = activeKernels[firstOutboundProtocol]->StartKernel(); + if (kernelStarted) { - emit OnConnected(currentConnectionId); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected }); - return {}; + emit OnConnected(id); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Connected }); } else { return tr("A plugin kernel failed to start. Please check the outbound settings."); + StopConnection(); } } else { - LOG(MODULE_CONNECTION, "Starting V2ray without kernel") - currentConnectionId = id; - lastConnectionId = id; + LOG(MODULE_CONNECTION, "Starting V2ray without plugin.") + currentId = id; auto result = vCoreInstance->StartConnection(fullConfig); if (result.has_value()) { - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Disconnected }); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Disconnected }); + StopConnection(); + return result; } else { - emit OnConnected(currentConnectionId); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected }); + emit OnConnected(id); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id.connectionId), inboundPorts, Events::Connectivity::Connected }); } - return result; } } - } - - void KernelInstanceHandler::RestartConnection() - { - StopConnection(); - StartConnection(lastConnectionId, root); + // Return + return std::nullopt; } void KernelInstanceHandler::OnKernelCrashed_p(const QString &msg) { + emit OnCrashed(currentId, msg); + emit OnDisconnected(currentId); StopConnection(); - emit OnCrashed(currentConnectionId, msg); - emit OnDisconnected(currentConnectionId); - lastConnectionId = currentConnectionId; - currentConnectionId = NullConnectionId; } - void KernelInstanceHandler::OnKernelLogAvailable_p(const QString &log) + void KernelInstanceHandler::OnKernelLog_p(const QString &log) { - emit OnKernelLogAvailable(currentConnectionId, log); + emit OnKernelLogAvailable(currentId, log); } void KernelInstanceHandler::StopConnection() { if (isConnected) { - PluginHost->Send_ConnectivityEvent( - { GetDisplayName(currentConnectionId), inboundPorts, Events::Connectivity::QvConnecticity_Disconnecting }); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(currentId.connectionId), inboundPorts, Events::Connectivity::Disconnecting }); if (vCoreInstance->KernelStarted) { vCoreInstance->StopConnection(); } - // - for (const auto &kernel : activeKernels.keys()) + for (const auto &[kernel, kernelObject] : activeKernels) { LOG(MODULE_CONNECTION, "Stopping plugin kernel: " + kernel) - disconnect(activeKernels[kernel], &QvPluginKernel::OnKernelStatsAvailable, this, &KernelInstanceHandler::OnStatsDataArrived_p); - activeKernels[kernel]->StopKernel(); + kernelObject->StopKernel(); } - // Copy - ConnectionId id = currentConnectionId; - currentConnectionId = NullConnectionId; - emit OnDisconnected(id); - PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Disconnected }); + emit OnDisconnected(currentId); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(currentId.connectionId), inboundPorts, Events::Connectivity::Disconnected }); } else { LOG(MODULE_CORE_HANDLER, "Cannot disconnect when there's nothing connected.") } + currentId.clear(); + activeKernels.clear(); } - void KernelInstanceHandler::OnStatsDataArrived_p(const quint64 uploadSpeed, const quint64 downloadSpeed) + void KernelInstanceHandler::OnStatsDataRcvd_p(const quint64 uploadSpeed, const quint64 downloadSpeed) { if (isConnected) { - emit OnStatsDataAvailable(currentConnectionId, uploadSpeed, downloadSpeed); + emit OnStatsDataAvailable(currentId, uploadSpeed, downloadSpeed); } } -} // namespace Qv2ray::core::handlers +} // namespace Qv2ray::core::handler diff --git a/src/core/handler/KernelInstanceHandler.hpp b/src/core/handler/KernelInstanceHandler.hpp index fa1d18c8..8af88521 100644 --- a/src/core/handler/KernelInstanceHandler.hpp +++ b/src/core/handler/KernelInstanceHandler.hpp @@ -1,12 +1,11 @@ #pragma once #include "components/plugins/QvPluginHost.hpp" -#include "core/CoreSafeTypes.hpp" #include "core/kernel/V2rayKernelInteractions.hpp" #include #include -namespace Qv2ray::core::handlers +namespace Qv2ray::core::handler { class KernelInstanceHandler : public QObject { @@ -15,42 +14,48 @@ namespace Qv2ray::core::handlers explicit KernelInstanceHandler(QObject *parent = nullptr); ~KernelInstanceHandler(); - std::optional StartConnection(const ConnectionId &id, const CONFIGROOT &root); - void RestartConnection(); + std::optional StartConnection(const ConnectionGroupPair &id, CONFIGROOT root); void StopConnection(); - const ConnectionId CurrentConnection() const + const ConnectionGroupPair CurrentConnection() const { - return currentConnectionId; + return currentId; } - bool isConnected(const ConnectionId &id) const + int ActivePluginKernelsCount() const { - return id == currentConnectionId; + return activeKernels.size(); } const QMap InboundPorts() const { return inboundPorts; } + const QMap InboundHosts() const + { + return inboundHosts; + } signals: - void OnConnected(const ConnectionId &id); - void OnDisconnected(const ConnectionId &id); - void OnCrashed(const ConnectionId &id, const QString &errMessage); - void OnStatsDataAvailable(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed); - void OnKernelLogAvailable(const ConnectionId &id, const QString &log); + void OnConnected(const ConnectionGroupPair &id); + void OnDisconnected(const ConnectionGroupPair &id); + void OnCrashed(const ConnectionGroupPair &id, const QString &errMessage); + void OnStatsDataAvailable(const ConnectionGroupPair &id, const quint64 uploadSpeed, const quint64 downloadSpeed); + void OnKernelLogAvailable(const ConnectionGroupPair &id, const QString &log); private slots: void OnKernelCrashed_p(const QString &msg); - void OnKernelLogAvailable_p(const QString &log); - void OnStatsDataArrived_p(const quint64 uploadSpeed, const quint64 downloadSpeed); + void OnKernelLog_p(const QString &log); + void OnStatsDataRcvd_p(const quint64 uploadSpeed, const quint64 downloadSpeed); private: - QMap kernels; - QMap activeKernels; + static std::optional CheckPort(QMap hosts, QMap ports, int plugins); + + private: + QMap outboundKernelMap; + // Since QMap does not support std::unique_ptr, we use std::map<> + std::map> activeKernels; QMap inboundPorts; - CONFIGROOT root; + QMap inboundHosts; V2rayKernelInstance *vCoreInstance = nullptr; - ConnectionId currentConnectionId = NullConnectionId; - ConnectionId lastConnectionId = NullConnectionId; + ConnectionGroupPair currentId = {}; }; inline const KernelInstanceHandler *KernelInstance; -} // namespace Qv2ray::core::handlers +} // namespace Qv2ray::core::handler diff --git a/src/core/handler/RouteHandler.cpp b/src/core/handler/RouteHandler.cpp new file mode 100644 index 00000000..b4a67ace --- /dev/null +++ b/src/core/handler/RouteHandler.cpp @@ -0,0 +1,439 @@ +#include "RouteHandler.hpp" + +#include "common/QvHelpers.hpp" +#include "core/CoreUtils.hpp" +#include "core/connection/Generation.hpp" +#include "core/handler/ConfigHandler.hpp" +namespace Qv2ray::core::handler +{ + RouteHandler::RouteHandler(QObject *parent) : QObject(parent) + { + const auto routesJson = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + "routes.json")); + for (const auto &routeId : routesJson.keys()) + { + configs.insert(GroupRoutingId{ routeId }, GroupRoutingConfig::fromJson(routesJson.value(routeId).toObject())); + } + } + + RouteHandler::~RouteHandler() + { + SaveRoutes(); + } + + void RouteHandler::SaveRoutes() const + { + QJsonObject routingObject; + for (const auto &key : configs.keys()) + { + routingObject[key.toString()] = configs[key].toJson(); + } + StringToFile(JsonToString(routingObject), QV2RAY_CONFIG_DIR + "routes.json"); + } + + bool RouteHandler::SetDNSSettings(const GroupRoutingId &id, bool overrideGlobal, const QvConfig_DNS &dns) + { + configs[id].overrideDNS = overrideGlobal; + configs[id].dnsConfig = dns; + return true; + } + bool RouteHandler::SetAdvancedRouteSettings(const GroupRoutingId &id, bool overrideGlobal, const QvConfig_Route &route) + { + configs[id].overrideRoute = overrideGlobal; + configs[id].routeConfig = route; + return true; + } + + // -------------------------- BEGIN CONFIG GENERATIONS + ROUTING RouteHandler::GenerateRoutes(bool enableProxy, bool bypassCN, const QString &outTag, const QvConfig_Route &routeConfig) const + { + ROUTING root; + root.insert("domainStrategy", routeConfig.domainStrategy); + // + // For Rules list + ROUTERULELIST rulesList; + + // Private IPs should always NOT TO PROXY! + rulesList.append(GenerateSingleRouteRule("geoip:private", false, OUTBOUND_TAG_DIRECT)); + // + if (!enableProxy) + { + // This is added to disable all proxies, as a alternative influence of #64 + rulesList.append(GenerateSingleRouteRule("regexp:.*", true, OUTBOUND_TAG_DIRECT)); + rulesList.append(GenerateSingleRouteRule("0.0.0.0/0", false, OUTBOUND_TAG_DIRECT)); + rulesList.append(GenerateSingleRouteRule("::/0", false, OUTBOUND_TAG_DIRECT)); + } + else + { + // + // Blocked. + if (!routeConfig.ips.block.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.ips.block, false, OUTBOUND_TAG_BLACKHOLE)); + } + if (!routeConfig.domains.block.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.domains.block, true, OUTBOUND_TAG_BLACKHOLE)); + } + // + // Proxied + if (!routeConfig.ips.proxy.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.ips.proxy, false, outTag)); + } + if (!routeConfig.domains.proxy.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.domains.proxy, true, outTag)); + } + // + // Directed + if (!routeConfig.ips.direct.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.ips.direct, false, OUTBOUND_TAG_DIRECT)); + } + if (!routeConfig.domains.direct.isEmpty()) + { + rulesList.append(GenerateSingleRouteRule(routeConfig.domains.direct, true, OUTBOUND_TAG_DIRECT)); + } + // + // Check if CN needs proxy, or direct. + if (bypassCN) + { + // No proxy agains CN addresses. + rulesList.append(GenerateSingleRouteRule("geoip:cn", false, OUTBOUND_TAG_DIRECT)); + rulesList.append(GenerateSingleRouteRule("geosite:cn", true, OUTBOUND_TAG_DIRECT)); + } + } + + root.insert("rules", rulesList); + return root; + } + // -------------------------- END CONFIG GENERATIONS + // + // BEGIN RUNTIME CONFIG GENERATION + // We need copy construct here + CONFIGROOT RouteHandler::GenerateFinalConfig(const ConnectionGroupPair &pair) const + { + return GenerateFinalConfig(ConnectionManager->GetConnectionRoot(pair.connectionId), ConnectionManager->GetGroupRoutingId(pair.groupId)); + } + CONFIGROOT RouteHandler::GenerateFinalConfig(CONFIGROOT root, const GroupRoutingId &routingId) const + { + const auto &config = configs.contains(routingId) ? configs[routingId] : GlobalConfig.defaultRouteConfig; + // + const auto &connConf = config.overrideConnectionConfig ? config.connectionConfig : GlobalConfig.defaultRouteConfig.connectionConfig; + const auto &dnsConf = config.overrideDNS ? config.dnsConfig : GlobalConfig.defaultRouteConfig.dnsConfig; + const auto &routeConf = config.overrideRoute ? config.routeConfig : GlobalConfig.defaultRouteConfig.routeConfig; + const auto &fpConf = config.overrideForwardProxyConfig ? config.forwardProxyConfig : GlobalConfig.defaultRouteConfig.forwardProxyConfig; + // + // See: https://github.com/Qv2ray/Qv2ray/issues/129 + // routeCountLabel in Mainwindow makes here failed to ENOUGH-ly + // check the routing tables + // + // Check if is complex BEFORE adding anything. + bool isComplex = IsComplexConfig(root); + // + // + // logObject.insert("access", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ACCESS_LOG_FILENAME); + // logObject.insert("error", QV2RAY_CONFIG_PATH + QV2RAY_VCORE_LOG_DIRNAME + QV2RAY_VCORE_ERROR_LOG_FILENAME); + QJsonIO::SetValue(root, V2rayLogLevel[GlobalConfig.logLevel], "log", "loglevel"); + // + // Since Qv2ray does not support settings DNS manually for now. + // These settings are being added for both complex config AND simple config. + if (root.contains("dns") && !root.value("dns").toObject().isEmpty()) + { + // We assume the users are using THEIR DNS settings. + LOG(MODULE_CONNECTION, "Found DNS settings specified manually, skipping inserting GlobalConfig") + } + else + { + root.insert("dns", GenerateDNS(connConf.withLocalDNS, dnsConf)); + } + // + // + // If inbounds list is empty we append our global configured inbounds to the config. + // The setting applies to BOTH complex config AND simple config. + // Just to ensure there's AT LEAST 1 possible inbound is being configured. + if (!root.contains("inbounds") || root.value("inbounds").toArray().empty()) + { +#define INCONF GlobalConfig.inboundConfig + INBOUNDS inboundsList; + const QJsonObject sniffingOff{ { "enabled", false } }; + const QJsonObject sniffingOn{ { "enabled", true }, { "destOverride", QJsonArray{ "http", "tls" } } }; + + // HTTP Inbound + if (GlobalConfig.inboundConfig.useHTTP) + { + const auto httpInBoundObject = // + GenerateInboundEntry(INCONF.listenip, // + INCONF.httpSettings.port, // + "http", // + INBOUNDSETTING{}, // + "http_IN", // + { INCONF.httpSettings.sniffing ? sniffingOn : sniffingOff }); + if (INCONF.httpSettings.useAuth) + { + QJsonIO::SetValue(httpInBoundObject, GenerateHTTPIN({ INCONF.httpSettings.account }), "settings"); + } + + inboundsList.append(httpInBoundObject); + } + + // SOCKS Inbound + if (INCONF.useSocks) + { + const auto socksInBoundObject = // + GenerateInboundEntry(INCONF.listenip, // + INCONF.socksSettings.port, // + "socks", // + GenerateSocksIN(INCONF.socksSettings.useAuth ? "password" : "noauth", // + { INCONF.socksSettings.account }, // + INCONF.socksSettings.enableUDP, // + INCONF.socksSettings.localIP), // + "socks_IN", // + { INCONF.socksSettings.sniffing ? sniffingOn : sniffingOff }); + inboundsList.append(socksInBoundObject); + } + + // TPROXY + if (INCONF.useTPROXY) + { + QList networks; + if (INCONF.tProxySettings.hasTCP) + networks << "tcp"; + if (INCONF.tProxySettings.hasUDP) + networks << "udp"; + const auto tproxy_network = networks.join(","); + // tProxy IPv4 Settings + { + LOG(MODULE_CONNECTION, "Processing tProxy IPv4 inbound") + INBOUND tProxyIn = GenerateInboundEntry(INCONF.tProxySettings.tProxyIP, // + INCONF.tProxySettings.port, // + "dokodemo-door", // + GenerateDokodemoIN("", 0, tproxy_network, 0, true, 0), // + "tproxy_IN", // + { + { "enabled", true }, // + { "destOverride", QJsonArray{ "http", "tls" } } // + }); + tProxyIn.insert("streamSettings", QJsonObject{ { "sockopt", QJsonObject{ { "tproxy", INCONF.tProxySettings.mode } } } }); + inboundsList.append(tProxyIn); + } + if (!INCONF.tProxySettings.tProxyV6IP.isEmpty()) + { + LOG(MODULE_CONNECTION, "Processing tProxy IPv6 inbound") + INBOUND tProxyIn = GenerateInboundEntry(INCONF.tProxySettings.tProxyV6IP, // + INCONF.tProxySettings.port, // + "dokodemo-door", // + GenerateDokodemoIN("", 0, tproxy_network, 0, true, 0), // + "tproxy_IN_V6", // + { + { "enabled", true }, // + { "destOverride", QJsonArray{ "http", "tls" } } // + }); + tProxyIn.insert("streamSettings", QJsonObject{ { "sockopt", QJsonObject{ { "tproxy", INCONF.tProxySettings.mode } } } }); + inboundsList.append(tProxyIn); + } + } + + root["inbounds"] = inboundsList; + DEBUG(MODULE_CONNECTION, "Added global config inbounds to the config") + } +#undef INCONF + // Process every inbounds to make sure a tag is configured, fixed + // API 0 speed issue when no tag is configured. + INBOUNDS newTaggedInbounds(root["inbounds"].toArray()); + + for (auto i = 0; i < newTaggedInbounds.count(); i++) + { + auto _inboundItem = newTaggedInbounds[i].toObject(); + if (!_inboundItem.contains("tag") || _inboundItem["tag"].toString().isEmpty()) + { + LOG(MODULE_SETTINGS, "Adding a tag to an inbound.") + _inboundItem["tag"] = GenerateRandomString(8); + newTaggedInbounds[i] = _inboundItem; + } + } + + root["inbounds"] = newTaggedInbounds; + // + // + // Note: The part below always makes the whole functionality in + // trouble...... BE EXTREME CAREFUL when changing these code + // below... + if (isComplex) + { + // For some config files that has routing entries already. + // We DO NOT add extra routings. + // + // HOWEVER, we need to verify the QV2RAY_RULE_ENABLED entry. + // And what's more, process (by removing unused items) from a + // rule object. + ROUTING routing(root["routing"].toObject()); + ROUTERULELIST rules; + LOG(MODULE_CONNECTION, "Processing an existing routing table.") + + for (const auto &_rule : routing["rules"].toArray()) + { + auto _b = _rule.toObject(); + + if (_b.contains("QV2RAY_RULE_USE_BALANCER")) + { + // We use balancer, or the normal outbound + _b.remove(_b["QV2RAY_RULE_USE_BALANCER"].toBool(false) ? "outboundTag" : "balancerTag"); + } + else + { + LOG(MODULE_SETTINGS, "We found a rule without QV2RAY_RULE_USE_BALANCER, so didn't process it.") + } + + // If this entry has been disabled. + if (_b.contains("QV2RAY_RULE_ENABLED") && _b["QV2RAY_RULE_ENABLED"].toBool() == false) + { + LOG(MODULE_SETTINGS, "Discarded a rule as it's been set DISABLED") + } + else + { + rules.append(_b); + } + } + + routing["rules"] = rules; + root["routing"] = routing; + } + else + { + LOG(MODULE_CONNECTION, "Inserting default values to simple config") + if (root["outbounds"].toArray().count() != 1) + { + // There are no ROUTING but 2 or more outbounds.... This is rare, but possible. + LOG(MODULE_CONNECTION, "WARN: This message usually indicates the config file has logic errors:") + LOG(MODULE_CONNECTION, "WARN: --> The config file has NO routing section, however more than 1 outbounds are detected.") + } + // + auto tag = getTag(OUTBOUND(QJsonIO::GetValue(root, "outbounds", 0).toObject())); + if (tag.isEmpty()) + { + LOG(MODULE_CONNECTION, "Applying workaround when an outbound tag is empty") + tag = GenerateRandomString(15); + QJsonIO::SetValue(root, tag, "outbounds", 0, "tag"); + } + root["routing"] = GenerateRoutes(connConf.enableProxy, connConf.bypassCN, tag, routeConf); + // + // Process forward proxy + // + if (fpConf.enableForwardProxy) + { + auto outboundArray = root["outbounds"].toArray(); + auto firstOutbound = outboundArray.first().toObject(); + + if (firstOutbound[QV2RAY_USE_FPROXY_KEY].toBool(false)) + { + LOG(MODULE_CONNECTION, "Applying forward proxy to current connection.") + PROXYSETTING proxy; + proxy["tag"] = OUTBOUND_TAG_FORWARD_PROXY; + firstOutbound["proxySettings"] = proxy; + + // FP Outbound. + if (fpConf.type.toLower() == "http" || fpConf.type.toLower() == "socks") + { + auto fpOutbound = + GenerateHTTPSOCKSOut(fpConf.serverAddress, fpConf.port, fpConf.useAuth, fpConf.username, fpConf.password); + outboundArray.push_back( + GenerateOutboundEntry(fpConf.type.toLower(), fpOutbound, {}, {}, "0.0.0.0", OUTBOUND_TAG_FORWARD_PROXY)); + } + else if (!fpConf.type.isEmpty()) + { + DEBUG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + fpConf.type) + } + else + { + DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") + } + } + else + { + // Remove proxySettings from firstOutbound + firstOutbound.remove("proxySettings"); + } + + outboundArray.replace(0, firstOutbound); + root["outbounds"] = outboundArray; + } +#undef fpConf + // + // Process FREEDOM and BLACKHOLE outbound + { + OUTBOUNDS outbounds(root["outbounds"].toArray()); + const auto freeDS = (connConf.v2rayFreedomDNS) ? "UseIP" : "AsIs"; + outbounds.append(GenerateOutboundEntry("freedom", GenerateFreedomOUT(freeDS, ":0", 0), {}, {}, "0.0.0.0", OUTBOUND_TAG_DIRECT)); + outbounds.append(GenerateOutboundEntry("blackhole", GenerateBlackHoleOUT(false), {}, {}, "0.0.0.0", OUTBOUND_TAG_BLACKHOLE)); + root["outbounds"] = outbounds; + } + // + // Connection Filters + // + { + if (GlobalConfig.inboundConfig.useTPROXY && GlobalConfig.inboundConfig.tProxySettings.dnsIntercept) + { + DNSInterceptFilter(root, !GlobalConfig.inboundConfig.tProxySettings.tProxyV6IP.isEmpty()); + } + + if (GlobalConfig.inboundConfig.useTPROXY && GlobalConfig.outboundConfig.mark > 0) + { + OutboundMarkSettingFilter(GlobalConfig.outboundConfig.mark, root); + } + + if (connConf.bypassBT) + { + BypassBTFilter(root); + } + // Process mKCP seed. + mKCPSeedFilter(root); + + // Remove empty Mux object from settings. + RemoveEmptyMuxFilter(root); + } + } + + // Let's process some api features. + if (GlobalConfig.kernelConfig.enableAPI) + { + // + // Stats + // + root.insert("stats", QJsonObject()); + // + // Routes + // + QJsonObject routing = root["routing"].toObject(); + QJsonArray routingRules = routing["rules"].toArray(); + QJsonObject APIRouteRoot{ { "type", "field" }, // + { "outboundTag", API_TAG_DEFAULT }, // + { "inboundTag", QJsonArray{ API_TAG_INBOUND } } }; + routingRules.push_front(APIRouteRoot); + routing["rules"] = routingRules; + root["routing"] = routing; + // + // Policy + // + QJsonIO::SetValue(root, true, "policy", "system", "statsInboundUplink"); + QJsonIO::SetValue(root, true, "policy", "system", "statsOutboundUplink"); + QJsonIO::SetValue(root, true, "policy", "system", "statsInboundDownlink"); + QJsonIO::SetValue(root, true, "policy", "system", "statsOutboundDownlink"); + // + // Inbounds + // + INBOUNDS inbounds(root["inbounds"].toArray()); + QJsonObject fakeDocodemoDoor{ { "address", "127.0.0.1" } }; + const auto apiInboundsRoot = GenerateInboundEntry("127.0.0.1", GlobalConfig.kernelConfig.statsPort, "dokodemo-door", + INBOUNDSETTING(fakeDocodemoDoor), API_TAG_INBOUND); + inbounds.push_front(apiInboundsRoot); + root["inbounds"] = inbounds; + // + // API + // + root["api"] = GenerateAPIEntry(API_TAG_DEFAULT); + } + + return root; + } +} // namespace Qv2ray::core::handler diff --git a/src/core/handler/RouteHandler.hpp b/src/core/handler/RouteHandler.hpp new file mode 100644 index 00000000..8e3ca061 --- /dev/null +++ b/src/core/handler/RouteHandler.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "base/Qv2rayBase.hpp" + +#include +namespace Qv2ray::core::handler +{ + class RouteHandler : public QObject + { + Q_OBJECT + public: + explicit RouteHandler(QObject *parent = nullptr); + ~RouteHandler(); + void SaveRoutes() const; + // + QPair GetDNSSettings(const GroupRoutingId &id) const + { + return { configs[id].overrideDNS, configs[id].dnsConfig }; + } + QPair GetAdvancedRoutingSettings(const GroupRoutingId &id) const + { + return { configs[id].overrideRoute, configs[id].routeConfig }; + } + // + bool SetDNSSettings(const GroupRoutingId &id, bool overrideGlobal, const QvConfig_DNS &dns); + bool SetAdvancedRouteSettings(const GroupRoutingId &id, bool overrideGlobal, const QvConfig_Route &dns); + // + // Final Config Generation + CONFIGROOT GenerateFinalConfig(const ConnectionGroupPair &pair) const; + CONFIGROOT GenerateFinalConfig(CONFIGROOT root, const GroupRoutingId &routingId) const; + // Route Table Generation + ROUTING GenerateRoutes(bool enableProxy, bool bypassCN, const QString &outboundTag, const QvConfig_Route &routeConfig) const; + + private: + QHash configs; + }; + inline ::Qv2ray::core::handler::RouteHandler *RouteManager = nullptr; +} // namespace Qv2ray::core::handler diff --git a/src/core/kernel/APIBackend.cpp b/src/core/kernel/APIBackend.cpp index 96b87f6c..f03b19ed 100644 --- a/src/core/kernel/APIBackend.cpp +++ b/src/core/kernel/APIBackend.cpp @@ -1,12 +1,15 @@ #include "APIBackend.hpp" -#ifndef BACKEND_LIBQVB +#ifndef ANDROID + #ifndef BACKEND_LIBQVB + #include "v2ray_api.pb.h" using namespace v2ray::core::app::stats::command; using grpc::Channel; using grpc::ClientContext; using grpc::Status; -#else - #include "libs/libqvb/build/libqvb.h" + #else + #include "libs/libqvb/build/libqvb.h" + #endif #endif namespace Qv2ray::core::kernel @@ -73,16 +76,19 @@ namespace Qv2ray::core::kernel { if (!dialed) { - auto channelAddress = "127.0.0.1:" + QString::number(GlobalConfig.apiConfig.statsPort); -#ifndef BACKEND_LIBQVB - Channel = grpc::CreateChannel(channelAddress.toStdString(), grpc::InsecureChannelCredentials()); - StatsService service; - Stub = service.NewStub(Channel); -#else + auto channelAddress = "127.0.0.1:" + QString::number(GlobalConfig.kernelConfig.statsPort); +#ifndef ANDROID + #ifdef BACKEND_LIBQVB auto str = Dial(const_cast(channelAddress.toStdString().c_str()), 10000); LOG(MODULE_VCORE, QString(str)) LOG(MODULE_VCORE, "Currently, libqvb does not support speed reporting, your stats might go wrong.") free(str); + #else + LOG(MODULE_VCORE, "gRPC Version: " + QString::fromStdString(grpc::Version())) + Channel = grpc::CreateChannel(channelAddress.toStdString(), grpc::InsecureChannelCredentials()); + v2ray::core::app::stats::command::StatsService service; + Stub = service.NewStub(Channel); + #endif #endif dialed = true; } @@ -91,7 +97,7 @@ namespace Qv2ray::core::kernel qint64 value_up = 0; qint64 value_down = 0; - for (const auto &tag : inboundTags) + for (auto tag : inboundTags) { value_up += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink"); value_down += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink"); @@ -154,8 +160,8 @@ namespace Qv2ray::core::kernel { return 0; } - -#ifndef BACKEND_LIBQVB +#ifndef ANDROID + #ifndef BACKEND_LIBQVB GetStatsRequest request; request.set_name(name.toStdString()); request.set_reset(true); @@ -174,9 +180,9 @@ namespace Qv2ray::core::kernel } qint64 data = response.stat().value(); -#else + #else qint64 data = GetStats(const_cast(name.toStdString().c_str()), 1000); -#endif + #endif if (data < 0) { @@ -186,5 +192,9 @@ namespace Qv2ray::core::kernel } return data; +#else + Q_UNUSED(name) + return 0; +#endif } } // namespace Qv2ray::core::kernel diff --git a/src/core/kernel/APIBackend.hpp b/src/core/kernel/APIBackend.hpp index b8929633..a0c799f3 100644 --- a/src/core/kernel/APIBackend.hpp +++ b/src/core/kernel/APIBackend.hpp @@ -1,15 +1,14 @@ #pragma once #include "base/Qv2rayBase.hpp" -#ifndef BACKEND_LIBQVB - #include "v2ray_api.grpc.pb.h" - #include "v2ray_api.pb.h" - #include "v2ray_geosite.pb.h" +#ifndef ANDROID + #ifndef BACKEND_LIBQVB + #include "v2ray_api.grpc.pb.h" - #include + #include + #endif #endif - // Check 10 times before telling user that API has failed. -constexpr auto QV2RAY_API_CALL_FAILEDCHECK_THRESHOLD = 10; +constexpr auto QV2RAY_API_CALL_FAILEDCHECK_THRESHOLD = 60; namespace Qv2ray::core::kernel { @@ -38,7 +37,7 @@ namespace Qv2ray::core::kernel bool started = false; bool running = false; uint apiFailedCounter = 0; -#ifndef BACKEND_LIBQVB +#if !defined(BACKEND_LIBQVB) && !defined(ANDROID) std::shared_ptr<::grpc::Channel> Channel; std::unique_ptr<::v2ray::core::app::stats::command::StatsService::Stub> Stub; #endif diff --git a/src/core/kernel/QvKernelABIChecker.cpp b/src/core/kernel/QvKernelABIChecker.cpp index d2c57b5f..7f05bdb4 100644 --- a/src/core/kernel/QvKernelABIChecker.cpp +++ b/src/core/kernel/QvKernelABIChecker.cpp @@ -6,6 +6,7 @@ namespace Qv2ray::core::kernel::abi { QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType) { +#ifndef QV2RAY_TRUSTED_ABI switch (hostType) { case ABI_WIN32: @@ -15,12 +16,19 @@ namespace Qv2ray::core::kernel::abi case ABI_ELF_X86: return targetType == hostType ? ABI_PERFECT : ABI_NOPE; case ABI_ELF_X86_64: return targetType == hostType ? ABI_PERFECT : targetType == ABI_ELF_X86 ? ABI_MAYBE : ABI_NOPE; case ABI_ELF_OTHER: return targetType == hostType ? ABI_PERFECT : ABI_MAYBE; + case ABI_TRUSTED: return ABI_PERFECT; default: return ABI_MAYBE; } +#else + return ABI_PERFECT; +#endif } std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable) { +#ifdef QV2RAY_TRUSTED_ABI + return { QvKernelABIType::ABI_TRUSTED, std::nullopt }; +#else QFile file(pathCoreExecutable); if (!file.exists()) return { std::nullopt, QObject::tr("core executable file %1 does not exist").arg(pathCoreExecutable) }; @@ -55,6 +63,7 @@ namespace Qv2ray::core::kernel::abi return { QvKernelABIType::ABI_MACH_O, std::nullopt }; else return { std::nullopt, QObject::tr("cannot deduce the type of core executable file %1").arg(pathCoreExecutable) }; +#endif } QString abiToString(QvKernelABIType abi) @@ -68,6 +77,7 @@ namespace Qv2ray::core::kernel::abi case ABI_ELF_AARCH64: return QObject::tr("ELF arm64 executable"); case ABI_ELF_ARM: return QObject::tr("ELF arm executable"); case ABI_ELF_OTHER: return QObject::tr("other ELF executable"); + case ABI_TRUSTED: return QObject::tr("trusted abi"); default: return QObject::tr("unknown abi"); } } diff --git a/src/core/kernel/QvKernelABIChecker.hpp b/src/core/kernel/QvKernelABIChecker.hpp index d0346388..5e166f13 100644 --- a/src/core/kernel/QvKernelABIChecker.hpp +++ b/src/core/kernel/QvKernelABIChecker.hpp @@ -19,6 +19,7 @@ namespace Qv2ray::core::kernel ABI_ELF_AARCH64, ABI_ELF_ARM, ABI_ELF_OTHER, + ABI_TRUSTED, }; enum QvKernelABICompatibility @@ -42,7 +43,8 @@ namespace Qv2ray::core::kernel #elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_V7) QvKernelABIType::ABI_ELF_ARM; #else - #error "unknown architecture" + QvKernelABIType::ABI_TRUSTED; + #define QV2RAY_TRUSTED_ABI #endif std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable); diff --git a/src/core/kernel/V2rayKernelInteractions.cpp b/src/core/kernel/V2rayKernelInteractions.cpp index 5c365bc0..b0811250 100644 --- a/src/core/kernel/V2rayKernelInteractions.cpp +++ b/src/core/kernel/V2rayKernelInteractions.cpp @@ -4,12 +4,80 @@ #include "common/QvHelpers.hpp" #include "core/connection/ConnectionIO.hpp" -#include +#include +#include #include -#include namespace Qv2ray::core::kernel { + std::tuple> V2rayKernelInstance::CheckAndSetCoreExecutableState(const QString &vCorePath) + { +#ifdef Q_OS_UNIX + // For Linux/macOS users: if they cannot execute the core, + // then we shall grant the permission to execute it. + + QFile coreFile(vCorePath); + + if (const auto permissions = coreFile.permissions(); !permissions.testFlag(QFileDevice::ExeUser)) + { + DEBUG(MODULE_VCORE, "Core file not executable. Trying to enable.") + const auto result = coreFile.setPermissions(coreFile.permissions().setFlag(QFileDevice::ExeUser)); + if (!result) + { + DEBUG(MODULE_VCORE, "Failed to enable executable permission.") + const auto message = tr("Core file is lacking executable permission for the current user.") % // + tr("Qv2ray tried to set, but failed because permission denied."); + return { false, message }; + } + else + { + DEBUG(MODULE_VCORE, "Core executable permission set.") + } + } + else + { + DEBUG(MODULE_VCORE, "Core file is executable.") + } + + // Also do the same thing for v2ctl. + // TODO: Simplify This / Extract This Creepy Thing + const auto coreControlFilePath = QDir::cleanPath(QFileInfo(coreFile).absoluteDir().path() + QDir::separator() + + #ifdef Q_OS_WIN + "v2ctl.exe"); + #else + "v2ctl"); + #endif + + if (QFile coreControlFile(coreControlFilePath); !coreControlFile.permissions().testFlag(QFileDevice::ExeUser)) + { + DEBUG(MODULE_VCORE, "Core control file not executable. Trying to enable.") + const auto result = coreControlFile.setPermissions(coreFile.permissions().setFlag(QFileDevice::ExeUser)); + + if (!result) + { + DEBUG(MODULE_VCORE, "Failed to enable executable permission for core control.") + const auto message = tr("Core control file is lacking executable permission for the current user.") % // + tr("Qv2ray tried to set, but failed because permission denied."); + return { false, message }; + } + else + { + DEBUG(MODULE_VCORE, "Core control executable permission set.") + } + } + else + { + DEBUG(MODULE_VCORE, "Core control file is executable.") + } + +#endif + return { true, std::nullopt }; + + // For Windows and other users: just skip this check. + DEBUG(MODULE_VCORE, "Skipped check and set core executable state.") + return { true, tr("Check is skipped") }; + } + bool V2rayKernelInstance::ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message) { QFile coreFile(vCorePath); @@ -31,8 +99,9 @@ namespace Qv2ray::core::kernel } coreFile.close(); + // Get Core ABI. - auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath); + const auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath); if (err) { LOG(MODULE_VCORE, "Core ABI deduction failed: " + ACCESS_OPTIONAL_VALUE(err)) @@ -69,6 +138,14 @@ namespace Qv2ray::core::kernel } } + // Check executable permissions. + const auto [isExecutableOk, strExecutableErr] = CheckAndSetCoreExecutableState(vCorePath); + if (!isExecutableOk) + { + *message = strExecutableErr.value_or(""); + return false; + } + // // Check file existance. // From: https://www.v2fly.org/chapter_02/env.html#asset-location @@ -107,7 +184,7 @@ namespace Qv2ray::core::kernel proc.setNativeArguments("--version"); proc.start(); #else - proc.start(vCorePath + " --version"); + proc.start(vCorePath, { "--version" }); #endif proc.waitForStarted(); proc.waitForFinished(); @@ -193,7 +270,7 @@ namespace Qv2ray::core::kernel KernelStarted = false; } - optional V2rayKernelInstance::StartConnection(const CONFIGROOT &root) + std::optional V2rayKernelInstance::StartConnection(const CONFIGROOT &root) { if (KernelStarted) { @@ -239,7 +316,7 @@ namespace Qv2ray::core::kernel { LOG(MODULE_VCORE, "API has been disabled by the command line argument \"-noAPI\"") } - else if (!GlobalConfig.apiConfig.enableAPI) + else if (!GlobalConfig.kernelConfig.enableAPI) { LOG(MODULE_VCORE, "API has been disabled by the global config option") } diff --git a/src/core/kernel/V2rayKernelInteractions.hpp b/src/core/kernel/V2rayKernelInteractions.hpp index 2b3a1ff7..52b1fd09 100644 --- a/src/core/kernel/V2rayKernelInteractions.hpp +++ b/src/core/kernel/V2rayKernelInteractions.hpp @@ -1,6 +1,5 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" -#include "core/CoreSafeTypes.hpp" #include "core/kernel/QvKernelABIChecker.hpp" #include @@ -21,12 +20,13 @@ namespace Qv2ray::core::kernel qulonglong getAllSpeedUp(); qulonglong getAllSpeedDown(); // - optional StartConnection(const CONFIGROOT &root); + std::optional StartConnection(const CONFIGROOT &root); void StopConnection(); bool KernelStarted = false; // static bool ValidateConfig(const QString &path); static bool ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message); + static std::tuple> CheckAndSetCoreExecutableState(const QString &vCorePath); signals: void OnProcessErrored(const QString &errMessage); diff --git a/src/core/settings/SettingsBackend.cpp b/src/core/settings/SettingsBackend.cpp index f03431cb..5a9d23a8 100644 --- a/src/core/settings/SettingsBackend.cpp +++ b/src/core/settings/SettingsBackend.cpp @@ -4,7 +4,7 @@ namespace Qv2ray::core::config { - void SaveGlobalSettings(const Qv2rayConfig &conf) + void SaveGlobalSettings(const Qv2rayConfigObject &conf) { GlobalConfig = conf; SaveGlobalSettings(); @@ -12,7 +12,7 @@ namespace Qv2ray::core::config void SaveGlobalSettings() { - QString str = StructToJsonString(GlobalConfig); + QString str = JsonToString(GlobalConfig.toJson()); StringToFile(str, QV2RAY_CONFIG_FILE); } @@ -29,32 +29,30 @@ namespace Qv2ray::core::config bool CheckSettingsPathAvailability(const QString &_path, bool checkExistingConfig) { auto path = _path; + if (!path.endsWith("/")) - { path.append("/"); - } + // Does not exist. if (!QDir(path).exists()) return false; - // A temp file used to test file permissions in that folder. - QFile testFile(path + ".qv2ray_file_write_test_file" + QSTRN(QTime::currentTime().msecsSinceStartOfDay())); - bool opened = testFile.open(QFile::OpenModeFlag::ReadWrite); + { + // A temp file used to test file permissions in that folder. + QFile testFile(path + ".qv2ray_test_file" + QSTRN(QTime::currentTime().msecsSinceStartOfDay())); + + if (!testFile.open(QFile::OpenModeFlag::ReadWrite)) + { + LOG(MODULE_SETTINGS, "Directory at: " + path + " cannot be used as a valid config file path.") + LOG(MODULE_SETTINGS, "---> Cannot create a new file or open a file for writing.") + return false; + } - if (!opened) - { - LOG(MODULE_SETTINGS, "Directory at: " + path + " cannot be used as a valid config file path.") - LOG(MODULE_SETTINGS, "---> Cannot create a new file or open a file for writing.") - return false; - } - else - { testFile.write("Qv2ray test file, feel free to remove."); testFile.flush(); testFile.close(); - bool removed = testFile.remove(); - if (!removed) + if (!testFile.remove()) { // This is rare, as we can create a file but failed to remove it. LOG(MODULE_SETTINGS, "Directory at: " + path + " cannot be used as a valid config file path.") @@ -63,63 +61,37 @@ namespace Qv2ray::core::config } } - if (checkExistingConfig) + if (!checkExistingConfig) { - // Check if an existing config is found. - QFile configFile(path + "Qv2ray.conf"); - - // No such config file. - if (!configFile.exists()) - return false; - - bool opened2 = configFile.open(QIODevice::ReadWrite); - - try - { - if (opened2) - { - // Verify if the config can be loaded. - // Failed to parse if we changed the file structure... - // Original: - // -- auto conf = - // StructFromJsonString(configFile.readAll()); - // - // Verify JSON file format. (only) because this file version may - // not be upgraded and may contain unsupported structure. - auto err = VerifyJsonString(StringFromFile(configFile)); - - if (!err.isEmpty()) - { - LOG(MODULE_INIT, "Json parse returns: " + err) - return false; - } - else - { - // If the file format is valid. - auto conf = JsonFromString(StringFromFile(configFile)); - LOG(MODULE_SETTINGS, - "Path: " + path + " contains a config file, in version " + conf["config_version"].toVariant().toString()) - configFile.close(); - return true; - } - } - else - { - LOG(MODULE_SETTINGS, "File: " + configFile.fileName() + " cannot be opened!") - return false; - } - } - catch (...) - { - LOG(MODULE_SETTINGS, "Exception raised when checking config: " + configFile.fileName()) - // LOG(INIT, e->what()) - QvMessageBoxWarn(nullptr, QObject::tr("Warning"), - QObject::tr("Qv2ray cannot load the config file from here:") + NEWLINE + configFile.fileName()); - return false; - } - } - else + // Just pass the test return true; + } + + // Check if an existing config is found. + QFile configFile(path + "Qv2ray.conf"); + + // No such config file. + if (!configFile.exists()) + return false; + + if (!configFile.open(QIODevice::ReadWrite)) + { + LOG(MODULE_SETTINGS, "File: " + configFile.fileName() + " cannot be opened!") + return false; + } + + const auto err = VerifyJsonString(StringFromFile(configFile)); + if (!err.isEmpty()) + { + LOG(MODULE_INIT, "Json parse returns: " + err) + return false; + } + + // If the file format is valid. + const auto conf = JsonFromString(StringFromFile(configFile)); + LOG(MODULE_SETTINGS, "Found a config file, v=" + conf["config_version"].toString() + " path=" + path) + configFile.close(); + return true; } } // namespace Qv2ray::core::config diff --git a/src/core/settings/SettingsBackend.hpp b/src/core/settings/SettingsBackend.hpp index cfcc20b1..2e68f805 100644 --- a/src/core/settings/SettingsBackend.hpp +++ b/src/core/settings/SettingsBackend.hpp @@ -4,10 +4,16 @@ namespace Qv2ray::core::config { void SaveGlobalSettings(); - void SaveGlobalSettings(const Qv2rayConfig &conf); + void SaveGlobalSettings(const Qv2rayConfigObject &conf); void SetConfigDirPath(const QString &path); bool CheckSettingsPathAvailability(const QString &_path, bool checkExistingConfig); } // namespace Qv2ray::core::config +namespace Qv2ray +{ + // Extra header for QvConfigUpgrade.cpp + QJsonObject UpgradeSettingsVersion(int fromVersion, int toVersion, const QJsonObject &root); +} // namespace Qv2ray + using namespace Qv2ray::core; using namespace Qv2ray::core::config; diff --git a/src/core/settings/SettingsUpgrade.cpp b/src/core/settings/SettingsUpgrade.cpp index edbaad98..2d9bc5dc 100644 --- a/src/core/settings/SettingsUpgrade.cpp +++ b/src/core/settings/SettingsUpgrade.cpp @@ -1,8 +1,9 @@ -// +// // This file handles some important migration // from old to newer versions of Qv2ray. // +#include "base/Qv2rayBase.hpp" #include "common/QvHelpers.hpp" #define UPGRADELOG(msg) LOG(MODULE_SETTINGS, " [" + QSTRN(fromVersion) + "-" + QSTRN(fromVersion + 1) + "] --> " + msg) @@ -10,88 +11,11 @@ namespace Qv2ray { // Private member - QJsonObject UpgradeConfig_Inc(int fromVersion, QJsonObject root) + QJsonObject UpgradeConfig_Inc(int fromVersion, const QJsonObject &original) { + auto root = original; switch (fromVersion) { - // Cases 1, 2, and 3 are not supported anymore. - // -------------------------------------------------------------------------------------- - // Below is for Qv2ray version 2 - case 4: - { - // We changed the "proxyCN" to "bypassCN" as it's easier to - // understand.... - auto v2_oldProxyCN = root["proxyCN"].toBool(); - // - // From 3 to 4, we changed 'runAsRoot' to 'tProxySupport' - auto v3_oldrunAsRoot = root["runAsRoot"].toBool(); - root.insert("tProxySupport", v3_oldrunAsRoot); - UPGRADELOG("Upgrading runAsRoot to tProxySupport, the value is not changed: " + QSTRN(v3_oldrunAsRoot)) - // - QString path; - path = QV2RAY_DEFAULT_VCORE_PATH; - root["v2CorePath"] = path; - UPGRADELOG("Added v2CorePath to the config file.") - // - QJsonObject uiSettings; - uiSettings["language"] = root["language"].toString("en-US").replace("-", "_"); - root["uiConfig"] = uiSettings; - // - root["inboundConfig"] = root["inBoundSettings"]; - root.remove("inBoundSettings"); - UPGRADELOG("Renamed inBoundSettings to inboundConfig.") - // - // connectionConfig - QJsonObject o; - o["dnsList"] = root["dnsList"]; - o["withLocalDNS"] = root["withLocalDNS"]; - o["enableProxy"] = root["enableProxy"]; - o["bypassCN"] = !v2_oldProxyCN; - o["enableStats"] = true; - o["statsPort"] = 13459; - UPGRADELOG("Default statistics enabled.") - root["connectionConfig"] = o; - UPGRADELOG("Renamed some connection configs to connectionConfig.") - // - // Do we need renaming here? - // //auto inbound = root["inboundConfig"].toObject(); - // //auto pacConfig = inbound["pacConfig"].toObject(); - // //pacConfig["enablePAC"] = pacConfig["usePAC"].toBool(); - // //inbound["pacConfig"] = pacConfig; - // //root["inboundConfig"] = inbound; - // //UPDATELOG("Renamed usePAC to enablePAC.") - // - QJsonObject i; - i["connectionName"] = root["autoStartConfig"].toString(); - root["autoStartConfig"] = i; - UPGRADELOG("Added subscription feature to autoStartConfig.") - break; - } - - // Qv2ray version 2, RC 2 - case 5: - { - // Added subscription auto update - auto subs = root["subscribes"].toObject(); - root.remove("subscribes"); - QJsonObject newSubscriptions; - - for (auto item = subs.begin(); item != subs.end(); item++) - { - auto key = item.key(); - SubscriptionObject_Config _conf; - _conf.address = item.value().toString(); - _conf.lastUpdated = system_clock::to_time_t(system_clock::now()); - _conf.updateInterval = 5; - auto value = GetRootObject(_conf); - newSubscriptions[key] = value; - } - - root["subscriptions"] = newSubscriptions; - UPGRADELOG("Added subscription renewal options.") - break; - } - // Qv2ray version 2, RC 4 case 6: { @@ -176,7 +100,7 @@ namespace Qv2ray for (auto i = 0; i < rootSubscriptions.count(); i++) { - auto key = rootSubscriptions.keys()[i]; + auto key = rootSubscriptions.keys().at(i); auto value = rootSubscriptions.value(key); // UPGRADELOG("Upgrading subscription: " + key) @@ -188,8 +112,8 @@ namespace Qv2ray subs["updateInterval"] = value["updateInterval"]; subs["displayName"] = key; // - auto baseDirPath = QV2RAY_SUBSCRIPTION_DIR + key; - auto newDirPath = QV2RAY_SUBSCRIPTION_DIR + subsUuid; + auto baseDirPath = QV2RAY_CONFIG_DIR + "/subscriptions/" + key; + auto newDirPath = QV2RAY_CONFIG_DIR + "/subscriptions/" + subsUuid; QDir newDir(newDirPath); if (!newDir.exists()) @@ -201,7 +125,7 @@ namespace Qv2ray auto fileList = GetFileList(baseDirPath); // Copy every file within a subscription. - for (auto fileName : fileList) + for (const auto &fileName : fileList) { auto subsConnectionId = GenerateUuid(); auto baseFilePath = baseDirPath + "/" + fileName; @@ -285,6 +209,211 @@ namespace Qv2ray } break; } + + // Splitted Qv2ray.conf, + case 11: + { + // Process AutoStartSettings + ConnectionGroupPair autoStartIdPair{ ConnectionId{ root["autoStartId"].toString() }, NullGroupId }; + + // Process connection entries. + // + { + // Moved root["connections"] into separated file: $QV2RAY_CONFIG_PATH/connections.json + QDir connectionsDir(QV2RAY_CONNECTIONS_DIR); + if (!connectionsDir.exists()) + { + connectionsDir.mkpath(QV2RAY_CONNECTIONS_DIR); + } + const auto connectionsArray = root["connections"].toObject().keys(); + QJsonObject newConnectionsArray; + /// + /// Connection.json + /// { + /// "connections" : [ + /// {ID1, connectionObject 1}, + /// {ID2, connectionObject 2}, + /// {ID3, connectionObject 3}, + /// {ID4, connectionObject 4}, + /// ] + /// } + /// + for (const auto &connectionVal : connectionsArray) + { + // Config file migrations: + // Connection Object: + // importDate --> creationDate + // lastUpdatedDate --> now + // + auto connection = root["connections"].toObject()[connectionVal].toObject(); + connection["creationDate"] = connection.take("importDate"); + connection["lastUpdatedDate"] = (qint64) system_clock::to_time_t(system_clock::now()); + UPGRADELOG("Migrating connection: " + connectionVal + " -- " + connection["displayName"].toString()) + newConnectionsArray[connectionVal] = connection; + } + QJsonObject ConnectionJsonObject; + root["connections"] = QJsonArray::fromStringList(connectionsArray); + // + // Store Connection.json + StringToFile(JsonToString(newConnectionsArray), QV2RAY_CONFIG_DIR + "connections.json"); + } + // Merged groups and subscriptions. $QV2RAY_GROUPS_PATH + groupId.json + { + // Susbcription Object + // Doesn't exist anymore, convert into normal group Object. + // + QMap ConnectionsCache; + QJsonObject allGroupsObject; + const auto subscriptionKeys = root["subscriptions"].toObject().keys(); + for (const auto &key : subscriptionKeys) + { + auto aSubscription = root["subscriptions"].toObject()[key].toObject(); + QJsonObject subscriptionSettings; + subscriptionSettings["address"] = aSubscription.take("address"); + subscriptionSettings["updateInterval"] = aSubscription.take("updateInterval"); + aSubscription["lastUpdatedDate"] = (qint64) system_clock::to_time_t(system_clock::now()); + aSubscription["creationDate"] = (qint64) system_clock::to_time_t(system_clock::now()); + aSubscription["subscriptionOption"] = subscriptionSettings; + UPGRADELOG("Migrating subscription: " + key + " -- " + aSubscription["displayName"].toString()) + // + if (autoStartIdPair.groupId != NullGroupId && + aSubscription["connections"].toArray().contains(autoStartIdPair.connectionId.toString())) + { + autoStartIdPair.groupId = GroupId{ key }; + } + // + for (const auto &cid : aSubscription["connections"].toArray()) + { + ConnectionsCache[cid.toString()] = JsonFromString(StringFromFile(QV2RAY_CONFIG_DIR + "subscriptions/" + key + "/" + + cid.toString() + QV2RAY_CONFIG_FILE_EXTENSION)); + } + // + allGroupsObject[key] = aSubscription; + } + // + root.remove("subscriptions"); + // + const auto groupKeys = root["groups"].toObject().keys(); + for (const auto &key : groupKeys) + { + // Group Object + // connections ---> ConnectionID + // idSubscription ---> if the group is a subscription + // subscriptionSettings ---> Originally SubscriptionObject + // creationDate ---> Now + // lastUpdateDate ---> Now + auto aGroup = root["groups"].toObject()[key].toObject(); + aGroup["lastUpdatedDate"] = (qint64) system_clock::to_time_t(system_clock::now()); + aGroup["creationDate"] = (qint64) system_clock::to_time_t(system_clock::now()); + UPGRADELOG("Migrating group: " + key + " -- " + aGroup["displayName"].toString()) + // + if (autoStartIdPair.groupId != NullGroupId && + aGroup["connections"].toArray().contains(autoStartIdPair.connectionId.toString())) + { + autoStartIdPair.groupId = GroupId{ key }; + } + for (const auto &cid : aGroup["connections"].toArray()) + { + ConnectionsCache[cid.toString()] = JsonFromString( + StringFromFile(QV2RAY_CONFIG_DIR + "connections/" + key + "/" + cid.toString() + QV2RAY_CONFIG_FILE_EXTENSION)); + } + // + allGroupsObject[key] = aGroup; + } + // + StringToFile(JsonToString(allGroupsObject), QV2RAY_CONFIG_DIR + "groups.json"); + // + root.remove("groups"); // + UPGRADELOG("Removing unused directory") + QDir(QV2RAY_CONFIG_DIR + "subscriptions/").removeRecursively(); + QDir(QV2RAY_CONFIG_DIR + "connections/").removeRecursively(); + // + QDir().mkpath(QV2RAY_CONFIG_DIR + "connections/"); + // + // + // FileSystem Migrations + // Move all files in GROUPS / SUBSCRIPTION subfolders into CONNECTIONS. + // Only Store (connections.json in CONFIG_PATH) and ($groupID.json in GROUP_PATH) + for (const auto &cid : ConnectionsCache.keys()) + { + StringToFile(JsonToString(ConnectionsCache[cid]), + QV2RAY_CONFIG_DIR + "connections/" + cid + QV2RAY_CONFIG_FILE_EXTENSION); + } + // + } + + // + // Main Object + // Drop recentConnections since it's ill-formed and not supported yet. + // convert autoStartId into ConnectionGroupPair / instead of QString + // Remove subscriptions item. + root.remove("recentConnections"); + root["autoStartId"] = autoStartIdPair.toJson(); + // 1 here means FIXED + root["autoStartBehavior"] = 1; + + // Moved apiConfig into kernelConfig + auto kernelConfig = root["kernelConfig"].toObject(); + kernelConfig["enableAPI"] = root["apiConfig"].toObject()["enableAPI"]; + kernelConfig["statsPort"] = root["apiConfig"].toObject()["statsPort"]; + root["kernelConfig"] = kernelConfig; + UPGRADELOG("Finished upgrading config file for Qv2ray Group Routing update.") + break; + } + case 12: + { + auto inboundConfig = root["inboundConfig"].toObject(); + // + QJsonObject socksSettings; + QJsonObject httpSettings; + QJsonObject tProxySettings; + QJsonObject systemProxySettings; + systemProxySettings["setSystemProxy"] = inboundConfig["setSystemProxy"]; + // + socksSettings["port"] = inboundConfig["socks_port"]; + socksSettings["useAuth"] = inboundConfig["socks_useAuth"]; + socksSettings["enableUDP"] = inboundConfig["socksUDP"]; + socksSettings["localIP"] = inboundConfig["socksLocalIP"]; + socksSettings["account"] = inboundConfig["socksAccount"]; + socksSettings["sniffing"] = inboundConfig["socksSniffing"]; + // + httpSettings["port"] = inboundConfig["http_port"]; + httpSettings["useAuth"] = inboundConfig["http_useAuth"]; + httpSettings["account"] = inboundConfig["httpAccount"]; + httpSettings["sniffing"] = inboundConfig["httpSniffing"]; + // + tProxySettings["tProxyIP"] = inboundConfig["tproxy_ip"]; + tProxySettings["port"] = inboundConfig["tproxy_port"]; + tProxySettings["hasTCP"] = inboundConfig["tproxy_use_tcp"]; + tProxySettings["hasUDP"] = inboundConfig["tproxy_use_udp"]; + tProxySettings["followRedirect"] = inboundConfig["tproxy_followRedirect"]; + tProxySettings["mode"] = inboundConfig["tproxy_mode"]; + tProxySettings["dnsIntercept"] = inboundConfig["dnsIntercept"]; + // + inboundConfig["systemProxySettings"] = systemProxySettings; + inboundConfig["socksSettings"] = socksSettings; + inboundConfig["httpSettings"] = httpSettings; + inboundConfig["tProxySettings"] = tProxySettings; + // + root["inboundConfig"] = inboundConfig; + break; + } + case 13: + { + const auto dnsList = QJsonIO::GetValue(root, "connectionConfig", "dnsList").toArray(); + auto connectionConfig = root["connectionConfig"].toObject(); + QJsonObject defaultRouteConfig; + defaultRouteConfig["forwardProxyConfig"] = connectionConfig.take("forwardProxyConfig"); + defaultRouteConfig["routeConfig"] = connectionConfig.take("routeConfig"); + + for (auto i = 0; i < dnsList.count(); i++) + { + QJsonIO::SetValue(defaultRouteConfig, dnsList[i], "dnsConfig", "servers", i, "address"); + QJsonIO::SetValue(defaultRouteConfig, false, "dnsConfig", "servers", i, "QV2RAY_DNS_IS_COMPLEX_DNS"); + } + root["defaultRouteConfig"] = defaultRouteConfig; + break; + } default: { // @@ -300,14 +429,14 @@ namespace Qv2ray qApp->exit(1); } } - root["config_version"] = root["config_version"].toInt() + 1; return root; } // Exported function - QJsonObject UpgradeSettingsVersion(int fromVersion, int toVersion, QJsonObject root) + QJsonObject UpgradeSettingsVersion(int fromVersion, int toVersion, const QJsonObject &original) { + auto root = original; LOG(MODULE_SETTINGS, "Migrating config from version " + QSTRN(fromVersion) + " to " + QSTRN(toVersion)) for (int i = fromVersion; i < toVersion; i++) diff --git a/src/main.cpp b/src/main.cpp index 8055c6ed..0b5c9554 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,340 +1,76 @@ -#include "3rdparty/SingleApplication/singleapplication.h" -#include "common/CommandArgs.hpp" +#include "Qv2rayApplication.hpp" +#include "StackTraceHelper.hpp" #include "common/QvHelpers.hpp" -#include "common/QvTranslator.hpp" #include "core/handler/ConfigHandler.hpp" -#include "core/settings/SettingsBackend.hpp" -#include "src/components/plugins/QvPluginHost.hpp" -#include "ui/w_MainWindow.hpp" -#include #include #include -#include -#include -#include -#include -#include +#include +#include #include -#include - -#ifdef Q_OS_UNIX - // For unix root user check - #include "unistd.h" -#endif void signalHandler(int signum) { - cout << "Qv2ray: Interrupt signal (" << signum << ") received." << endl; - ExitQv2ray(); - qApp->exit(-99); -} - -bool initialiseQv2ray() -{ - LOG(MODULE_INIT, "Application exec path: " + QApplication::applicationDirPath()) - const QString currentPathConfig = QApplication::applicationDirPath() + "/config" QV2RAY_CONFIG_DIR_SUFFIX; - // Standard paths already handles the _debug suffix for us. - const QString configQv2ray = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - const QString homeQv2ray = QDir::homePath() + "/.qv2ray" QV2RAY_CONFIG_DIR_SUFFIX; - // - // - // Some built-in search paths for Qv2ray to find configs. (load the first one if possible). - // - QStringList configFilePaths; - configFilePaths << currentPathConfig; - // Application name changed to `qv2ray`, so these code are now becoming unnecessary. - //#ifdef WITH_FLATHUB_CONFIG_PATH - // // AppConfigLocation uses 'Q'v2ray instead of `q`v2ray. Keep here as backward compatibility. - // configFilePaths << QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation) + QV2RAY_CONFIG_DIR_SUFFIX; - //#endif - configFilePaths << configQv2ray; - configFilePaths << homeQv2ray; - // - QString configPath = ""; - bool hasExistingConfig = false; - - for (const auto &path : configFilePaths) + std::cout << "Qv2ray: Interrupt signal (" << signum << ") received." << std::endl; + if (SIGSEGV == signum) { - // Verify the config path, check if the config file exists and in the - // correct JSON format. True means we check for config existence as - // well. ----------------------------------------------|HERE| - bool isValidConfigPath = CheckSettingsPathAvailability(path, true); - - // If we already found a valid config file. just simply load it... - if (hasExistingConfig) - break; - - if (isValidConfigPath) + std::cout << "Collecting StackTrace" << std::endl; + const auto msg = StackTraceHelper::GetStackTrace(); + std::cout << msg.toStdString() << std::endl; + QDir().mkpath(QV2RAY_CONFIG_DIR + "bugreport/"); + auto filePath = QV2RAY_CONFIG_DIR + "bugreport/QvBugReport_" + QSTRN(system_clock::to_time_t(system_clock::now())) + ".stacktrace"; + StringToFile(msg, filePath); + std::cout << "Backtrace saved in: " + filePath.toStdString() << std::endl; + if (qApp) { - DEBUG(MODULE_INIT, "Path: " + path + " is valid.") - configPath = path; - hasExistingConfig = true; + qApp->clipboard()->setText(filePath); + auto message = QObject::tr("Qv2ray has encountered an uncaught exception: ") + NEWLINE + // + QObject::tr("Please report a bug via Github with the file located here: ") + NEWLINE NEWLINE + // + filePath; + QvMessageBoxWarn(nullptr, "UNCAUGHT EXCEPTION", message); } - else - { - LOG(MODULE_INIT, "Path: " + path + " does not contain a valid config file.") - } - } - - if (hasExistingConfig) - { - // Use the config path found by the checks above - SetConfigDirPath(configPath); - LOG(MODULE_INIT, "Using " + QV2RAY_CONFIG_DIR + " as the config path.") - } - else - { - // If there's no existing config. - // - // Create new config at these dirs, these are default values for each - // platform. -#if defined(Q_OS_WIN) && !defined(QV2RAY_NO_ASIDECONFIG) - configPath = currentPathConfig; -#else - configPath = configQv2ray; -#endif - bool mkpathResult = QDir().mkpath(configPath); - bool hasPossibleNewLocation = mkpathResult && CheckSettingsPathAvailability(configPath, false); - // Check if the dirs are write-able - if (!hasPossibleNewLocation) - { - // - // None of the path above can be used as a dir for storing config. - // Even the last folder failed to pass the check. - LOG(MODULE_INIT, "FATAL") - LOG(MODULE_INIT, " ---> CANNOT find a proper place to store Qv2ray config files.") - QvMessageBoxWarn(nullptr, QObject::tr("Cannot Start Qv2ray"), - QObject::tr("Cannot find a place to store config files.") + NEWLINE + - QObject::tr("Qv2ray has searched these paths below:") + NEWLINE + NEWLINE + // - configFilePaths.join(NEWLINE) + NEWLINE + - QObject::tr("It usually means you don't have the write permission to all of those locations.") + - QObject::tr("Qv2ray will now exit.")); - return false; - } - // Found a valid config dir, with write permission, but assume no config is located in it. - LOG(MODULE_INIT, "Set " + configPath + " as the config path.") - SetConfigDirPath(configPath); - - if (QFile::exists(QV2RAY_CONFIG_FILE)) - { - // As we already tried to load config from every possible dir. - // - // This condition branch (!hasExistingConfig check) holds the fact that current config dir, - // should NOT contain any valid file (at least in the same name) - // - // It usually means that QV2RAY_CONFIG_FILE here has a corrupted JSON format. - // - // Otherwise Qv2ray would have loaded this config already instead of notifying to create a new config in this folder. - // - LOG(MODULE_INIT, "This should not occur: Qv2ray config exists but failed to load.") - QvMessageBoxWarn(nullptr, QObject::tr("Failed to initialise Qv2ray"), - QObject::tr("Failed to determine the location of config file:") + NEWLINE + - QObject::tr("Qv2ray has found a config file, but it failed to be loaded due to some errors.") + NEWLINE + - QObject::tr("A workaround is to remove the this file and restart Qv2ray:") + NEWLINE + QV2RAY_CONFIG_FILE + - QObject::tr("Qv2ray will now exit.") + NEWLINE + QObject::tr("Please report if you think it's a bug.")); - return false; - } - - Qv2rayConfig conf; - conf.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH)); - conf.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH)); - conf.logLevel = 3; - conf.uiConfig.language = QLocale::system().name(); - // - // Save initial config. - SaveGlobalSettings(conf); - LOG(MODULE_INIT, "Created initial config file.") - } - - if (!QDir(QV2RAY_GENERATED_DIR).exists()) - { - // The dir used to generate final config file, for V2ray interaction. - QDir().mkdir(QV2RAY_GENERATED_DIR); - LOG(MODULE_INIT, "Created config generation dir at: " + QV2RAY_GENERATED_DIR) - } - - return true; -} - -int main(int argc, char *argv[]) -{ #ifndef Q_OS_WIN - // Register signal handlers. - signal(SIGINT, signalHandler); - signal(SIGHUP, signalHandler); - signal(SIGKILL, signalHandler); - signal(SIGTERM, signalHandler); + kill(getpid(), SIGKILL); #endif - // - // parse the command line before starting as a Qt application - { - std::unique_ptr consoleApp(new QCoreApplication(argc, argv)); - // - // Install a default translator. From the OS/DE - Qv2rayTranslator = std::make_unique(); - Qv2rayTranslator->InstallTranslation(QLocale::system().name()); - QvCommandArgParser parser; - QString errorMessage; - - switch (parser.ParseCommandLine(&errorMessage)) - { - case CommandLineOk: break; - - case CommandLineError: - cout << "Invalid command line arguments" << endl; - cout << errorMessage.toStdString() << endl; - cout << parser.Parser()->helpText().toStdString() << endl; - break; - - case CommandLineVersionRequested: - LOG("Qv2ray", QV2RAY_VERSION_STRING) - LOG("QV2RAY_BUILD_INFO", QV2RAY_BUILD_INFO) - LOG("QV2RAY_BUILD_EXTRA_INFO", QV2RAY_BUILD_EXTRA_INFO) - return 0; - - case CommandLineHelpRequested: cout << parser.Parser()->helpText().toStdString() << endl; return 0; - } } -#ifdef Q_OS_UNIX + exit(-99); +} - // Unix OS root user check. - // Do not use getuid() here since it's installed as owned by the root, - // someone may accidently setuid to it. - if (!StartupOption.forceRunAsRootUser && geteuid() == 0) +Qv2rayExitCode RunQv2rayApplicationScoped(int argc, char *argv[]) +{ + Qv2rayApplication app(argc, argv); + + const auto setupStatus = app.SetupQv2ray(); + switch (setupStatus) { - LOG("ERROR", QObject::tr("You cannot run Qv2ray as root, please use --I-just-wanna-run-with-root if you REALLY want to do so.")) - LOG("ERROR", QObject::tr(" --> USE IT AT YOUR OWN RISK!")) - return 1; + case Qv2rayApplication::NORMAL: break; + case Qv2rayApplication::SINGLE_APPLICATION: return QV2RAY_SECONDARY_INSTANCE; + case Qv2rayApplication::FAILED: return QV2RAY_EARLY_SETUP_FAIL; } -#endif - // - // finished: command line parsing - // - LOG("QV2RAY_BUILD_INFO", QV2RAY_BUILD_INFO) - LOG("QV2RAY_BUILD_EXTRA_INFO", QV2RAY_BUILD_EXTRA_INFO) - LOG("QV2RAY_BUILD_NUMBER", QSTRN(QV2RAY_VERSION_BUILD)) - LOG(MODULE_INIT, "Qv2ray " QV2RAY_VERSION_STRING " running on " + QSysInfo::prettyProductName() + " " + QSysInfo::currentCpuArchitecture()) - // - // This line must be called before any other ones, since we are using these - // values to identify instances. - SingleApplication::setApplicationName("qv2ray"); - SingleApplication::setApplicationVersion(QV2RAY_VERSION_STRING); - SingleApplication::setApplicationDisplayName("Qv2ray"); - // - // + LOG("LICENCE", NEWLINE // + "This program comes with ABSOLUTELY NO WARRANTY." NEWLINE // + "This is free software, and you are welcome to redistribute it" NEWLINE // + "under certain conditions." NEWLINE NEWLINE // + "Copyright (c) 2019-2020 Qv2ray Development Group." NEWLINE // + "Third-party libraries that have been used in Qv2ray can be found in the About page." NEWLINE) + #ifdef QT_DEBUG - // ----------------------------> For debug build... - SingleApplication::setApplicationName("qv2ray_debug"); - SingleApplication::setApplicationDisplayName("Qv2ray - " + QObject::tr("Debug version")); -#endif - if (StartupOption.noScaleFactors) - { - LOG(MODULE_INIT, "Force set QT_SCALE_FACTOR to 1.") - LOG(MODULE_UI, "Original QT_SCALE_FACTOR was: " + qEnvironmentVariable("QT_SCALE_FACTOR")) - qputenv("QT_SCALE_FACTOR", "1"); - } - else - { - LOG(MODULE_INIT, "High DPI scaling is enabled.") - QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif - } - using _SA = SingleApplication; - SingleApplication _qApp(argc, argv, false, _SA::User | _SA::ExcludeAppPath | _SA::ExcludeAppVersion); - _qApp.setQuitOnLastWindowClosed(false); - // Early initialisation - // - // Not duplicated. - // Install a default translater. From the OS/DE - auto _lang = QLocale::system().name(); - Qv2rayTranslator = std::make_unique(); - bool _result_ = Qv2rayTranslator->InstallTranslation(_lang); - LOG(MODULE_UI, "Installing a tranlator from OS: " + _lang + " -- " + (_result_ ? "OK" : "Failed")) - // - LOG("LICENCE", NEWLINE // - "This program comes with ABSOLUTELY NO WARRANTY." NEWLINE // - "This is free software, and you are welcome to redistribute it" NEWLINE // - "under certain conditions." NEWLINE // - NEWLINE // - "Copyright (c) 2019-2020 Qv2ray Development Group." NEWLINE // - NEWLINE // - "Libraries that have been used in Qv2ray are listed below (Sorted by date added):" NEWLINE // - "Copyright (c) 2020 xyz347 (@xyz347): X2Struct (Apache)" NEWLINE // - "Copyright (c) 2011 SCHUTZ Sacha (@dridk): QJsonModel (MIT)" NEWLINE // - "Copyright (c) 2020 Nikolaos Ftylitakis (@ftylitak): QZXing (Apache2)" NEWLINE // - "Copyright (c) 2016 Singein (@Singein): ScreenShot (MIT)" NEWLINE // - "Copyright (c) 2020 Itay Grudev (@itay-grudev): SingleApplication (MIT)" NEWLINE // - "Copyright (c) 2020 paceholder (@paceholder): nodeeditor (Qv2ray group modified version) (BSD-3-Clause)" NEWLINE // - "Copyright (c) 2019 TheWanderingCoel (@TheWanderingCoel): ShadowClash (launchatlogin) (GPLv3)" NEWLINE // - "Copyright (c) 2020 Ram Pani (@DuckSoft): QvRPCBridge (WTFPL)" NEWLINE // - "Copyright (c) 2019 ShadowSocks (@shadowsocks): libQtShadowsocks (LGPLv3)" NEWLINE // - "Copyright (c) 2015-2020 qBittorrent (Anton Lashkov) (@qBittorrent): speedplotview (GPLv2)" NEWLINE // - "Copyright (c) 2020 Diffusions Nu-book Inc. (@nu-book): zxing-cpp (Apache)" NEWLINE // - NEWLINE) // - // - LOG(MODULE_INIT, "Qv2ray Start Time: " + QSTRN(QTime::currentTime().msecsSinceStartOfDay())) - // -#ifdef QT_DEBUG - cout << "WARNING: ========================= This is a debug build, many features are not stable enough. =========================" << endl; + std::cerr << "WARNING: ================ This is a debug build, many features are not stable enough. ================" << std::endl; #endif // // Qv2ray Initialize, find possible config paths and verify them. - if (!initialiseQv2ray()) + if (!app.FindAndCreateInitialConfiguration()) { - LOG(MODULE_INIT, "Failed to initialise Qv2ray, exiting.") - return -1; + LOG(MODULE_INIT, "Cannot find or create initial configuration file.") + return QV2RAY_CONFIG_PATH_FAIL; + } + if (!app.LoadConfiguration()) + { + LOG(MODULE_INIT, "Cannot load existing configuration file.") + return QV2RAY_CONFIG_FILE_FAIL; } - // Load the config for upgrade, but do not parse it to the struct. - auto conf = JsonFromString(StringFromFile(QV2RAY_CONFIG_FILE)); - auto confVersion = conf["config_version"].toVariant().toString().toInt(); - - if (confVersion > QV2RAY_CONFIG_VERSION) - { - // Config version is larger than the current version... - // This is rare but it may happen.... - QvMessageBoxWarn(nullptr, QObject::tr("Qv2ray Cannot Continue"), - QObject::tr("You are running a lower version of Qv2ray compared to the current config file.") + NEWLINE + - QObject::tr("Please check if there's an issue explaining about it.") + NEWLINE + - QObject::tr("Or submit a new issue if you think this is an error.") + NEWLINE + NEWLINE + - QObject::tr("Qv2ray will now exit.")); - return -2; - } - - if (confVersion < QV2RAY_CONFIG_VERSION) - { - // That is, config file needs to be upgraded. - conf = Qv2ray::UpgradeSettingsVersion(confVersion, QV2RAY_CONFIG_VERSION, conf); - } - - // Load config object from upgraded config QJsonObject - auto confObject = StructFromJsonString(JsonToString(conf)); - - if (confObject.uiConfig.language.isEmpty()) - { - // Prevent empty. - LOG(MODULE_UI, "Setting default UI language to system locale.") - confObject.uiConfig.language = QLocale::system().name(); - } - - if (Qv2rayTranslator->InstallTranslation(confObject.uiConfig.language)) - { - LOG(MODULE_INIT, "Successfully installed a translator for " + confObject.uiConfig.language) - } - else - { - QvMessageBoxWarn(nullptr, "Translation Failed", - "Cannot load translation for " + confObject.uiConfig.language + ", English is now used." + NEWLINE + NEWLINE + - "Please go to Preferences Window to change language or open an Issue"); - } - - // Let's save the config. - SaveGlobalSettings(confObject); - // // Check OpenSSL version for auto-update and subscriptions auto osslReqVersion = QSslSocket::sslLibraryBuildVersionString(); auto osslCurVersion = QSslSocket::sslLibraryVersionString(); @@ -347,103 +83,54 @@ int main(int argc, char *argv[]) QvMessageBoxWarn(nullptr, QObject::tr("Dependency Missing"), QObject::tr("Cannot find openssl libs") + NEWLINE + QObject::tr("This could be caused by a missing of `openssl` package in your system.") + NEWLINE + - QObject::tr("If you are using an AppImage from Github Action, please report a bug.") + NEWLINE + NEWLINE + - QObject::tr("Technical Details") + NEWLINE + "OSsl.Rq.V=" + osslReqVersion + NEWLINE + + QObject::tr("If you are using an AppImage from Github Action, please report a bug.") + NEWLINE + // + NEWLINE + QObject::tr("Technical Details") + NEWLINE + // + "OSsl.Rq.V=" + osslReqVersion + NEWLINE + // "OSsl.Cr.V=" + osslCurVersion); - return -3; + return QV2RAY_SSL_FAIL; } -#ifdef Q_OS_WIN - // Set special font in Windows - QFont font; - font.setPointSize(9); - font.setFamily("Microsoft YaHei"); - _qApp.setFont(font); -#endif - // Set custom themes. - QStringList themes = QStyleFactory::keys(); - //_qApp.setDesktopFileName("qv2ray.desktop"); + app.InitializeGlobalVariables(); - if (themes.contains(confObject.uiConfig.theme)) - { - LOG(MODULE_INIT + " " + MODULE_UI, "Setting Qv2ray UI themes: " + confObject.uiConfig.theme) - qApp->setStyle(confObject.uiConfig.theme); - } -#if (QV2RAY_USE_BUILTIN_DARKTHEME) - LOG(MODULE_UI, "Using built-in theme.") - - if (confObject.uiConfig.useDarkTheme) - { - LOG(MODULE_UI, " --> Using built-in dark theme.") - // From https://forum.qt.io/topic/101391/windows-10-dark-theme/4 - _qApp.setStyle("Fusion"); - QPalette darkPalette; - QColor darkColor = QColor(45, 45, 45); - QColor disabledColor = QColor(127, 127, 127); - // See Qv2rayBase.hpp MACRO --> BLACK(obj) - QColor defaultTextColor = QColor(210, 210, 210); - darkPalette.setColor(QPalette::Window, darkColor); - darkPalette.setColor(QPalette::WindowText, defaultTextColor); - darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, disabledColor); - darkPalette.setColor(QPalette::Base, QColor(18, 18, 18)); - darkPalette.setColor(QPalette::AlternateBase, darkColor); - darkPalette.setColor(QPalette::ToolTipBase, defaultTextColor); - darkPalette.setColor(QPalette::ToolTipText, defaultTextColor); - darkPalette.setColor(QPalette::Text, defaultTextColor); - darkPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor); - darkPalette.setColor(QPalette::Button, darkColor); - darkPalette.setColor(QPalette::ButtonText, defaultTextColor); - darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor); - darkPalette.setColor(QPalette::BrightText, Qt::red); - darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::HighlightedText, Qt::black); - darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor); - _qApp.setPalette(darkPalette); - _qApp.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"); - } -#endif -#ifndef QT_DEBUG - - try - { -#endif - //_qApp.setAttribute(Qt::AA_DontUseNativeMenuBar); -#ifdef Q_OS_LINUX - _qApp.setFallbackSessionManagementEnabled(false); - QObject::connect(&_qApp, &QGuiApplication::commitDataRequest, [] { // - ConnectionManager->CHSaveConfigData(); - LOG(MODULE_INIT, "Quit triggered by session manager.") - }); -#endif - // Initialise Connection Handler - PluginHost = new QvPluginHost(); - ConnectionManager = new QvConfigHandler(); - // Show MainWindow - MainWindow w; - QObject::connect(&_qApp, &SingleApplication::instanceStarted, [&]() { - // When a second instance is connected, show the mainwindow. - w.show(); - w.raise(); - w.activateWindow(); - }); #ifndef Q_OS_WIN - signal(SIGUSR1, [](int) { emit MainWindow::MainWindowInstance->StartConnection(); }); - signal(SIGUSR2, [](int) { emit MainWindow::MainWindowInstance->StopConnection(); }); -#endif - auto rcode = _qApp.exec(); - delete ConnectionManager; - delete PluginHost; - LOG(MODULE_INIT, "Quitting normally") - return rcode; -#ifndef QT_DEBUG - } - catch (...) - { - QvMessageBoxWarn(nullptr, "ERROR", "There's something wrong happened and Qv2ray will quit now."); - LOG(MODULE_INIT, "EXCEPTION THROWN: " __FILE__) - return -99; - } - + signal(SIGUSR1, [](int) { ConnectionManager->RestartConnection(); }); + signal(SIGUSR2, [](int) { ConnectionManager->StopConnection(); }); #endif + return app.RunQv2ray(); +} + +int main(int argc, char *argv[]) +{ + // Register signal handlers. + signal(SIGINT, signalHandler); + signal(SIGABRT, signalHandler); + signal(SIGSEGV, signalHandler); + signal(SIGTERM, signalHandler); +#ifndef Q_OS_WIN + signal(SIGHUP, signalHandler); + signal(SIGKILL, signalHandler); +#endif + // + // This line must be called before any other ones, since we are using these + // values to identify instances. + QApplication::setApplicationVersion(QV2RAY_VERSION_STRING); + // +#ifdef QT_DEBUG + QApplication::setApplicationName("qv2ray_debug"); + QApplication::setApplicationDisplayName("Qv2ray - " + QObject::tr("Debug version")); +#else + QApplication::setApplicationName("qv2ray"); + QApplication::setApplicationDisplayName("Qv2ray"); +#endif + // + // parse the command line before starting as a Qt application + if (!Qv2rayApplication::PreInitialize(argc, argv)) + return QV2RAY_PRE_INITIALIZE_FAIL; + const auto rcode = RunQv2rayApplicationScoped(argc, argv); + if (rcode == QV2RAY_NEW_VERSION) + { + LOG(MODULE_INIT, "Starting new version of Qv2ray: " + Qv2rayProcessArgument._qvNewVersionPath) + QProcess::startDetached(Qv2rayProcessArgument._qvNewVersionPath, {}); + } + return rcode; } diff --git a/src/plugin-interface b/src/plugin-interface new file mode 160000 index 00000000..8425a473 --- /dev/null +++ b/src/plugin-interface @@ -0,0 +1 @@ +Subproject commit 8425a473155b8e914a092712495a48978fe63c26 diff --git a/src/common/JsonHighlighter.cpp b/src/ui/common/JsonHighlighter.cpp similarity index 96% rename from src/common/JsonHighlighter.cpp rename to src/ui/common/JsonHighlighter.cpp index a981d423..099c210b 100644 --- a/src/common/JsonHighlighter.cpp +++ b/src/ui/common/JsonHighlighter.cpp @@ -2,14 +2,14 @@ #include "core/settings/SettingsBackend.hpp" -namespace Qv2ray::common +namespace Qv2ray::ui { vCoreConfigJsonHighlighter::vCoreConfigJsonHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) { QTextCharFormat keywordFormat; keywordFormat.setForeground(GlobalConfig.uiConfig.useDarkTheme ? Qt::GlobalColor::magenta : Qt::darkMagenta); keywordFormat.setFontWeight(QFont::Bold); - // It's holy a dirty hack here, we'll fully ultilize the vCoreConfig models. + // It's holy a dirty hack here, we'll fully utilize the vCoreConfig models. auto keywordPatterns = { "inbounds", "outbounds", "routing", @@ -108,4 +108,4 @@ namespace Qv2ray::common setCurrentBlockState(0); } -} // namespace Qv2ray::common +} // namespace Qv2ray::ui diff --git a/src/common/JsonHighlighter.hpp b/src/ui/common/JsonHighlighter.hpp similarity index 88% rename from src/common/JsonHighlighter.hpp rename to src/ui/common/JsonHighlighter.hpp index d8eac5c0..105daffd 100644 --- a/src/common/JsonHighlighter.hpp +++ b/src/ui/common/JsonHighlighter.hpp @@ -6,7 +6,7 @@ #include #include -namespace Qv2ray::common +namespace Qv2ray::ui { struct JsonHighlightingRule { @@ -25,5 +25,5 @@ namespace Qv2ray::common void SetRule(const QString &kind, const QString &pattern, QTextCharFormat format); void highlightBlock(const QString &text) override; }; -} // namespace Qv2ray::common -using namespace Qv2ray::common; +} // namespace Qv2ray::ui +using namespace Qv2ray::ui; diff --git a/src/common/LogHighlighter.cpp b/src/ui/common/LogHighlighter.cpp similarity index 99% rename from src/common/LogHighlighter.cpp rename to src/ui/common/LogHighlighter.cpp index 46c6f42f..69aec233 100644 --- a/src/common/LogHighlighter.cpp +++ b/src/ui/common/LogHighlighter.cpp @@ -4,7 +4,7 @@ #define TO_EOL "(([\\s\\S]*)|([\\d\\D]*)|([\\w\\W]*))$" -namespace Qv2ray::common +namespace Qv2ray::ui { SyntaxHighlighter::SyntaxHighlighter(bool darkMode, QTextDocument *parent) : QSyntaxHighlighter(parent) { @@ -134,4 +134,4 @@ namespace Qv2ray::common setCurrentBlockState(0); } -} // namespace Qv2ray::common +} // namespace Qv2ray::ui diff --git a/src/common/LogHighlighter.hpp b/src/ui/common/LogHighlighter.hpp similarity index 97% rename from src/common/LogHighlighter.hpp rename to src/ui/common/LogHighlighter.hpp index 7a4284e0..f2a90d4e 100644 --- a/src/common/LogHighlighter.hpp +++ b/src/ui/common/LogHighlighter.hpp @@ -54,7 +54,7 @@ #include #include -namespace Qv2ray::common +namespace Qv2ray::ui { class SyntaxHighlighter : public QSyntaxHighlighter { @@ -89,6 +89,6 @@ namespace Qv2ray::common QTextCharFormat qvAppLogFormat; QTextCharFormat qvAppDebugLogFormat; }; -} // namespace Qv2ray::common +} // namespace Qv2ray::ui -using namespace Qv2ray::common; +using namespace Qv2ray::ui; diff --git a/src/common/QRCodeHelper.cpp b/src/ui/common/QRCodeHelper.cpp similarity index 96% rename from src/common/QRCodeHelper.cpp rename to src/ui/common/QRCodeHelper.cpp index 87cc2486..55d4ad3a 100644 --- a/src/common/QRCodeHelper.cpp +++ b/src/ui/common/QRCodeHelper.cpp @@ -4,10 +4,8 @@ #include "ByteMatrix.h" #include "MultiFormatWriter.h" #include "ReadBarcode.h" -#include "TextUtfEncoding.h" #include "base/Qv2rayBase.hpp" - -namespace Qv2ray::common +namespace Qv2ray::ui { using namespace ZXing; QString DecodeQRCode(const QImage &source) @@ -67,4 +65,4 @@ namespace Qv2ray::common return {}; } } -} // namespace Qv2ray::common +} // namespace Qv2ray::ui diff --git a/src/common/QRCodeHelper.hpp b/src/ui/common/QRCodeHelper.hpp similarity index 65% rename from src/common/QRCodeHelper.hpp rename to src/ui/common/QRCodeHelper.hpp index b69ecacc..0fd4d4a2 100644 --- a/src/common/QRCodeHelper.hpp +++ b/src/ui/common/QRCodeHelper.hpp @@ -2,9 +2,9 @@ #include #include -namespace Qv2ray::common +namespace Qv2ray::ui { QString DecodeQRCode(const QImage &img); QImage EncodeQRCode(const QString &content, const QSize &size); -} // namespace Qv2ray::common -using namespace Qv2ray::common; +} // namespace Qv2ray::ui +using namespace Qv2ray::ui; diff --git a/src/ui/common/QvDialog.hpp b/src/ui/common/QvDialog.hpp new file mode 100644 index 00000000..2959e60e --- /dev/null +++ b/src/ui/common/QvDialog.hpp @@ -0,0 +1,13 @@ +#pragma once +#include + +class QvDialog : public QDialog +{ + Q_OBJECT + public: + explicit QvDialog(QWidget *parent) : QDialog(parent){}; + virtual void processCommands(QString command, QStringList commands, QMap args) = 0; + virtual void updateColorScheme() = 0; +}; + +#define QvAutoDelete(w) connect(w, &QDialog::finished, [w]() { delete w; }) diff --git a/src/ui/common/UIBase.hpp b/src/ui/common/UIBase.hpp new file mode 100644 index 00000000..12d6718e --- /dev/null +++ b/src/ui/common/UIBase.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace Qv2ray::ui +{ + inline QPixmap ApplyEffectToImage(QPixmap src, QGraphicsEffect *effect, int extent = 0) + { + if (src.isNull() || !effect) + return src; + QGraphicsScene scene; + QGraphicsPixmapItem item; + item.setPixmap(src); + item.setGraphicsEffect(effect); + scene.addItem(&item); + QImage res(src.size() + QSize(extent * 2, extent * 2), QImage::Format_ARGB32); + res.fill(Qt::transparent); + QPainter ptr(&res); + scene.render(&ptr, QRectF(), QRectF(-extent, -extent, src.width() + extent * 2, src.height() + extent * 2)); + return QPixmap::fromImage(res); + } + + inline QPixmap BlurImage(const QPixmap &pixmap, const double rad) + { + auto pBlur = new QGraphicsBlurEffect(); + pBlur->setBlurRadius(rad); + return ApplyEffectToImage(pixmap, pBlur, 0); + } + + inline QPixmap ColorizeImage(const QPixmap &pixmap, const QColor &color, const qreal factor) + { + auto pColor = new QGraphicsColorizeEffect(); + pColor->setColor(color); + pColor->setStrength(factor); + return ApplyEffectToImage(pixmap, pColor, 0); + } + inline void FastAppendTextDocument(const QString &message, QTextDocument *doc) + { + QTextCursor cursor(doc); + cursor.movePosition(QTextCursor::End); + cursor.beginEditBlock(); + cursor.insertBlock(); + cursor.insertText(message); + cursor.endEditBlock(); + } +} // namespace Qv2ray::ui + +using namespace Qv2ray::ui; diff --git a/src/ui/editors/w_InboundEditor.cpp b/src/ui/editors/w_InboundEditor.cpp index 7a7957a1..6c161073 100644 --- a/src/ui/editors/w_InboundEditor.cpp +++ b/src/ui/editors/w_InboundEditor.cpp @@ -1,7 +1,9 @@ -#include "w_InboundEditor.hpp" +#include "w_InboundEditor.hpp" + #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" #include "core/connection/ConnectionIO.hpp" +#include "ui/common/UIBase.hpp" static bool isLoading = false; #define CHECKLOADING \ @@ -28,6 +30,9 @@ InboundEditor::InboundEditor(INBOUND root, QWidget *parent) : QDialog(parent), o else if (inboundType == "dokodemo-door") { dokoSettings = root["settings"].toObject(); + dokotproxy = QJsonIO ::GetValue(root, "streamSettings", "sockopt", "tproxy").toString(); + if (dokotproxy.isEmpty()) + dokotproxy = "off"; } else if (inboundType == "mtproto") { @@ -56,7 +61,10 @@ QvMessageBusSlotImpl(InboundEditor) { switch (msg) { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + case UPDATE_COLORSCHEME: break; } } @@ -99,6 +107,7 @@ INBOUND InboundEditor::GenerateNewRoot() else if (inboundType == "dokodemo-door") { _newRoot["settings"] = dokoSettings; + QJsonIO::SetValue(_newRoot, dokotproxy, "streamSettings", "sockopt", "tproxy"); } else if (inboundType == "mtproto") { @@ -167,6 +176,7 @@ void InboundEditor::LoadUIData() dokoUserLevelSB->setValue(dokoSettings["userLevel"].toInt()); dokoTCPCB->setChecked(dokoSettings["network"].toString().contains("tcp")); dokoUDPCB->setChecked(dokoSettings["network"].toString().contains("udp")); + dokotproxyCombo->setCurrentText(dokotproxy); // MTProto mtEMailTxt->setText(mtSettings["users"].toArray().first().toObject()["email"].toString()); mtUserLevelSB->setValue(mtSettings["users"].toArray().first().toObject()["level"].toInt()); @@ -451,6 +461,12 @@ void InboundEditor::on_dokoUserLevelSB_valueChanged(int arg1) dokoSettings["userLevel"] = arg1; } +void InboundEditor::on_dokotproxyCombo_currentIndexChanged(const QString &arg1) +{ + CHECKLOADING + dokotproxy = arg1; +} + void InboundEditor::on_mtEMailTxt_textEdited(const QString &arg1) { CHECKLOADING diff --git a/src/ui/editors/w_InboundEditor.hpp b/src/ui/editors/w_InboundEditor.hpp index 9e6a8333..c3cd1690 100644 --- a/src/ui/editors/w_InboundEditor.hpp +++ b/src/ui/editors/w_InboundEditor.hpp @@ -1,11 +1,10 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_w_InboundEditor.h" #include -#include #include class InboundEditor @@ -73,6 +72,8 @@ class InboundEditor void on_dokoUserLevelSB_valueChanged(int arg1); + void on_dokotproxyCombo_currentIndexChanged(const QString &arg1); + void on_mtEMailTxt_textEdited(const QString &arg1); void on_mtSecretTxt_textEdited(const QString &arg1); @@ -95,6 +96,7 @@ class InboundEditor QJsonObject socksSettings; QJsonObject mtSettings; QJsonObject dokoSettings; + QString dokotproxy; // QJsonObject sniffing; QJsonObject allocate; diff --git a/src/ui/editors/w_InboundEditor.ui b/src/ui/editors/w_InboundEditor.ui index be7e9834..38aabe30 100644 --- a/src/ui/editors/w_InboundEditor.ui +++ b/src/ui/editors/w_InboundEditor.ui @@ -9,8 +9,8 @@ 0 0 - 815 - 489 + 867 + 595 @@ -19,8 +19,8 @@ true - - + + @@ -113,7 +113,7 @@ - + Allocation Settings @@ -204,94 +204,7 @@ - - - - Sniffing Settings - - - - - - - 0 - 32 - - - - - 80 - 16777215 - - - - Destination Override - - - Qt::RichText - - - false - - - true - - - destOverrideList - - - - - - - - HTTP - - - Unchecked - - - - - TLS - - - Unchecked - - - - - - - - Enabled - - - - - - - Enabled - - - enableSniffingCB - - - - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - + 0 @@ -303,7 +216,7 @@ HTTP Inbound Settings - + @@ -352,32 +265,42 @@ - - - - - - - - - - + + - - - - Password - - + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + - + + + + - - - - + - - - - + Add @@ -387,14 +310,21 @@ - + Username - + + + + Password + + + + Accounts @@ -556,7 +486,7 @@ Dokodemo-Door Inbound Settings - + IP Address @@ -566,14 +496,14 @@ - + Not necessary when setting "Follow Redirect" - + Port @@ -583,7 +513,7 @@ - + 1 @@ -596,7 +526,7 @@ - + Network @@ -606,7 +536,17 @@ - + + + + Timeout + + + dokoTimeoutSB + + + + @@ -630,37 +570,7 @@ - - - - Timeout - - - dokoTimeoutSB - - - - - - - Follow Redirect - - - dokoFollowRedirectCB - - - - - - - User Level - - - dokoUserLevelSB - - - - + -1 @@ -673,21 +583,66 @@ - + Enabled - + + + + Follow Redirect + + + dokoFollowRedirectCB + + + + + + + User Level + + + dokoUserLevelSB + + + + - + - If you want to use tProxy, please go to Preference Window to enable this feature. + tproxy mode + + dokoUserLevelSB + + + + + + + off + + + + off + + + + + redirect + + + + + tproxy + + @@ -757,6 +712,81 @@ + + + + Sniffing Settings + + + + + + Enabled + + + enableSniffingCB + + + + + + + Enabled + + + + + + + + HTTP + + + Unchecked + + + + + TLS + + + Unchecked + + + + + + + + Destination Override + + + Qt::RichText + + + false + + + true + + + destOverrideList + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -767,11 +797,8 @@ httpTimeoutSpinBox httpTransparentCB httpUserLevelSB - httpAccountListBox - httpRemoveUserBtn httpAddUserTxt httpAddPasswordTxt - httpAddUserBtn socksAuthCombo socksUDPCB socksUDPIPAddrTxt @@ -785,9 +812,6 @@ dokoPortSB dokoTCPCB dokoUDPCB - dokoTimeoutSB - dokoFollowRedirectCB - dokoUserLevelSB mtEMailTxt mtSecretTxt mtUserLevelSB @@ -795,7 +819,6 @@ refreshNumberBox concurrencyNumberBox enableSniffingCB - destOverrideList diff --git a/src/ui/editors/w_JsonEditor.cpp b/src/ui/editors/w_JsonEditor.cpp index e8a541d5..341551bf 100644 --- a/src/ui/editors/w_JsonEditor.cpp +++ b/src/ui/editors/w_JsonEditor.cpp @@ -1,12 +1,13 @@ -#include "w_JsonEditor.hpp" +#include "w_JsonEditor.hpp" #include "common/QvHelpers.hpp" +#include "ui/common/UIBase.hpp" JsonEditor::JsonEditor(QJsonObject rootObject, QWidget *parent) : QDialog(parent) { setupUi(this); QvMessageBusConnect(JsonEditor); - highlighter = make_unique(jsonEditor->document()); + highlighter = std::make_unique(jsonEditor->document()); // original = rootObject; final = rootObject; diff --git a/src/ui/editors/w_JsonEditor.hpp b/src/ui/editors/w_JsonEditor.hpp index 53b171d3..1f0c3ac0 100644 --- a/src/ui/editors/w_JsonEditor.hpp +++ b/src/ui/editors/w_JsonEditor.hpp @@ -1,13 +1,12 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" -#include "common/JsonHighlighter.hpp" #include "common/QJsonModel.hpp" +#include "ui/common/JsonHighlighter.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_w_JsonEditor.h" #include -#include class JsonEditor : public QDialog @@ -35,5 +34,5 @@ class JsonEditor QJsonObject original; QJsonObject final; // - unique_ptr highlighter; + std::unique_ptr highlighter; }; diff --git a/src/ui/editors/w_OutboundEditor.cpp b/src/ui/editors/w_OutboundEditor.cpp index 40b426fd..2d330170 100644 --- a/src/ui/editors/w_OutboundEditor.cpp +++ b/src/ui/editors/w_OutboundEditor.cpp @@ -1,9 +1,9 @@ -#include "w_OutboundEditor.hpp" +#include "w_OutboundEditor.hpp" #include "core/connection/Generation.hpp" +#include "ui/common/UIBase.hpp" #include "ui/editors/w_JsonEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp" -#include "ui/w_MainWindow.hpp" #include #include @@ -80,7 +80,7 @@ QString OutboundEditor::GetFriendlyName() OUTBOUND OutboundEditor::GenerateConnectionJson() { OUTBOUNDSETTING settings; - auto streaming = GetRootObject(streamSettingsWidget->GetStreamSettings()); + auto streaming = streamSettingsWidget->GetStreamSettings().toJson(); if (outboundType == "vmess") { @@ -88,17 +88,17 @@ OUTBOUND OutboundEditor::GenerateConnectionJson() QJsonArray vnext; vmess.address = address; vmess.port = port; - vnext.append(GetRootObject(vmess)); + vnext.append(vmess.toJson()); settings.insert("vnext", vnext); } else if (outboundType == "shadowsocks") { - streaming = QJsonObject(); - LOG(MODULE_CONNECTION, "Shadowsocks outbound does not need StreamSettings.") + // streaming = QJsonObject(); + // LOG(MODULE_CONNECTION, "Shadowsocks outbound does not need StreamSettings.") QJsonArray servers; shadowsocks.address = address; shadowsocks.port = port; - servers.append(GetRootObject(shadowsocks)); + servers.append(shadowsocks.toJson()); settings["servers"] = servers; } else if (outboundType == "socks") @@ -110,10 +110,10 @@ OUTBOUND OutboundEditor::GenerateConnectionJson() } socks.address = address; socks.port = port; - streaming = QJsonObject(); - LOG(MODULE_CONNECTION, "Socks outbound does not need StreamSettings.") + // streaming = QJsonObject(); + // LOG(MODULE_CONNECTION, "Socks outbound does not need StreamSettings.") QJsonArray servers; - servers.append(GetRootObject(socks)); + servers.append(socks.toJson()); settings["servers"] = servers; } else @@ -149,7 +149,7 @@ void OutboundEditor::ReloadGUI() outboundType = originalConfig["protocol"].toString("vmess"); muxConfig = originalConfig["mux"].toObject(); useForwardProxy = originalConfig[QV2RAY_USE_FPROXY_KEY].toBool(false); - streamSettingsWidget->SetStreamObject(StructFromJsonString(JsonToString(originalConfig["streamSettings"].toObject()))); + streamSettingsWidget->SetStreamObject(StreamSettingsObject::fromJson(originalConfig["streamSettings"].toObject())); // useFPCB->setChecked(useForwardProxy); muxEnabledCB->setChecked(muxConfig["enabled"].toBool()); @@ -160,7 +160,7 @@ void OutboundEditor::ReloadGUI() if (outboundType == "vmess") { outBoundTypeCombo->setCurrentIndex(0); - vmess = StructFromJsonString(JsonToString(settings["vnext"].toArray().first().toObject())); + vmess = VMessServerObject::fromJson(settings["vnext"].toArray().first().toObject()); if (vmess.users.empty()) { vmess.users.push_back({}); @@ -170,11 +170,12 @@ void OutboundEditor::ReloadGUI() idLineEdit->setText(vmess.users.front().id); alterLineEdit->setValue(vmess.users.front().alterId); securityCombo->setCurrentText(vmess.users.front().security); + testsEnabledCombo->setCurrentText(vmess.users.front().testsEnabled); } else if (outboundType == "shadowsocks") { outBoundTypeCombo->setCurrentIndex(1); - shadowsocks = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); + shadowsocks = ShadowSocksServerObject::fromJson(settings["servers"].toArray().first().toObject()); address = shadowsocks.address; port = shadowsocks.port; // ShadowSocks Configs @@ -187,7 +188,7 @@ void OutboundEditor::ReloadGUI() else if (outboundType == "socks") { outBoundTypeCombo->setCurrentIndex(2); - socks = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); + socks = SocksServerObject::fromJson(settings["servers"].toArray().first().toObject()); address = socks.address; port = socks.port; if (socks.users.empty()) @@ -298,10 +299,15 @@ void OutboundEditor::on_outBoundTypeCombo_currentIndexChanged(int index) if (index < 3) { outboundType = outBoundTypeCombo->currentText().toLower(); + useFPCB->setEnabled(true); + useFPCB->setToolTip(tr("")); } else { outboundType = pluginWidgets.value(index).first.protocol; + useFPCB->setChecked(false); + useFPCB->setEnabled(false); + useFPCB->setToolTip(tr("Forward proxy has been disabled when using plugin outbound")); } } @@ -343,3 +349,8 @@ void OutboundEditor::on_socks_PasswordTxt_textEdited(const QString &arg1) socks.users.push_back({}); socks.users.front().pass = arg1; } + +void OutboundEditor::on_testsEnabledCombo_currentIndexChanged(const QString &arg1) +{ + vmess.users.front().testsEnabled = arg1; +} diff --git a/src/ui/editors/w_OutboundEditor.hpp b/src/ui/editors/w_OutboundEditor.hpp index 4038ee01..682838ab 100644 --- a/src/ui/editors/w_OutboundEditor.hpp +++ b/src/ui/editors/w_OutboundEditor.hpp @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" #include "components/plugins/QvPluginHost.hpp" #include "ui/messaging/QvMessageBus.hpp" @@ -44,6 +44,8 @@ class OutboundEditor void on_socks_PasswordTxt_textEdited(const QString &arg1); void on_securityCombo_currentIndexChanged(const QString &arg1); + void on_testsEnabledCombo_currentIndexChanged(const QString &arg1); + private: QString tag; void ReloadGUI(); diff --git a/src/ui/editors/w_OutboundEditor.ui b/src/ui/editors/w_OutboundEditor.ui index 3d5d3dde..abaf8f36 100644 --- a/src/ui/editors/w_OutboundEditor.ui +++ b/src/ui/editors/w_OutboundEditor.ui @@ -241,7 +241,7 @@ - 1024 + 65535 16 @@ -285,6 +285,30 @@ + + + + Tests + + + + + + + true + + + + none + + + + + VMessAEAD + + + + diff --git a/src/ui/editors/w_RoutesEditor.cpp b/src/ui/editors/w_RoutesEditor.cpp index 14479a16..8b26149b 100644 --- a/src/ui/editors/w_RoutesEditor.cpp +++ b/src/ui/editors/w_RoutesEditor.cpp @@ -1,20 +1,23 @@ #include "w_RoutesEditor.hpp" -#include "FlowScene.hpp" -#include "FlowView.hpp" -#include "FlowViewStyle.hpp" -#include "NodeStyle.hpp" +#include "components/plugins/QvPluginHost.hpp" #include "core/CoreUtils.hpp" #include "core/connection/ConnectionIO.hpp" #include "core/connection/Generation.hpp" +#include "ui/common/UIBase.hpp" #include "ui/models/InboundNodeModel.hpp" #include "ui/models/OutboundNodeModel.hpp" #include "ui/models/RuleNodeModel.hpp" -#include "ui/w_ImportConfig.hpp" +#include "ui/windows/w_ImportConfig.hpp" #include "w_InboundEditor.hpp" #include "w_JsonEditor.hpp" #include "w_OutboundEditor.hpp" +#include +#include +#include +#include + using QtNodes::FlowView; using namespace Qv2ray::ui::nodemodels; @@ -22,8 +25,8 @@ using namespace Qv2ray::ui::nodemodels; #define LOADINGCHECK \ if (isLoading) \ return; -#define GetFirstNodeData(node, nodeModel, dataModel) \ - (static_cast(static_cast((node).nodeDataModel())->outData(0).get())) +#define GetFirstNodeData(_node, name) \ + (static_cast(static_cast((nodeScene->node(_node))->nodeDataModel())->outData(0).get())) #define CHECKEMPTYRULES \ if (this->rules.isEmpty()) \ @@ -72,7 +75,7 @@ void RouteEditor::SetupNodeWidget() l->setSpacing(0); } -RouteEditor::RouteEditor(QJsonObject connection, QWidget *parent) : QDialog(parent), root(connection), original(connection) +RouteEditor::RouteEditor(QJsonObject connection, QWidget *parent) : QvDialog(parent), root(connection), original(connection) { QvMessageBusConnect(RouteEditor); setupUi(this); @@ -106,7 +109,7 @@ RouteEditor::RouteEditor(QJsonObject connection, QWidget *parent) : QDialog(pare for (auto item : root["routing"].toObject()["rules"].toArray()) { - AddRule(StructFromJsonString(JsonToString(item.toObject()))); + AddRule(RuleObject::fromJson(item.toObject())); } // Set default outboung combo text AFTER adding all outbounds. @@ -131,7 +134,10 @@ QvMessageBusSlotImpl(RouteEditor) { switch (msg) { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + MBUpdateColorSchemeDefaultImpl; } } @@ -142,14 +148,14 @@ void RouteEditor::onNodeClicked(Node &n) if (isExiting) return; - auto isOut = outboundNodes.values().contains(&n); - auto isIn = inboundNodes.values().contains(&n); - auto isRule = ruleNodes.values().contains(&n); + auto isOut = outboundNodes.values().contains(n.id()); + auto isIn = inboundNodes.values().contains(n.id()); + auto isRule = ruleNodes.values().contains(n.id()); if (isRule) { // It's a rule object - currentRuleTag = GetFirstNodeData(n, QvRuleNodeDataModel, RuleNodeData)->GetRuleTag(); + currentRuleTag = GetFirstNodeData(n.id(), RuleNode)->GetRuleTag(); DEBUG(MODULE_GRAPH, "Selecting rule: " + currentRuleTag) ShowCurrentRuleDetail(); toolBox->setCurrentIndex(1); @@ -159,27 +165,30 @@ void RouteEditor::onNodeClicked(Node &n) // It's an inbound or an outbound. QString alias; QString host; - int port; + QString port; QString protocol; if (isOut) { - alias = GetFirstNodeData(n, QvOutboundNodeModel, OutboundNodeData)->GetOutbound(); + alias = GetFirstNodeData(n.id(), OutboundNode)->GetOutbound(); QJsonObject _root = outbounds[alias].raw(); - GetOutboundInfo(OUTBOUND(_root), &host, &port, &protocol); + int _port; + GetOutboundInfo(OUTBOUND(_root), &host, &_port, &protocol); + port = QString::number(_port); } else { - alias = GetFirstNodeData(n, QvInboundNodeModel, InboundNodeData)->GetInbound(); + alias = GetFirstNodeData(n.id(), InboundNode)->GetInbound(); QJsonObject _root = inbounds[alias].raw(); host = _root["listen"].toString(); protocol = _root["protocol"].toString(); - port = _root["port"].toInt(); + // Port could be a string, or an integer. + port = _root["port"].toVariant().toString(); } tagLabel->setText(alias); protocolLabel->setText(protocol); - portLabel->setNum(port); + portLabel->setText(port); hostLabel->setText(host); } else @@ -196,10 +205,10 @@ void RouteEditor::onConnectionCreated(QtNodes::Connection const &c) return; // Connection Established - auto const sourceNode = c.getNode(PortType::Out); - auto const targetNode = c.getNode(PortType::In); + auto const &sourceNode = c.getNode(PortType::Out); + auto const &targetNode = c.getNode(PortType::In); - if (inboundNodes.values().contains(sourceNode) && ruleNodes.values().contains(targetNode)) + if (inboundNodes.values().contains(sourceNode->id()) && ruleNodes.values().contains(targetNode->id())) { // It's a inbound-rule connection onNodeClicked(*sourceNode); @@ -207,34 +216,39 @@ void RouteEditor::onConnectionCreated(QtNodes::Connection const &c) LOG(MODULE_GRAPH, "Inbound-rule new connection.") // Get all connected inbounds to this rule node. // QStringList has an helper to let us remove duplicates, see below. - QStringList _inbounds; - + QSet _inbounds; + // + // Workaround for removing a connection within the loop. + QList> connectionsTobeRemoved; for (auto &&[_, conn] : nodeScene->connections()) { - auto _connection = conn.get(); - - if (_connection->getNode(PortType::In) == targetNode && _connection->getNode(PortType::Out) == sourceNode && - _connection->id() != c.id()) + const auto &inNode = conn->getNode(PortType::In); + const auto &outNode = conn->getNode(PortType::Out); + // If a connection is not current Id, but with same IN/OUT nodes. + // It is a "duplicated" connection. + if (inNode->id() == targetNode->id() && outNode->id() == sourceNode->id() && conn->id() != c.id()) { - nodeScene->deleteConnection(*_connection); + connectionsTobeRemoved << (conn); } // Append all inbounds - else if (_connection->getNode(PortType::In) == targetNode) + if (inNode->id() == targetNode->id()) { - _inbounds.append(GetFirstNodeData(*_connection->getNode(PortType::Out), QvInboundNodeModel, InboundNodeData)->GetInbound()); + _inbounds.insert(GetFirstNodeData(outNode->id(), InboundNode)->GetInbound()); } } + for (const auto &connRemoved : connectionsTobeRemoved) + { + nodeScene->deleteConnection(*connRemoved); + } - // caused by multi-in connection - _inbounds.removeDuplicates(); - CurrentRule.inboundTag = _inbounds; + CurrentRule.inboundTag = _inbounds.values(); } - else if (ruleNodes.values().contains(sourceNode) && outboundNodes.values().contains(targetNode)) + else if (ruleNodes.values().contains(sourceNode->id()) && outboundNodes.values().contains(targetNode->id())) { // It's a rule-outbound connection onNodeClicked(*sourceNode); onNodeClicked(*targetNode); - CurrentRule.outboundTag = GetFirstNodeData((*targetNode), QvOutboundNodeModel, OutboundNodeData)->GetOutbound(); + CurrentRule.outboundTag = GetFirstNodeData(targetNode->id(), OutboundNode)->GetOutbound(); // Connecting to an outbound will disable the balancer feature. CurrentRule.QV2RAY_RULE_USE_BALANCER = false; // Update balancer settings. @@ -256,26 +270,26 @@ void RouteEditor::onConnectionDeleted(QtNodes::Connection const &c) return; // Connection Deleted - auto const source = c.getNode(PortType::Out); - auto const target = c.getNode(PortType::In); + const auto &source = c.getNode(PortType::Out); + const auto &target = c.getNode(PortType::In); - if (inboundNodes.values().contains(source) && ruleNodes.values().contains(target)) + if (inboundNodes.values().contains(source->id()) && ruleNodes.values().contains(target->id())) { // It's a inbound-rule connection onNodeClicked(*source); onNodeClicked(*target); - currentRuleTag = GetFirstNodeData(*target, QvRuleNodeDataModel, RuleNodeData)->GetRuleTag(); - auto _inboundTag = GetFirstNodeData(*source, QvInboundNodeModel, InboundNodeData)->GetInbound(); + currentRuleTag = GetFirstNodeData(target->id(), RuleNode)->GetRuleTag(); + auto _inboundTag = GetFirstNodeData(source->id(), InboundNode)->GetInbound(); LOG(MODULE_UI, "Removing inbound: " + _inboundTag + " from rule: " + currentRuleTag) CurrentRule.inboundTag.removeAll(_inboundTag); } - else if (ruleNodes.values().contains(source) && outboundNodes.values().contains(target)) + else if (ruleNodes.values().contains(source->id()) && outboundNodes.values().contains(target->id())) { // It's a rule-outbound connection onNodeClicked(*source); onNodeClicked(*target); - currentRuleTag = GetFirstNodeData(*source, QvRuleNodeDataModel, RuleNodeData)->GetRuleTag(); - auto _outboundTag = GetFirstNodeData(*target, QvOutboundNodeModel, OutboundNodeData)->GetOutbound(); + currentRuleTag = GetFirstNodeData(source->id(), RuleNode)->GetRuleTag(); + auto _outboundTag = GetFirstNodeData(target->id(), OutboundNode)->GetOutbound(); if (!CurrentRule.QV2RAY_RULE_USE_BALANCER && CurrentRule.outboundTag == _outboundTag) { @@ -312,7 +326,7 @@ CONFIGROOT RouteEditor::OpenEditor() for (auto i = 0; i < ruleListWidget->count(); i++) { auto _rule = rules[ruleListWidget->item(i)->text()]; - auto ruleJsonObject = GetRootObject(_rule); + auto ruleJsonObject = _rule.toJson(); // Process balancer for a rule if (_rule.QV2RAY_RULE_USE_BALANCER) @@ -487,7 +501,7 @@ void RouteEditor::ShowCurrentRuleDetail() void RouteEditor::on_insertDirectBtn_clicked() { - auto freedom = GenerateFreedomOUT("as-is", "", 0); + auto freedom = GenerateFreedomOUT("AsIs", "", 0); auto tag = "Freedom_" + QSTRN(QTime::currentTime().msecsSinceStartOfDay()); auto out = GenerateOutboundEntry("freedom", freedom, QJsonObject(), QJsonObject(), "0.0.0.0", tag); // ADD NODE @@ -648,8 +662,7 @@ void RouteEditor::on_enableBalancerCB_stateChanged(int arg1) auto ruleNode = ruleNodes[currentRuleTag]; for (auto &&[_, conn] : nodeScene->connections()) { - auto x = conn.get(); - if (x != nullptr && x->getNode(PortType::Out) == ruleNode) + if (conn->getNode(PortType::Out)->id() == ruleNode) { nodeScene->deleteConnection(*conn); // Since there should be only one connection from this rule node. @@ -668,16 +681,80 @@ void RouteEditor::on_addDefaultBtn_clicked() // Add default connection from GlobalConfig // auto _Inconfig = GlobalConfig.inboundConfig; + QJsonObject sniffingOff{ { "enabled", false } }; + QJsonObject sniffingOn{ { "enabled", true }, { "destOverride", QJsonArray{ "http", "tls" } } }; // - auto _in_httpConf = GenerateHTTPIN(QList() << _Inconfig.httpAccount); - auto _in_socksConf = GenerateSocksIN((_Inconfig.socks_useAuth ? "password" : "noauth"), QList() << _Inconfig.socksAccount, - _Inconfig.socksUDP, _Inconfig.socksLocalIP); - // - auto _in_HTTP = GenerateInboundEntry(_Inconfig.listenip, _Inconfig.http_port, "http", _in_httpConf, "HTTP_gConf"); - auto _in_SOCKS = GenerateInboundEntry(_Inconfig.listenip, _Inconfig.socks_port, "socks", _in_socksConf, "SOCKS_gConf"); - // - AddInbound(_in_HTTP); - AddInbound(_in_SOCKS); + if (_Inconfig.useHTTP) + { + INBOUND _in_HTTP; + _in_HTTP.insert("listen", _Inconfig.listenip); + _in_HTTP.insert("port", _Inconfig.httpSettings.port); + _in_HTTP.insert("protocol", "http"); + _in_HTTP.insert("tag", "http_gConf"); + if (!_Inconfig.httpSettings.sniffing) + { + _in_HTTP.insert("sniffing", sniffingOff); + } + else + { + _in_HTTP.insert("sniffing", sniffingOn); + } + + if (_Inconfig.httpSettings.useAuth) + { + auto httpInSettings = GenerateHTTPIN(QList() << _Inconfig.httpSettings.account); + _in_HTTP.insert("settings", httpInSettings); + } + + AddInbound(_in_HTTP); + } + if (_Inconfig.useSocks) + { + auto _in_socksConf = GenerateSocksIN((_Inconfig.socksSettings.useAuth ? "password" : "noauth"), // + QList() << _Inconfig.socksSettings.account, // + _Inconfig.socksSettings.enableUDP, // + _Inconfig.socksSettings.localIP); + auto _in_SOCKS = GenerateInboundEntry(_Inconfig.listenip, _Inconfig.socksSettings.port, "socks", _in_socksConf, "SOCKS_gConf"); + if (!_Inconfig.socksSettings.sniffing) + { + _in_SOCKS.insert("sniffing", sniffingOff); + } + else + { + _in_SOCKS.insert("sniffing", sniffingOn); + } + AddInbound(_in_SOCKS); + } + + if (_Inconfig.useTPROXY) + { + QList networks; +#define _ts_ _Inconfig.tProxySettings + if (_ts_.hasTCP) + networks << "tcp"; + if (_ts_.hasUDP) + networks << "udp"; + const auto tproxy_network = networks.join(","); + auto tproxyInSettings = GenerateDokodemoIN("", 0, tproxy_network, 0, true, 0); + // + QJsonObject tproxy_sniff{ { "enabled", true }, { "destOverride", QJsonArray{ "http", "tls" } } }; + QJsonObject tproxy_streamSettings{ { "sockopt", QJsonObject{ { "tproxy", _ts_.mode } } } }; + + auto _in_TPROXY = GenerateInboundEntry(_ts_.tProxyIP, _ts_.port, "dokodemo-door", tproxyInSettings, "TPROXY_gConf"); + _in_TPROXY.insert("sniffing", tproxy_sniff); + _in_TPROXY.insert("streamSettings", tproxy_streamSettings); + AddInbound(_in_TPROXY); + + if (!_ts_.tProxyV6IP.isEmpty()) + { + auto _in_TPROXY = GenerateInboundEntry(_ts_.tProxyV6IP, _ts_.port, "dokodemo-door", tproxyInSettings, "TPROXY_gConf_V6"); + _in_TPROXY.insert("sniffing", tproxy_sniff); + _in_TPROXY.insert("streamSettings", tproxy_streamSettings); + AddInbound(_in_TPROXY); + } +#undef _ts_ + } + CHECKEMPTYRULES } void RouteEditor::on_insertBlackBtn_clicked() @@ -743,17 +820,17 @@ void RouteEditor::on_delBtn_clicked() } auto firstNode = nodeScene->selectedNodes()[0]; - auto isInbound = inboundNodes.values().contains(firstNode); - auto isOutbound = outboundNodes.values().contains(firstNode); - auto isRule = ruleNodes.values().contains(firstNode); + auto isInbound = inboundNodes.values().contains(firstNode->id()); + auto isOutbound = outboundNodes.values().contains(firstNode->id()); + auto isRule = ruleNodes.values().contains(firstNode->id()); // Get the tag first, and call inbounds/outbounds/rules container variable // remove() Remove the node last since some events may trigger. Then remove // the node container. if (isInbound) { - currentInboundOutboundTag = GetFirstNodeData(*firstNode, QvInboundNodeModel, InboundNodeData)->GetInbound(); - nodeScene->removeNode(*inboundNodes[currentInboundOutboundTag]); + currentInboundOutboundTag = GetFirstNodeData(firstNode->id(), InboundNode)->GetInbound(); + nodeScene->removeNode(*nodeScene->node(inboundNodes[currentInboundOutboundTag])); inboundNodes.remove(currentInboundOutboundTag); // Remove corresponded inbound tags from the rules. @@ -768,7 +845,7 @@ void RouteEditor::on_delBtn_clicked() } else if (isOutbound) { - currentInboundOutboundTag = GetFirstNodeData(*firstNode, QvOutboundNodeModel, OutboundNodeData)->GetOutbound(); + currentInboundOutboundTag = GetFirstNodeData(firstNode->id(), OutboundNode)->GetOutbound(); outbounds.remove(currentInboundOutboundTag); ResolveDefaultOutboundTag(currentInboundOutboundTag, ""); @@ -783,7 +860,7 @@ void RouteEditor::on_delBtn_clicked() rules[k] = v; } - nodeScene->removeNode(*outboundNodes[currentInboundOutboundTag]); + nodeScene->removeNode(*nodeScene->node(outboundNodes[currentInboundOutboundTag])); outboundNodes.remove(currentInboundOutboundTag); } else if (isRule) @@ -791,12 +868,12 @@ void RouteEditor::on_delBtn_clicked() ruleEnableCB->setEnabled(false); ruleTagLineEdit->setEnabled(false); ruleRenameBtn->setEnabled(false); - auto RuleTag = GetFirstNodeData(*firstNode, QvRuleNodeDataModel, RuleNodeData)->GetRuleTag(); + auto RuleTag = GetFirstNodeData(firstNode->id(), RuleNode)->GetRuleTag(); currentRuleTag.clear(); routeRuleGroupBox->setEnabled(false); routeEditGroupBox->setEnabled(false); rules.remove(RuleTag); - nodeScene->removeNode(*ruleNodes[RuleTag]); + nodeScene->removeNode(*nodeScene->node(ruleNodes[RuleTag])); ruleNodes.remove(RuleTag); // // Remove item from the rule order list widget. @@ -819,13 +896,13 @@ void RouteEditor::on_editBtn_clicked() return; } - auto firstNode = nodeScene->selectedNodes().front(); - auto isInbound = inboundNodes.values().contains(firstNode); - auto isOutbound = outboundNodes.values().contains(firstNode); + const auto firstNode = nodeScene->selectedNodes().at(0); + const auto &isInbound = inboundNodes.values().contains(firstNode->id()); + const auto &isOutbound = outboundNodes.values().contains(firstNode->id()); if (isInbound) { - currentInboundOutboundTag = GetFirstNodeData(*firstNode, QvInboundNodeModel, InboundNodeData)->GetInbound(); + currentInboundOutboundTag = GetFirstNodeData(firstNode->id(), InboundNode)->GetInbound(); if (!inbounds.contains(currentInboundOutboundTag)) { @@ -876,7 +953,7 @@ void RouteEditor::on_editBtn_clicked() } else if (isOutbound) { - currentInboundOutboundTag = GetFirstNodeData(*firstNode, QvOutboundNodeModel, OutboundNodeData)->GetOutbound(); + currentInboundOutboundTag = GetFirstNodeData(firstNode->id(), OutboundNode)->GetOutbound(); if (!outbounds.contains(currentInboundOutboundTag)) { @@ -889,7 +966,21 @@ void RouteEditor::on_editBtn_clicked() auto protocol = _out["protocol"].toString().toLower(); int _code; + bool guisupport = true; if (protocol != "vmess" && protocol != "shadowsocks" && protocol != "socks") + { + guisupport = false; + auto pluginEditorWidgetsInfo = PluginHost->GetOutboundEditorWidgets(); + for (const auto &plugin : pluginEditorWidgetsInfo) + { + for (const auto &_d : plugin->OutboundCapabilities()) + { + guisupport = guisupport || protocol == _d.protocol; + } + } + } + + if (!guisupport) { QvMessageBoxWarn(this, tr("Unsupported Outbound Type"), tr("This outbound entry is not supported by the GUI editor.") + NEWLINE + diff --git a/src/ui/editors/w_RoutesEditor.hpp b/src/ui/editors/w_RoutesEditor.hpp index 8b5d25ae..dbd8c057 100644 --- a/src/ui/editors/w_RoutesEditor.hpp +++ b/src/ui/editors/w_RoutesEditor.hpp @@ -1,17 +1,14 @@ #pragma once -#include "ConnectionStyle.hpp" -#include "Node.hpp" -#include "NodeData.hpp" +#include "base/Qv2rayBase.hpp" #include "common/QvHelpers.hpp" +#include "ui/common/QvDialog.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_w_RoutesEditor.h" -#include -#include -#include -#include -#include +#include +#include +#include using QtNodes::ConnectionStyle; using QtNodes::FlowScene; @@ -27,7 +24,7 @@ enum ROUTE_EDIT_MODE }; class RouteEditor - : public QDialog + : public QvDialog , private Ui::RouteEditor { Q_OBJECT @@ -36,8 +33,10 @@ class RouteEditor explicit RouteEditor(QJsonObject connection, QWidget *parent = nullptr); ~RouteEditor(); CONFIGROOT OpenEditor(); + void processCommands(QString, QStringList, QMap) override{}; private: + void updateColorScheme() override{}; QvMessageBusSlotDecl; private slots: @@ -98,7 +97,7 @@ class RouteEditor void on_defaultOutboundCombo_currentTextChanged(const QString &arg1); public slots: - void onNodeClicked(QtNodes::Node &n); + void onNodeClicked(Node &n); void onConnectionCreated(QtNodes::Connection const &c); void onConnectionDeleted(QtNodes::Connection const &c); @@ -122,9 +121,9 @@ class RouteEditor // // ---------------------------- Node Graph Impl -------------------------- void SetupNodeWidget(); - QHash inboundNodes; - QHash outboundNodes; - QHash ruleNodes; + QHash inboundNodes; + QHash outboundNodes; + QHash ruleNodes; // FlowScene *nodeScene; // ---------------------------- Extra Source File Headers ---------------- diff --git a/src/ui/editors/w_RoutesEditor_extra.cpp b/src/ui/editors/w_RoutesEditor_extra.cpp index 59b9e49c..a180e0ee 100644 --- a/src/ui/editors/w_RoutesEditor_extra.cpp +++ b/src/ui/editors/w_RoutesEditor_extra.cpp @@ -1,5 +1,6 @@ -#include "FlowScene.hpp" +#include #include "core/CoreUtils.hpp" +#include "ui/common/UIBase.hpp" #include "ui/models/InboundNodeModel.hpp" #include "ui/models/OutboundNodeModel.hpp" #include "ui/models/RuleNodeModel.hpp" @@ -17,13 +18,13 @@ void RouteEditor::AddInbound(INBOUND in) } in["tag"] = tag; - auto _nodeData = make_unique(make_shared(tag)); + auto _nodeData = std::make_unique(std::make_shared(tag)); auto &node = nodeScene->createNode(std::move(_nodeData)); - auto pos = QPointF(); + QPointF pos; pos.setX(0 + GRAPH_GLOBAL_OFFSET_X); pos.setY(inboundNodes.count() * 130 + GRAPH_GLOBAL_OFFSET_Y); nodeScene->setNodePosition(node, pos); - inboundNodes.insert(tag, &node); + inboundNodes.insert(tag, node.id()); inbounds.insert(getTag(in), in); } @@ -37,13 +38,13 @@ void RouteEditor::AddOutbound(OUTBOUND out) } out["tag"] = tag; - auto _nodeData = make_unique(make_shared(tag)); + auto _nodeData = std::make_unique(std::make_shared(tag)); auto pos = nodeGraphWidget->pos(); pos.setX(pos.x() + 850 + GRAPH_GLOBAL_OFFSET_X); pos.setY(pos.y() + outboundNodes.count() * 120 + GRAPH_GLOBAL_OFFSET_Y); auto &node = nodeScene->createNode(std::move(_nodeData)); nodeScene->setNodePosition(node, pos); - outboundNodes.insert(tag, &node); + outboundNodes.insert(tag, node.id()); outbounds.insert(tag, out); defaultOutboundCombo->addItem(tag); } @@ -76,7 +77,7 @@ void RouteEditor::AddRule(RuleObject rule) auto pos = nodeGraphWidget->pos(); pos.setX(pos.x() + 350 + GRAPH_GLOBAL_OFFSET_X); pos.setY(pos.y() + ruleNodes.count() * 120 + GRAPH_GLOBAL_OFFSET_Y); - auto _nodeData = make_unique(make_shared(rule.QV2RAY_RULE_TAG)); + auto _nodeData = std::make_unique(std::make_shared(rule.QV2RAY_RULE_TAG)); auto &node = nodeScene->createNode(std::move(_nodeData)); nodeScene->setNodePosition(node, pos); @@ -91,7 +92,7 @@ void RouteEditor::AddRule(RuleObject rule) else { auto inboundNode = inboundNodes.value(inTag); - auto conn = nodeScene->createConnection(node, 0, *inboundNode, 0); + auto conn = nodeScene->createConnection(node, 0, *nodeScene->node(inboundNode), 0); connect(conn.get(), &QtNodes::Connection::connectionCompleted, this, &RouteEditor::onConnectionCreated); } } @@ -102,7 +103,7 @@ void RouteEditor::AddRule(RuleObject rule) if (outboundNodes.contains(rule.outboundTag)) { DEBUG(MODULE_GRAPH, "Found outbound tag: " + rule.outboundTag + ", for rule: " + rule.QV2RAY_RULE_TAG) - auto conn = nodeScene->createConnection(*outboundNodes.value(rule.outboundTag), 0, node, 0); + auto conn = nodeScene->createConnection(*nodeScene->node(outboundNodes.value(rule.outboundTag)), 0, node, 0); connect(conn.get(), &QtNodes::Connection::connectionCompleted, this, &RouteEditor::onConnectionCreated); } else @@ -113,7 +114,7 @@ void RouteEditor::AddRule(RuleObject rule) } } - this->ruleNodes.insert(rule.QV2RAY_RULE_TAG, &node); + this->ruleNodes.insert(rule.QV2RAY_RULE_TAG, node.id()); ruleListWidget->addItem(rule.QV2RAY_RULE_TAG); } @@ -131,14 +132,14 @@ void RouteEditor::RenameItemTag(ROUTE_EDIT_MODE mode, const QString originalTag, *newTag += "_" + GenerateRandomString(5); } - auto node = static_cast(ruleNodes.value(originalTag)->nodeDataModel()); + const auto &nodeDataModel = (QvRuleNodeModel *) nodeScene->node(ruleNodes.value(originalTag))->nodeDataModel(); - if (node == nullptr) + if (nodeDataModel == nullptr) { LOG(MODULE_GRAPH, "EMPTY NODE WARN") } - node->setData(*newTag); + nodeDataModel->setData(*newTag); // auto rule = rules.take(originalTag); rule.QV2RAY_RULE_TAG = *newTag; @@ -183,7 +184,7 @@ void RouteEditor::RenameItemTag(ROUTE_EDIT_MODE mode, const QString originalTag, out["tag"] = *newTag; outbounds.insert(*newTag, out); outboundNodes.insert(*newTag, outboundNodes.take(originalTag)); - auto node = static_cast(outboundNodes.value(*newTag)->nodeDataModel()); + const auto &node = (QvOutboundNodeModel *) nodeScene->node(outboundNodes.value(*newTag))->nodeDataModel(); if (node == nullptr) { @@ -193,7 +194,7 @@ void RouteEditor::RenameItemTag(ROUTE_EDIT_MODE mode, const QString originalTag, node->setData(*newTag); // Change outbound tag in rules accordingly. - for (auto k : rules.keys()) + for (const auto &k : rules.keys()) { auto v = rules.value(k); @@ -228,7 +229,7 @@ void RouteEditor::RenameItemTag(ROUTE_EDIT_MODE mode, const QString originalTag, in["tag"] = *newTag; inbounds.insert(*newTag, in); inboundNodes.insert(*newTag, inboundNodes.take(originalTag)); - auto node = static_cast(inboundNodes.value(*newTag)->nodeDataModel()); + const auto &node = (QvInboundNodeModel *) nodeScene->node(inboundNodes.value(*newTag))->nodeDataModel(); if (node == nullptr) { @@ -240,7 +241,7 @@ void RouteEditor::RenameItemTag(ROUTE_EDIT_MODE mode, const QString originalTag, // Change inbound tag in rules accordingly. // k -> rule tag // v -> rule object - for (auto k : rules.keys()) + for (const auto &k : rules.keys()) { auto v = rules.value(k); diff --git a/src/ui/messaging/QvMessageBus.hpp b/src/ui/messaging/QvMessageBus.hpp index 71cfb0e9..725c650b 100644 --- a/src/ui/messaging/QvMessageBus.hpp +++ b/src/ui/messaging/QvMessageBus.hpp @@ -25,7 +25,7 @@ break; #define MBUpdateColorSchemeDefaultImpl \ - case UPDATE_COLORSCHEME: this->UpdateColorScheme(); break; + case UPDATE_COLORSCHEME: this->updateColorScheme(); break; namespace Qv2ray::ui::messaging { @@ -41,15 +41,13 @@ namespace Qv2ray::ui::messaging /// Change Color Scheme UPDATE_COLORSCHEME }; - Q_ENUM_NS(QvMBMessage); + Q_ENUM_NS(QvMBMessage) // class QvMessageBusObject : public QObject { Q_OBJECT public: explicit QvMessageBusObject(); - - // void EmitGlobalSignal(const QvMBMessage &msg); signals: void QvSendMessage(const QvMBMessage &msg); diff --git a/src/ui/models/InboundNodeModel.cpp b/src/ui/models/InboundNodeModel.cpp index caa42543..970ef688 100644 --- a/src/ui/models/InboundNodeModel.cpp +++ b/src/ui/models/InboundNodeModel.cpp @@ -1,4 +1,4 @@ -#include "ui/models/InboundNodeModel.hpp" +#include "InboundNodeModel.hpp" QvInboundNodeModel::QvInboundNodeModel(std::shared_ptr data) : NodeDataModel() { diff --git a/src/ui/models/InboundNodeModel.hpp b/src/ui/models/InboundNodeModel.hpp index 5d3e990b..103ca655 100644 --- a/src/ui/models/InboundNodeModel.hpp +++ b/src/ui/models/InboundNodeModel.hpp @@ -1,6 +1,6 @@ #pragma once -#include "ui/models/NodeModelsBase.hpp" +#include "NodeModelsBase.hpp" #include @@ -38,8 +38,8 @@ class QvInboundNodeModel : public NodeDataModel std::shared_ptr dataType(PortType portType, PortIndex portIndex) const override { - Q_UNUSED(portType); - Q_UNUSED(portIndex); + Q_UNUSED(portType) + Q_UNUSED(portIndex) return inboundType; } @@ -53,7 +53,7 @@ class QvInboundNodeModel : public NodeDataModel } void setData(const QString &data) { - _in = make_shared(data); + _in = std::make_shared(data); _label->setText(data); _label->adjustSize(); } diff --git a/src/ui/models/NodeModelsBase.hpp b/src/ui/models/NodeModelsBase.hpp index 687fbdf1..7d49e3be 100644 --- a/src/ui/models/NodeModelsBase.hpp +++ b/src/ui/models/NodeModelsBase.hpp @@ -1,10 +1,11 @@ #pragma once -#include "NodeDataModel.hpp" -#include "PortType.hpp" +#include +#include #include "common/QvHelpers.hpp" #include +#include using QtNodes::NodeData; using QtNodes::NodeDataModel; diff --git a/src/ui/models/OutboundNodeModel.hpp b/src/ui/models/OutboundNodeModel.hpp index 780dee4e..74032afe 100644 --- a/src/ui/models/OutboundNodeModel.hpp +++ b/src/ui/models/OutboundNodeModel.hpp @@ -37,8 +37,8 @@ class QvOutboundNodeModel : public NodeDataModel std::shared_ptr dataType(PortType portType, PortIndex portIndex) const override { - Q_UNUSED(portType); - Q_UNUSED(portIndex); + Q_UNUSED(portType) + Q_UNUSED(portIndex) return outboundType; } @@ -47,16 +47,16 @@ class QvOutboundNodeModel : public NodeDataModel return _out; } - void setInData(shared_ptr, int) override + void setInData(std::shared_ptr, int) override { } - void setInData(vector>, int) override + void setInData(std::vector>, int) override { } void setData(const QString &data) { - _out = make_shared(data); + _out = std::make_shared(data); _label->setText(_out->GetOutbound()); _label->adjustSize(); } diff --git a/src/ui/models/RuleNodeModel.cpp b/src/ui/models/RuleNodeModel.cpp index 8d668ca3..10b68faf 100644 --- a/src/ui/models/RuleNodeModel.cpp +++ b/src/ui/models/RuleNodeModel.cpp @@ -1,6 +1,6 @@ #include "ui/models/RuleNodeModel.hpp" -QvRuleNodeDataModel::QvRuleNodeDataModel(std::shared_ptr data) : NodeDataModel() +QvRuleNodeModel::QvRuleNodeModel(std::shared_ptr data) : NodeDataModel() { _ruleTag = data; _label = new QLabel(); diff --git a/src/ui/models/RuleNodeModel.hpp b/src/ui/models/RuleNodeModel.hpp index 5839f6de..2a97340b 100644 --- a/src/ui/models/RuleNodeModel.hpp +++ b/src/ui/models/RuleNodeModel.hpp @@ -4,12 +4,12 @@ #include -class QvRuleNodeDataModel : public NodeDataModel +class QvRuleNodeModel : public NodeDataModel { Q_OBJECT public: - QvRuleNodeDataModel(std::shared_ptr data); - ~QvRuleNodeDataModel() + QvRuleNodeModel(std::shared_ptr data); + ~QvRuleNodeModel() { // if (_label) { // delete _label; @@ -70,12 +70,12 @@ class QvRuleNodeDataModel : public NodeDataModel void setInData(std::shared_ptr, int) override { } - void setInData(vector>, int) override + void setInData(std::vector>, int) override { } void setData(const QString &data) { - _ruleTag = make_shared(data); + _ruleTag = std::make_shared(data); _label->setText(_ruleTag->GetRuleTag()); _label->adjustSize(); } @@ -96,13 +96,13 @@ class QvRuleNodeDataModel : public NodeDataModel } std::unique_ptr clone() const override { - return std::make_unique(_ruleTag); + return std::make_unique(_ruleTag); } private: NodeValidationState modelValidationState = NodeValidationState::Warning; QString modelValidationError = tr("Missing or incorrect inputs"); // - shared_ptr _ruleTag; + std::shared_ptr _ruleTag; QLabel *_label; }; diff --git a/src/ui/styles/StyleManager.cpp b/src/ui/styles/StyleManager.cpp new file mode 100644 index 00000000..acb8420e --- /dev/null +++ b/src/ui/styles/StyleManager.cpp @@ -0,0 +1,121 @@ +#include "StyleManager.hpp" + +#include "base/Qv2rayBase.hpp" +#include "common/QvHelpers.hpp" + +#include +#include +#include +#include +#include + +constexpr auto QV2RAY_BUILT_IN_DARK_MODE_NAME = "Built-in Darkmode"; + +namespace Qv2ray::ui::styles +{ + QvStyleManager::QvStyleManager(QObject *parent) : QObject(parent) + { + ReloadStyles(); + } + + void QvStyleManager::ReloadStyles() + { + styles.clear(); + styles.insert(QV2RAY_BUILT_IN_DARK_MODE_NAME, {}); + for (const auto &key : QStyleFactory::keys()) + { + LOG(MODULE_UI, "Found factory style: " + key) + QvStyle style; + style.Name = key; + style.Type = QvStyle::QVSTYLE_FACTORY; + styles.insert(key, style); + } + + for (const auto &styleDir : Qv2rayAssetsPaths("uistyles")) + { + for (const auto &file : GetFileList(QDir(styleDir))) + { + QFileInfo fileInfo(styleDir + "/" + file); + if (fileInfo.suffix() == "css" || fileInfo.suffix() == "qss" || fileInfo.suffix() == "qvstyle") + { + LOG(MODULE_UI, "Found QSS style at: \"" + fileInfo.absoluteFilePath() + "\"") + QvStyle style; + style.Name = fileInfo.baseName(); + style.qssPath = fileInfo.absoluteFilePath(); + style.Type = QvStyle::QVSTYLE_QSS; + styles.insert(style.Name, style); + } + } + } + } + + bool QvStyleManager::ApplyStyle(const QString &style) + { + if (!styles.contains(style)) + return false; + qApp->setStyle("fusion"); + if (style == QV2RAY_BUILT_IN_DARK_MODE_NAME) + { + LOG(MODULE_UI, "Applying built-in darkmode theme.") + // From https://forum.qt.io/topic/101391/windows-10-dark-theme/4 + + QPalette darkPalette; + // + QColor darkColor(45, 45, 45); + QColor disabledColor(127, 127, 127); + QColor defaultTextColor(210, 210, 210); + // + darkPalette.setColor(QPalette::Window, darkColor); + darkPalette.setColor(QPalette::Button, darkColor); + darkPalette.setColor(QPalette::AlternateBase, darkColor); + // + darkPalette.setColor(QPalette::Text, defaultTextColor); + darkPalette.setColor(QPalette::ButtonText, defaultTextColor); + darkPalette.setColor(QPalette::WindowText, defaultTextColor); + darkPalette.setColor(QPalette::ToolTipBase, defaultTextColor); + darkPalette.setColor(QPalette::ToolTipText, defaultTextColor); + // + darkPalette.setColor(QPalette::Disabled, QPalette::Text, disabledColor); + darkPalette.setColor(QPalette::Disabled, QPalette::WindowText, disabledColor); + darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText, disabledColor); + darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText, disabledColor); + // + darkPalette.setColor(QPalette::Base, QColor(18, 18, 18)); + darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + // + darkPalette.setColor(QPalette::BrightText, Qt::red); + darkPalette.setColor(QPalette::HighlightedText, Qt::black); + qApp->setPalette(darkPalette); + qApp->setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }"); + return true; + } + + const auto &s = styles[style]; + switch (s.Type) + { + case QvStyle::QVSTYLE_QSS: + { + LOG(MODULE_UI, "Applying UI QSS style: " + s.qssPath) + const auto content = StringFromFile(s.qssPath); + qApp->setStyleSheet(content); + break; + } + case QvStyle::QVSTYLE_FACTORY: + { + LOG(MODULE_UI, "Applying UI style: " + s.Name) + const auto &_style = QStyleFactory::create(s.Name); + qApp->setPalette(_style->standardPalette()); + qApp->setStyle(_style); + qApp->setStyleSheet(""); + break; + } + default: + { + return false; + } + } + qApp->processEvents(); + return true; + } +} // namespace Qv2ray::ui::styles diff --git a/src/ui/styles/StyleManager.hpp b/src/ui/styles/StyleManager.hpp new file mode 100644 index 00000000..7e4329df --- /dev/null +++ b/src/ui/styles/StyleManager.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + +namespace Qv2ray::ui::styles +{ + struct QvStyle + { + enum StyleType + { + QVSTYLE_FACTORY, + QVSTYLE_QSS + } Type; + QString Name; + QString qssPath; + }; + + class QvStyleManager : QObject + { + public: + QvStyleManager(QObject *parent = nullptr); + inline QStringList AllStyles() const + { + return styles.keys(); + } + bool ApplyStyle(const QString &); + + private: + void ReloadStyles(); + QMap styles; + }; + + inline QvStyleManager *StyleManager = nullptr; +} // namespace Qv2ray::ui::styles + +using namespace Qv2ray::ui::styles; diff --git a/src/ui/w_MainWindow_extra.cpp b/src/ui/w_MainWindow_extra.cpp deleted file mode 100644 index 62c87969..00000000 --- a/src/ui/w_MainWindow_extra.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "common/QvHelpers.hpp" -#include "components/proxy/QvProxyConfigurator.hpp" -#include "w_MainWindow.hpp" - -void MainWindow::MWSetSystemProxy() -{ - auto inboundPorts = KernelInstance->InboundPorts(); - bool httpEnabled = inboundPorts.contains("http"); - bool socksEnabled = inboundPorts.contains("socks"); - auto httpPort = inboundPorts["http"]; - auto socksPort = inboundPorts["socks"]; - - QString proxyAddress; - - if (httpEnabled || socksEnabled) - { - proxyAddress = "127.0.0.1"; - SetSystemProxy(proxyAddress, httpPort, socksPort); - hTray.setIcon(Q_TRAYICON("tray-systemproxy.png")); - if (!GlobalConfig.uiConfig.quietMode) - { - hTray.showMessage("Qv2ray", tr("System proxy configured.")); - } - } - else - { - LOG(MODULE_PROXY, "Neither of HTTP nor SOCKS is enabled, cannot set system proxy.") - QvMessageBoxWarn(this, tr("Cannot set system proxy"), tr("Both HTTP and SOCKS inbounds are not enabled")); - } -} - -void MainWindow::MWClearSystemProxy() -{ - ClearSystemProxy(); - hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png")); - if (!GlobalConfig.uiConfig.quietMode) - { - hTray.showMessage("Qv2ray", tr("System proxy removed.")); - } -} - -void MainWindow::CheckSubscriptionsUpdate() -{ - QStringList updateList; - - auto subscriptions = ConnectionManager->Subscriptions(); - for (const auto &entry : subscriptions) - { - const auto info = ConnectionManager->GetGroupMetaObject(entry); - // - // The update is ignored. - if (info.updateInterval == 0) - continue; - // - const auto &lastRenewDate = QDateTime::fromTime_t(info.lastUpdated); - const auto &renewTime = lastRenewDate.addSecs(info.updateInterval * 86400); - LOG(MODULE_SUBSCRIPTION, // - "Subscription \"" + info.displayName + "\": " + // - NEWLINE + " --> Last renewal time: " + lastRenewDate.toString() + // - NEWLINE + " --> Renew interval: " + QSTRN(info.updateInterval) + // - NEWLINE + " --> Ideal renew time: " + renewTime.toString()) // - - if (renewTime <= QDateTime::currentDateTime()) - { - LOG(MODULE_SUBSCRIPTION, "Subscription: " + info.displayName + " needs to be updated.") - updateList.append(info.displayName); - } - } - - if (!updateList.isEmpty()) - { - QvMessageBoxWarn(this, tr("Update Subscriptions"), - tr("There are subscriptions need to be updated, please go to subscriptions window to update them.") + NEWLINE + - NEWLINE + tr("These subscriptions are out-of-date: ") + NEWLINE + updateList.join(";")); - on_subsButton_clicked(); - } -} diff --git a/src/ui/w_SubscriptionManager.cpp b/src/ui/w_SubscriptionManager.cpp deleted file mode 100644 index b776da28..00000000 --- a/src/ui/w_SubscriptionManager.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "w_SubscriptionManager.hpp" - -#include "common/QvHelpers.hpp" -#include "core/handler/ConfigHandler.hpp" -#include "core/settings/SettingsBackend.hpp" - -SubscriptionEditor::SubscriptionEditor(QWidget *parent) : QDialog(parent) -{ - setupUi(this); - QvMessageBusConnect(SubscriptionEditor); - UpdateColorScheme(); - for (auto subs : ConnectionManager->Subscriptions()) - { - subscriptionList->addTopLevelItem(new QTreeWidgetItem(QStringList{ GetDisplayName(subs), subs.toString() })); - } - if (subscriptionList->topLevelItemCount() > 0) - { - subscriptionList->setCurrentItem(subscriptionList->topLevelItem(0)); - } -} - -void SubscriptionEditor::UpdateColorScheme() -{ - addSubsButton->setIcon(QICON_R("add.png")); - removeSubsButton->setIcon(QICON_R("delete.png")); -} - -QvMessageBusSlotImpl(SubscriptionEditor) -{ - switch (msg) - { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl MBUpdateColorSchemeDefaultImpl - } -} - -tuple SubscriptionEditor::GetSelectedConfig() -{ - return { GetDisplayName(currentConnectionId), ConnectionManager->GetConnectionRoot(currentConnectionId) }; -} - -SubscriptionEditor::~SubscriptionEditor() -{ -} - -void SubscriptionEditor::on_addSubsButton_clicked() -{ - auto const key = QSTRN(QTime::currentTime().msecsSinceStartOfDay()); - auto id = ConnectionManager->CreateGroup(key, true); - // - subscriptionList->addTopLevelItem(new QTreeWidgetItem(QStringList{ key, id.toString() })); -} - -void SubscriptionEditor::on_updateButton_clicked() -{ - if (QvMessageBoxAsk(this, tr("Reload Subscription"), tr("Would you like to reload the subscription?")) == QMessageBox::Yes) - { - this->setEnabled(false); - ConnectionManager->UpdateSubscription(currentSubId); // - this->setEnabled(true); - on_subscriptionList_itemClicked(subscriptionList->currentItem(), 0); - } -} - -void SubscriptionEditor::on_removeSubsButton_clicked() -{ - if (QvMessageBoxAsk(this, tr("Deleting a subscription"), tr("All connections will be moved to default group, do you want to continue?")) == - QMessageBox::Yes) - { - ConnectionManager->DeleteGroup(currentSubId); // - auto item = subscriptionList->currentItem(); - subscriptionList->removeItemWidget(item, 0); - delete item; - if (subscriptionList->topLevelItemCount() > 0) - { - subscriptionList->setCurrentItem(subscriptionList->topLevelItem(0)); - on_subscriptionList_itemClicked(subscriptionList->topLevelItem(0), 0); - } - else - { - groupBox_2->setEnabled(false); - } - } -} - -void SubscriptionEditor::on_buttonBox_accepted() -{ - // Nothing? -} - -void SubscriptionEditor::on_subscriptionList_itemSelectionChanged() -{ - groupBox_2->setEnabled(subscriptionList->selectedItems().count() > 0); -} - -void SubscriptionEditor::on_subscriptionList_itemClicked(QTreeWidgetItem *item, int column) -{ - Q_UNUSED(column) - - if (item == nullptr) - { - return; - } - - // - groupBox_2->setEnabled(true); - currentSubId = GroupId(item->text(1)); - // - subNameTxt->setText(GetDisplayName(currentSubId)); - auto const [addr, lastUpdated, updateInterval] = ConnectionManager->GetSubscriptionData(currentSubId); - subAddrTxt->setText(addr); - lastUpdatedLabel->setText(timeToString(lastUpdated)); - updateIntervalSB->setValue(updateInterval); - // - connectionsList->clear(); - - for (auto conn : ConnectionManager->Connections(currentSubId)) - { - connectionsList->addItem(GetDisplayName(conn)); // - } -} - -void SubscriptionEditor::on_subscriptionList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous) -{ - Q_UNUSED(previous) - on_subscriptionList_itemClicked(current, 0); -} - -void SubscriptionEditor::on_subNameTxt_textEdited(const QString &arg1) -{ - subscriptionList->selectedItems().first()->setText(0, arg1); - ConnectionManager->RenameGroup(currentSubId, arg1.trimmed()); -} - -void SubscriptionEditor::on_subAddrTxt_textEdited(const QString &arg1) -{ - auto newUpdateInterval = updateIntervalSB->value(); - ConnectionManager->SetSubscriptionData(currentSubId, arg1, newUpdateInterval); -} - -void SubscriptionEditor::on_updateIntervalSB_valueChanged(double arg1) -{ - auto newAddress = subAddrTxt->text().trimmed(); - ConnectionManager->SetSubscriptionData(currentSubId, newAddress, arg1); -} - -void SubscriptionEditor::on_connectionsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - Q_UNUSED(previous) - if (current != nullptr) - { - currentConnectionId = ConnectionManager->GetConnectionIdByDisplayName(current->text(), currentSubId); - } -} diff --git a/src/ui/w_SubscriptionManager.hpp b/src/ui/w_SubscriptionManager.hpp deleted file mode 100644 index 01134023..00000000 --- a/src/ui/w_SubscriptionManager.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "base/Qv2rayBase.hpp" -#include "core/CoreSafeTypes.hpp" -#include "ui/messaging/QvMessageBus.hpp" -#include "ui_w_SubscriptionManager.h" - -#include - -class SubscriptionEditor - : public QDialog - , private Ui::w_SubscribeEditor -{ - Q_OBJECT - - public: - explicit SubscriptionEditor(QWidget *parent = nullptr); - ~SubscriptionEditor(); - tuple GetSelectedConfig(); - - private: - QvMessageBusSlotDecl; - - private slots: - void on_addSubsButton_clicked(); - - void on_updateButton_clicked(); - - void on_removeSubsButton_clicked(); - - void on_buttonBox_accepted(); - - void on_subscriptionList_itemSelectionChanged(); - - void on_subscriptionList_itemClicked(QTreeWidgetItem *item, int column); - - void on_subscriptionList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); - - void on_subNameTxt_textEdited(const QString &arg1); - - void on_subAddrTxt_textEdited(const QString &arg1); - - void on_updateIntervalSB_valueChanged(double arg1); - - void on_connectionsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - private: - void UpdateColorScheme(); - bool isUpdateInProgress = false; - GroupId currentSubId = NullGroupId; - ConnectionId currentConnectionId = NullConnectionId; -}; diff --git a/src/ui/w_SubscriptionManager.ui b/src/ui/w_SubscriptionManager.ui deleted file mode 100644 index e665ec05..00000000 --- a/src/ui/w_SubscriptionManager.ui +++ /dev/null @@ -1,313 +0,0 @@ - - - w_SubscribeEditor - - - - 0 - 0 - 655 - 440 - - - - - 655 - 440 - - - - SubscribeEditor - - - true - - - true - - - - - - Qt::Horizontal - - - QDialogButtonBox::Ok - - - - - - - Subscription List - - - - - - QAbstractItemView::SingleSelection - - - QAbstractItemView::SelectRows - - - false - - - false - - - true - - - - 1 - - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Add Subscription - - - - - - - :/assets/icons/ui_light/add.png:/assets/icons/ui_light/add.png - - - - - - - - 0 - 0 - - - - Remove Subscription - - - - - - - :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - - - - false - - - Subscription Details - - - - - - - - Subscription Name - - - - - - - - - - Subscription Address - - - - - - - - - - Update Interval - - - - - - - - - 365.000000000000000 - - - 0.500000000000000 - - - 5.000000000000000 - - - - - - - Days - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - Last Updated - - - - - - - - - - - - - - - - Connection List - - - - - - - QAbstractItemView::NoEditTriggers - - - QListView::Adjust - - - - - - - - - Update Subscription Data - - - - - - - - - - - - subscriptionList - addSubsButton - removeSubsButton - subAddrTxt - subNameTxt - updateIntervalSB - connectionsList - updateButton - - - - - - - buttonBox - accepted() - w_SubscribeEditor - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - w_SubscribeEditor - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/src/ui/widgets/ConnectionInfoWidget.cpp b/src/ui/widgets/ConnectionInfoWidget.cpp index 438f71d1..67ca9c26 100644 --- a/src/ui/widgets/ConnectionInfoWidget.cpp +++ b/src/ui/widgets/ConnectionInfoWidget.cpp @@ -1,9 +1,10 @@ #include "ConnectionInfoWidget.hpp" -#include "common/QRCodeHelper.hpp" #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" #include "core/connection/Serialization.hpp" +#include "ui/common/QRCodeHelper.hpp" +#include "ui/common/UIBase.hpp" constexpr auto INDEX_CONNECTION = 0; constexpr auto INDEX_GROUP = 1; @@ -19,7 +20,7 @@ QvMessageBusSlotImpl(ConnectionInfoWidget) } } -void ConnectionInfoWidget::UpdateColorScheme() +void ConnectionInfoWidget::updateColorScheme() { latencyBtn->setIcon(QICON_R("ping_gauge.png")); deleteBtn->setIcon(QICON_R("delete.png")); @@ -33,7 +34,7 @@ void ConnectionInfoWidget::UpdateColorScheme() auto isDarkTheme = GlobalConfig.uiConfig.useDarkTheme; qrPixmapBlured = BlurImage(ColorizeImage(qrPixmap, isDarkTheme ? QColor(Qt::black) : QColor(Qt::white), 0.7), 35); qrLabel->setPixmap(IsComplexConfig(connectionId) ? QPixmap(":/assets/icons/qv2ray.ico") : (isRealPixmapShown ? qrPixmap : qrPixmapBlured)); - connectBtn->setIcon(ConnectionManager->IsConnected(connectionId) ? QICON_R("stop.png") : QICON_R("connect.png")); + connectBtn->setIcon(KernelInstance->CurrentConnection().connectionId == connectionId ? QICON_R("stop.png") : QICON_R("connect.png")); } ConnectionInfoWidget::ConnectionInfoWidget(QWidget *parent) : QWidget(parent) @@ -41,7 +42,7 @@ ConnectionInfoWidget::ConnectionInfoWidget(QWidget *parent) : QWidget(parent) setupUi(this); // QvMessageBusConnect(ConnectionInfoWidget); - UpdateColorScheme(); + updateColorScheme(); // shareLinkTxt->setAutoFillBackground(true); shareLinkTxt->setCursor(QCursor(Qt::CursorShape::IBeamCursor)); @@ -51,16 +52,16 @@ ConnectionInfoWidget::ConnectionInfoWidget(QWidget *parent) : QWidget(parent) // connect(ConnectionManager, &QvConfigHandler::OnConnected, this, &ConnectionInfoWidget::OnConnected); connect(ConnectionManager, &QvConfigHandler::OnDisconnected, this, &ConnectionInfoWidget::OnDisConnected); - connect(ConnectionManager, &QvConfigHandler::OnConnectionModified, this, &ConnectionInfoWidget::OnConnectionModified); - connect(ConnectionManager, &QvConfigHandler::OnConnectionGroupChanged, this, &ConnectionInfoWidget::OnConnectionModified); connect(ConnectionManager, &QvConfigHandler::OnGroupRenamed, this, &ConnectionInfoWidget::OnGroupRenamed); - connect(ConnectionManager, &QvConfigHandler::OnConnectionGroupChanged, this, &ConnectionInfoWidget::OnConnectionModified); + connect(ConnectionManager, &QvConfigHandler::OnConnectionModified, this, &ConnectionInfoWidget::OnConnectionModified); + connect(ConnectionManager, &QvConfigHandler::OnConnectionLinkedWithGroup, this, &ConnectionInfoWidget::OnConnectionModified_Pair); + connect(ConnectionManager, &QvConfigHandler::OnConnectionRemovedFromGroup, this, &ConnectionInfoWidget::OnConnectionModified_Pair); } -void ConnectionInfoWidget::ShowDetails(const tuple &_identifier) +void ConnectionInfoWidget::ShowDetails(const ConnectionGroupPair &_identifier) { - this->groupId = get<0>(_identifier); - this->connectionId = get<1>(_identifier); + this->groupId = _identifier.groupId; + this->connectionId = _identifier.connectionId; bool isConnection = connectionId != NullConnectionId; // editBtn->setEnabled(isConnection); @@ -69,7 +70,7 @@ void ConnectionInfoWidget::ShowDetails(const tuple &_iden stackedWidget->setCurrentIndex(isConnection ? INDEX_CONNECTION : INDEX_GROUP); if (isConnection) { - auto shareLink = ConvertConfigToString(connectionId); + auto shareLink = ConvertConfigToString(_identifier); // shareLinkTxt->setText(shareLink); protocolLabel->setText(GetConnectionProtocolString(connectionId)); @@ -90,28 +91,28 @@ void ConnectionInfoWidget::ShowDetails(const tuple &_iden qrLabel->setPixmap(IsComplexConfig(connectionId) ? QPixmap(":/assets/icons/qv2ray.ico") : qrPixmapBlured); qrLabel->setScaledContents(true); // - connectBtn->setIcon(ConnectionManager->IsConnected(connectionId) ? QICON_R("stop.png") : QICON_R("connect.png")); + connectBtn->setIcon(KernelInstance->CurrentConnection().connectionId == connectionId ? QICON_R("stop.png") : QICON_R("connect.png")); } else { connectBtn->setIcon(QICON_R("connect.png")); groupNameLabel->setText(GetDisplayName(groupId)); QStringList shareLinks; - for (auto connection : ConnectionManager->Connections(groupId)) + for (const auto &connection : ConnectionManager->Connections(groupId)) { - shareLinks << ConvertConfigToString(connection, false); + shareLinks << ConvertConfigToString({ connection, groupId }, false); } // auto complexCount = shareLinks.removeAll(QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER); complexCount += shareLinks.removeAll(""); if (complexCount > 0) { - shareLinks << "# " + tr("(Ignored %1 complex config(s))").arg(complexCount); + shareLinks << "# " + tr("(Ignored %n complex config(s))", "", complexCount); } // groupShareTxt->setPlainText(shareLinks.join(NEWLINE)); - groupSubsLinkTxt->setText(ConnectionManager->IsSubscription(groupId) ? get<0>(ConnectionManager->GetSubscriptionData(groupId)) : - tr("Not a subscription")); + const auto &groupMetaData = ConnectionManager->GetGroupMetaObject(groupId); + groupSubsLinkTxt->setText(groupMetaData.isSubscription ? groupMetaData.subscriptionOption.address : tr("Not a subscription")); } } @@ -121,10 +122,15 @@ ConnectionInfoWidget::~ConnectionInfoWidget() void ConnectionInfoWidget::OnConnectionModified(const ConnectionId &id) { - if (id == connectionId) - ShowDetails({ GetConnectionGroupId(id), id }); + if (id == this->connectionId) + ShowDetails({ id, groupId }); } +void ConnectionInfoWidget::OnConnectionModified_Pair(const ConnectionGroupPair &id) +{ + if (id.connectionId == this->connectionId && id.groupId == this->groupId) + ShowDetails(id); +} void ConnectionInfoWidget::OnGroupRenamed(const GroupId &id, const QString &oldName, const QString &newName) { Q_UNUSED(oldName) @@ -137,13 +143,13 @@ void ConnectionInfoWidget::OnGroupRenamed(const GroupId &id, const QString &oldN void ConnectionInfoWidget::on_connectBtn_clicked() { - if (ConnectionManager->IsConnected(connectionId)) + if (ConnectionManager->IsConnected({ connectionId, groupId })) { ConnectionManager->StopConnection(); } else { - ConnectionManager->StartConnection(connectionId); + ConnectionManager->StartConnection({ connectionId, groupId }); } } @@ -163,7 +169,7 @@ void ConnectionInfoWidget::on_deleteBtn_clicked() { if (connectionId != NullConnectionId) { - ConnectionManager->DeleteConnection(connectionId); + ConnectionManager->RemoveConnectionFromGroup(connectionId, groupId); } else { @@ -194,17 +200,17 @@ bool ConnectionInfoWidget::eventFilter(QObject *object, QEvent *event) return QWidget::eventFilter(object, event); } -void ConnectionInfoWidget::OnConnected(const ConnectionId &id) +void ConnectionInfoWidget::OnConnected(const ConnectionGroupPair &id) { - if (connectionId == id) + if (id == ConnectionGroupPair{ connectionId, groupId }) { connectBtn->setIcon(QICON_R("stop.png")); } } -void ConnectionInfoWidget::OnDisConnected(const ConnectionId &id) +void ConnectionInfoWidget::OnDisConnected(const ConnectionGroupPair &id) { - if (connectionId == id) + if (id == ConnectionGroupPair{ connectionId, groupId }) { connectBtn->setIcon(QICON_R("connect.png")); } diff --git a/src/ui/widgets/ConnectionInfoWidget.hpp b/src/ui/widgets/ConnectionInfoWidget.hpp index c91f4dcd..7b9fb109 100644 --- a/src/ui/widgets/ConnectionInfoWidget.hpp +++ b/src/ui/widgets/ConnectionInfoWidget.hpp @@ -14,7 +14,7 @@ class ConnectionInfoWidget public: explicit ConnectionInfoWidget(QWidget *parent = nullptr); - void ShowDetails(const tuple &_identifier); + void ShowDetails(const ConnectionGroupPair &_identifier); ~ConnectionInfoWidget(); signals: @@ -30,14 +30,15 @@ class ConnectionInfoWidget void on_editJsonBtn_clicked(); void on_deleteBtn_clicked(); - void OnConnectionModified(const ConnectionId &id); void OnGroupRenamed(const GroupId &id, const QString &oldName, const QString &newName); - void OnConnected(const ConnectionId &id); - void OnDisConnected(const ConnectionId &id); + void OnConnected(const ConnectionGroupPair &id); + void OnDisConnected(const ConnectionGroupPair &id); + void OnConnectionModified(const ConnectionId &id); + void OnConnectionModified_Pair(const ConnectionGroupPair &id); void on_latencyBtn_clicked(); private: - void UpdateColorScheme(); + void updateColorScheme(); QvMessageBusSlotDecl; ConnectionId connectionId = NullConnectionId; GroupId groupId = NullGroupId; diff --git a/src/ui/widgets/ConnectionInfoWidget.ui b/src/ui/widgets/ConnectionInfoWidget.ui index f166b383..0545e53a 100644 --- a/src/ui/widgets/ConnectionInfoWidget.ui +++ b/src/ui/widgets/ConnectionInfoWidget.ui @@ -428,7 +428,11 @@ - + + + true + + diff --git a/src/ui/widgets/ConnectionItemWidget.cpp b/src/ui/widgets/ConnectionItemWidget.cpp index ebfa22e4..3f529b1b 100644 --- a/src/ui/widgets/ConnectionItemWidget.cpp +++ b/src/ui/widgets/ConnectionItemWidget.cpp @@ -1,6 +1,7 @@ #include "ConnectionItemWidget.hpp" #include "common/QvHelpers.hpp" +#include "core/handler/ConfigHandler.hpp" #include @@ -14,23 +15,22 @@ ConnectionItemWidget::ConnectionItemWidget(QWidget *parent) : QWidget(parent), c connect(ConnectionManager, &QvConfigHandler::OnLatencyTestFinished, this, &ConnectionItemWidget::OnLatencyTestFinished); } -ConnectionItemWidget::ConnectionItemWidget(const ConnectionId &id, QWidget *parent) : ConnectionItemWidget(parent) +ConnectionItemWidget::ConnectionItemWidget(const ConnectionGroupPair &id, QWidget *parent) : ConnectionItemWidget(parent) { - connectionId = id; - groupId = GetConnectionGroupId(id); - originalItemName = GetDisplayName(id); - itemType = NODE_ITEM; + connectionId = id.connectionId; + groupId = id.groupId; + originalItemName = GetDisplayName(id.connectionId); // indentSpacer->changeSize(10, indentSpacer->sizeHint().height()); // - auto latency = GetConnectionLatency(id); - latencyLabel->setText(latency == QVTCPING_VALUE_NODATA ? // - tr("Not Tested") : // - (latency == QVTCPING_VALUE_ERROR ? // - tr("Error") : // - (QSTRN(latency) + " ms"))); // + auto latency = GetConnectionLatency(id.connectionId); + latencyLabel->setText(latency == LATENCY_TEST_VALUE_NODATA ? // + tr("Not Tested") : // + (latency == LATENCY_TEST_VALUE_ERROR ? // + tr("Error") : // + (QSTRN(latency) + " ms"))); // // - connTypeLabel->setText(tr("Type: ") + GetConnectionProtocolString(id)); + connTypeLabel->setText(tr("Type: ") + GetConnectionProtocolString(id.connectionId)); auto [uplink, downlink] = GetConnectionUsageAmount(connectionId); dataLabel->setText(FormatBytes(uplink) + " / " + FormatBytes(downlink)); // @@ -38,7 +38,8 @@ ConnectionItemWidget::ConnectionItemWidget(const ConnectionId &id, QWidget *pare { emit RequestWidgetFocus(this); } - OnConnectionItemRenamed(id, "", originalItemName); + // Fake trigger + OnConnectionItemRenamed(id.connectionId, "", originalItemName); connect(ConnectionManager, &QvConfigHandler::OnConnectionRenamed, this, &ConnectionItemWidget::OnConnectionItemRenamed); // // Rename events @@ -58,7 +59,6 @@ ConnectionItemWidget::ConnectionItemWidget(const GroupId &id, QWidget *parent) : delete renameTxt; // groupId = id; - itemType = GROUP_HEADER_ITEM; originalItemName = GetDisplayName(id); RecalculateConnectionsCount(); // @@ -68,19 +68,19 @@ ConnectionItemWidget::ConnectionItemWidget(const GroupId &id, QWidget *parent) : // OnGroupItemRenamed(id, "", originalItemName); connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &ConnectionItemWidget::RecalculateConnectionsCount); - connect(ConnectionManager, &QvConfigHandler::OnConnectionDeleted, this, &ConnectionItemWidget::RecalculateConnectionsCount); connect(ConnectionManager, &QvConfigHandler::OnConnectionModified, this, &ConnectionItemWidget::RecalculateConnectionsCount); - connect(ConnectionManager, &QvConfigHandler::OnConnectionGroupChanged, this, &ConnectionItemWidget::RecalculateConnectionsCount); - connect(ConnectionManager, &QvConfigHandler::OnSubscriptionUpdateFinished, this, &ConnectionItemWidget::RecalculateConnectionsCount); + connect(ConnectionManager, &QvConfigHandler::OnConnectionLinkedWithGroup, this, &ConnectionItemWidget::RecalculateConnectionsCount); + connect(ConnectionManager, &QvConfigHandler::OnSubscriptionAsyncUpdateFinished, this, &ConnectionItemWidget::RecalculateConnectionsCount); + connect(ConnectionManager, &QvConfigHandler::OnConnectionRemovedFromGroup, this, &ConnectionItemWidget::RecalculateConnectionsCount); // connect(ConnectionManager, &QvConfigHandler::OnGroupRenamed, this, &ConnectionItemWidget::OnGroupItemRenamed); } void ConnectionItemWidget::BeginConnection() { - if (itemType == NODE_ITEM) + if (IsConnection()) { - ConnectionManager->StartConnection(connectionId); + ConnectionManager->StartConnection({ connectionId, groupId }); } else { @@ -88,31 +88,52 @@ void ConnectionItemWidget::BeginConnection() } } -void ConnectionItemWidget::OnConnected(const ConnectionId &id) +bool ConnectionItemWidget::NameMatched(const QString &arg) const { - if (id == connectionId) + auto searchString = arg.toLower(); + auto isGroupNameMatched = GetDisplayName(groupId).toLower().contains(arg); + + if (IsConnection()) + { + return isGroupNameMatched || GetDisplayName(connectionId).toLower().contains(searchString); + } + else + { + return isGroupNameMatched; + } +} + +void ConnectionItemWidget::RecalculateConnectionsCount() +{ + auto connectionCount = ConnectionManager->Connections(groupId).count(); + latencyLabel->setText(QSTRN(connectionCount) + " " + (connectionCount < 2 ? tr("connection") : tr("connections"))); +} + +void ConnectionItemWidget::OnConnected(const ConnectionGroupPair &id) +{ + if (id == ConnectionGroupPair{ connectionId, groupId }) { connNameLabel->setText("• " + originalItemName); - LOG(MODULE_UI, "OnConnected signal received for: " + id.toString()) + LOG(MODULE_UI, "OnConnected signal received for: " + id.connectionId.toString()) emit RequestWidgetFocus(this); } } -void ConnectionItemWidget::OnDisConnected(const ConnectionId &id) +void ConnectionItemWidget::OnDisConnected(const ConnectionGroupPair &id) { - if (id == connectionId) + if (id == ConnectionGroupPair{ connectionId, groupId }) { connNameLabel->setText(originalItemName); } } -void ConnectionItemWidget::OnConnectionStatsArrived(const ConnectionId &id, const quint64 upSpeed, const quint64 downSpeed, +void ConnectionItemWidget::OnConnectionStatsArrived(const ConnectionGroupPair &id, const quint64 upSpeed, const quint64 downSpeed, const quint64 totalUp, const quint64 totalDown) { Q_UNUSED(upSpeed) Q_UNUSED(downSpeed) - if (id == connectionId) + if (id.connectionId == connectionId) { dataLabel->setText(FormatBytes(totalUp) + " / " + FormatBytes(totalDown)); } @@ -130,11 +151,11 @@ void ConnectionItemWidget::OnLatencyTestStart(const ConnectionId &id) latencyLabel->setText(tr("Testing...")); } } -void ConnectionItemWidget::OnLatencyTestFinished(const ConnectionId &id, const uint average) +void ConnectionItemWidget::OnLatencyTestFinished(const ConnectionId &id, const int average) { if (id == connectionId) { - latencyLabel->setText(average == QVTCPING_VALUE_ERROR ? tr("Error") : QSTRN(average) + tr("ms")); + latencyLabel->setText(average == LATENCY_TEST_VALUE_ERROR ? tr("Error") : QSTRN(average) + tr("ms")); } } @@ -164,13 +185,9 @@ void ConnectionItemWidget::on_doRenameBtn_clicked() if (renameTxt->text().isEmpty()) return; if (connectionId == NullConnectionId) - { ConnectionManager->RenameGroup(groupId, renameTxt->text()); - } else - { ConnectionManager->RenameConnection(connectionId, renameTxt->text()); - } stackedWidget->setCurrentIndex(0); } @@ -178,9 +195,12 @@ void ConnectionItemWidget::OnConnectionItemRenamed(const ConnectionId &id, const { if (id == connectionId) { - connNameLabel->setText((ConnectionManager->IsConnected(id) ? "• " : "") + newName); + connNameLabel->setText((ConnectionManager->IsConnected({ connectionId, groupId }) ? "• " : "") + newName); originalItemName = newName; - this->setToolTip(newName); + const auto conn = ConnectionManager->GetConnectionMetaObject(connectionId); + this->setToolTip(newName + // + NEWLINE + tr("Last Connected: ") + timeToString(conn.lastConnected) + // + NEWLINE + tr("Last Updated: ") + timeToString(conn.lastUpdatedDate)); } } @@ -190,6 +210,9 @@ void ConnectionItemWidget::OnGroupItemRenamed(const GroupId &id, const QString & { originalItemName = newName; connNameLabel->setText(newName); - this->setToolTip(newName); + const auto grp = ConnectionManager->GetGroupMetaObject(id); + this->setToolTip(newName + NEWLINE + // + (grp.isSubscription ? (tr("Subscription") + NEWLINE) : "") + // + tr("Last Updated: ") + timeToString(grp.lastUpdatedDate)); } } diff --git a/src/ui/widgets/ConnectionItemWidget.hpp b/src/ui/widgets/ConnectionItemWidget.hpp index d8bc87db..e24effe6 100644 --- a/src/ui/widgets/ConnectionItemWidget.hpp +++ b/src/ui/widgets/ConnectionItemWidget.hpp @@ -1,23 +1,17 @@ #pragma once -#include "core/handler/ConfigHandler.hpp" +#include "base/models/QvConfigIdentifier.hpp" #include "ui_ConnectionItemWidget.h" #include -enum ITEM_TYPE -{ - GROUP_HEADER_ITEM, - NODE_ITEM -}; - class ConnectionItemWidget : public QWidget , private Ui::ConnectionWidget { Q_OBJECT public: - explicit ConnectionItemWidget(const ConnectionId &connecionId, QWidget *parent = nullptr); + explicit ConnectionItemWidget(const ConnectionGroupPair &id, QWidget *parent = nullptr); explicit ConnectionItemWidget(const GroupId &groupId, QWidget *parent = nullptr); // void BeginConnection(); @@ -25,23 +19,10 @@ class ConnectionItemWidget // void BeginRename(); void CancelRename(); - inline bool NameMatched(const QString &arg) + bool NameMatched(const QString &arg) const; + inline const ConnectionGroupPair Identifier() const { - auto searchString = arg.toLower(); - auto headerMatched = GetDisplayName(groupId).toLower().contains(arg); - - if (itemType != NODE_ITEM) - { - return headerMatched; - } - else - { - return headerMatched || GetDisplayName(connectionId).toLower().contains(searchString); - } - } - inline const tuple Identifier() const - { - return { groupId, connectionId }; + return { this->connectionId, this->groupId }; } inline bool IsRenaming() const { @@ -49,22 +30,18 @@ class ConnectionItemWidget } inline bool IsConnection() const { - return itemType == NODE_ITEM; + return connectionId != NullConnectionId; } signals: void RequestWidgetFocus(const ConnectionItemWidget *me); private slots: - void OnConnectionStatsArrived(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); - void OnConnected(const ConnectionId &id); - void OnDisConnected(const ConnectionId &id); + void OnConnected(const ConnectionGroupPair &id); + void OnDisConnected(const ConnectionGroupPair &id); + void OnConnectionStatsArrived(const ConnectionGroupPair &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); void OnLatencyTestStart(const ConnectionId &id); void OnConnectionModified(const ConnectionId &id); - void OnLatencyTestFinished(const ConnectionId &id, const uint average); - inline void RecalculateConnectionsCount() - { - auto connectionCount = ConnectionManager->Connections(groupId).count(); - latencyLabel->setText(QSTRN(connectionCount) + " " + (connectionCount < 2 ? tr("connection") : tr("connections"))); - } + void OnLatencyTestFinished(const ConnectionId &id, const int average); + void RecalculateConnectionsCount(); void OnConnectionItemRenamed(const ConnectionId &id, const QString &, const QString &newName); void OnGroupItemRenamed(const GroupId &id, const QString &, const QString &newName); void on_doRenameBtn_clicked(); @@ -72,7 +49,6 @@ class ConnectionItemWidget private: explicit ConnectionItemWidget(QWidget *parent = nullptr); QString originalItemName; - ITEM_TYPE itemType; ConnectionId connectionId; GroupId groupId; }; diff --git a/src/ui/widgets/ConnectionSettingsWidget.cpp b/src/ui/widgets/ConnectionSettingsWidget.cpp new file mode 100644 index 00000000..98dfe1f0 --- /dev/null +++ b/src/ui/widgets/ConnectionSettingsWidget.cpp @@ -0,0 +1,16 @@ +#include "ConnectionSettingsWidget.hpp" + +ConnectionSettingsWidget::ConnectionSettingsWidget(QWidget *parent) : QWidget(parent) +{ + setupUi(this); +} + +void ConnectionSettingsWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) + { + case QEvent::LanguageChange: retranslateUi(this); break; + default: break; + } +} diff --git a/src/ui/widgets/ConnectionSettingsWidget.hpp b/src/ui/widgets/ConnectionSettingsWidget.hpp new file mode 100644 index 00000000..32bb1c3c --- /dev/null +++ b/src/ui/widgets/ConnectionSettingsWidget.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "ui_ConnectionSettingsWidget.h" + +class ConnectionSettingsWidget + : public QWidget + , private Ui::ConnectionSettingsWidget +{ + Q_OBJECT + + public: + explicit ConnectionSettingsWidget(QWidget *parent = nullptr); + + protected: + void changeEvent(QEvent *e); +}; diff --git a/src/ui/widgets/ConnectionSettingsWidget.ui b/src/ui/widgets/ConnectionSettingsWidget.ui new file mode 100644 index 00000000..bb32e6ad --- /dev/null +++ b/src/ui/widgets/ConnectionSettingsWidget.ui @@ -0,0 +1,21 @@ + + + + + ConnectionSettingsWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/src/ui/widgets/DnsSettingsWidget.cpp b/src/ui/widgets/DnsSettingsWidget.cpp new file mode 100644 index 00000000..f4ad0405 --- /dev/null +++ b/src/ui/widgets/DnsSettingsWidget.cpp @@ -0,0 +1,274 @@ +#include "DnsSettingsWidget.hpp" + +#include "common/QvHelpers.hpp" +#include "components/geosite/QvGeositeReader.hpp" +#include "ui/widgets/QvAutoCompleteTextEdit.hpp" + +using Qv2ray::common::validation::IsIPv4Address; +using Qv2ray::common::validation::IsIPv6Address; +using Qv2ray::common::validation::IsValidDNSServer; +using Qv2ray::common::validation::IsValidIPAddress; + +#define CHECK_DISABLE_MOVE_BTN \ + if (serversListbox->count() <= 1) \ + { \ + moveServerUpBtn->setEnabled(false); \ + moveServerDownBtn->setEnabled(false); \ + } + +#define UPDATEUI \ + detailsSettingsGB->setEnabled(serversListbox->count() > 0); \ + serverAddressTxt->setEnabled(serversListbox->count() > 0); \ + removeServerBtn->setEnabled(serversListbox->count() > 0); \ + ProcessDnsPortEnabledState(); \ + CHECK_DISABLE_MOVE_BTN + +#define currentServerIndex serversListbox->currentRow() +DnsSettingsWidget::DnsSettingsWidget(QWidget *parent) : QWidget(parent) +{ + setupUi(this); + QvMessageBusConnect(DnsSettingsWidget); + // + auto sourceStringsDomain = ReadGeoSiteFromFile(GlobalConfig.kernelConfig.AssetsPath() + "/geosite.dat"); + auto sourceStringsIP = ReadGeoSiteFromFile(GlobalConfig.kernelConfig.AssetsPath() + "/geoip.dat"); + // + domainListTxt = new AutoCompleteTextEdit("geosite", sourceStringsDomain, this); + ipListTxt = new AutoCompleteTextEdit("geoip", sourceStringsIP, this); + connect(domainListTxt, &AutoCompleteTextEdit::textChanged, + [&]() { this->dns.servers[currentServerIndex].domains = SplitLines(domainListTxt->toPlainText()); }); + connect(ipListTxt, &AutoCompleteTextEdit::textChanged, + [&]() { this->dns.servers[currentServerIndex].expectIPs = SplitLines(ipListTxt->toPlainText()); }); + + // + domainsLayout->addWidget(domainListTxt); + expectedIPsLayout->addWidget(ipListTxt); + detailsSettingsGB->setCheckable(true); + detailsSettingsGB->setChecked(false); + UPDATEUI +} + +QvMessageBusSlotImpl(DnsSettingsWidget) +{ + switch (msg) + { + MBRetranslateDefaultImpl; + case HIDE_WINDOWS: + case SHOW_WINDOWS: + case UPDATE_COLORSCHEME: break; + } +} + +void DnsSettingsWidget::SetDNSObject(const DNSObject &_dns) +{ + this->dns = _dns; + // + dnsClientIPTxt->setText(dns.clientIp); + dnsTagTxt->setText(dns.tag); + serversListbox->clear(); + for (const auto &server : dns.servers) + { + serversListbox->addItem(server.address); + } + if (serversListbox->count() > 0) + { + serversListbox->setCurrentRow(0); + ShowCurrentDnsServerDetails(); + } + staticResolvedDomainsTable->clearContents(); + for (const auto &[host, ip] : dns.hosts.toStdMap()) + { + const auto rowId = staticResolvedDomainsTable->rowCount(); + staticResolvedDomainsTable->insertRow(rowId); + staticResolvedDomainsTable->setItem(rowId, 0, new QTableWidgetItem(host)); + staticResolvedDomainsTable->setItem(rowId, 1, new QTableWidgetItem(ip)); + } + staticResolvedDomainsTable->resizeColumnsToContents(); + UPDATEUI +} + +bool DnsSettingsWidget::CheckIsValidDNS() const +{ + if (!dns.clientIp.isEmpty() && !IsValidIPAddress(dns.clientIp)) + return false; + for (const auto &server : dns.servers) + { + if (!IsValidDNSServer(server.address)) + return false; + } + return true; +} + +void DnsSettingsWidget::ProcessDnsPortEnabledState() +{ + if (detailsSettingsGB->isChecked()){ + if (const auto addr = serverAddressTxt->text(); addr.startsWith("https:") || addr.startsWith("https+")) + { + serverPortSB->setEnabled(false); + } + else + { + serverPortSB->setEnabled(true); + } + } +} + +void DnsSettingsWidget::ShowCurrentDnsServerDetails() +{ + serverAddressTxt->setText(dns.servers[currentServerIndex].address); + // + domainListTxt->setPlainText(dns.servers[currentServerIndex].domains.join(NEWLINE)); + ipListTxt->setPlainText(dns.servers[currentServerIndex].expectIPs.join(NEWLINE)); + // + serverPortSB->setValue(dns.servers[currentServerIndex].port); + detailsSettingsGB->setChecked(dns.servers[currentServerIndex].QV2RAY_DNS_IS_COMPLEX_DNS); + // + if (serverAddressTxt->text().isEmpty() || IsValidDNSServer(serverAddressTxt->text())) + { + BLACK(serverAddressTxt) + } + else + { + RED(serverAddressTxt) + } + ProcessDnsPortEnabledState(); +} + +DNSObject DnsSettingsWidget::GetDNSObject() +{ + dns.hosts.clear(); + for (auto i = 0; i < staticResolvedDomainsTable->rowCount(); i++) + { + const auto &item1 = staticResolvedDomainsTable->item(i, 0); + const auto &item2 = staticResolvedDomainsTable->item(i, 1); + if (item1 && item2) + dns.hosts[item1->text()] = item2->text(); + } + return dns; +} + +void DnsSettingsWidget::on_dnsClientIPTxt_textEdited(const QString &arg1) +{ + dns.clientIp = arg1; +} + +void DnsSettingsWidget::on_dnsTagTxt_textEdited(const QString &arg1) +{ + dns.tag = arg1; +} +void DnsSettingsWidget::on_addServerBtn_clicked() +{ + DNSObject::DNSServerObject o; + o.address = "1.1.1.1"; + o.port = 53; + dns.servers.push_back(o); + serversListbox->addItem(o.address); + serversListbox->setCurrentRow(serversListbox->count() - 1); + UPDATEUI + ShowCurrentDnsServerDetails(); +} + +void DnsSettingsWidget::on_removeServerBtn_clicked() +{ + dns.servers.removeAt(currentServerIndex); + // Block the signals + serversListbox->blockSignals(true); + auto item = serversListbox->item(currentServerIndex); + serversListbox->removeItemWidget(item); + delete item; + serversListbox->blockSignals(false); + UPDATEUI + + if (serversListbox->count() > 0) + { + if (currentServerIndex < 0) + serversListbox->setCurrentRow(0); + ShowCurrentDnsServerDetails(); + } +} + +void DnsSettingsWidget::on_serversListbox_currentRowChanged(int currentRow) +{ + if (currentRow < 0) + return; + + moveServerUpBtn->setEnabled(true); + moveServerDownBtn->setEnabled(true); + if (currentRow == 0) + { + moveServerUpBtn->setEnabled(false); + } + if (currentRow == serversListbox->count() - 1) + { + moveServerDownBtn->setEnabled(false); + } + + ShowCurrentDnsServerDetails(); +} + +void DnsSettingsWidget::on_moveServerUpBtn_clicked() +{ + auto temp = dns.servers[currentServerIndex - 1]; + dns.servers[currentServerIndex - 1] = dns.servers[currentServerIndex]; + dns.servers[currentServerIndex] = temp; + + serversListbox->currentItem()->setText(dns.servers[currentServerIndex].address); + serversListbox->setCurrentRow(currentServerIndex - 1); + serversListbox->currentItem()->setText(dns.servers[currentServerIndex].address); +} + +void DnsSettingsWidget::on_moveServerDownBtn_clicked() +{ + auto temp = dns.servers[currentServerIndex + 1]; + dns.servers[currentServerIndex + 1] = dns.servers[currentServerIndex]; + dns.servers[currentServerIndex] = temp; + + serversListbox->currentItem()->setText(dns.servers[currentServerIndex].address); + serversListbox->setCurrentRow(currentServerIndex + 1); + serversListbox->currentItem()->setText(dns.servers[currentServerIndex].address); +} + +void DnsSettingsWidget::on_serverAddressTxt_textEdited(const QString &arg1) +{ + dns.servers[currentServerIndex].address = arg1; + serversListbox->currentItem()->setText(arg1); + if (arg1.isEmpty() || IsValidDNSServer(arg1)) + { + BLACK(serverAddressTxt) + } + else + { + RED(serverAddressTxt) + } + + ProcessDnsPortEnabledState(); +} + +void DnsSettingsWidget::on_serverPortSB_valueChanged(int arg1) +{ + dns.servers[currentServerIndex].port = arg1; +} + +void DnsSettingsWidget::on_addStaticHostBtn_clicked() +{ + if (staticResolvedDomainsTable->rowCount() >= 0) + staticResolvedDomainsTable->insertRow(staticResolvedDomainsTable->rowCount()); +} + +void DnsSettingsWidget::on_removeStaticHostBtn_clicked() +{ + if (staticResolvedDomainsTable->rowCount() >= 0) + staticResolvedDomainsTable->removeRow(staticResolvedDomainsTable->currentRow()); + staticResolvedDomainsTable->resizeColumnsToContents(); +} + +void DnsSettingsWidget::on_staticResolvedDomainsTable_cellChanged(int, int) +{ + staticResolvedDomainsTable->resizeColumnsToContents(); +} + +void DnsSettingsWidget::on_detailsSettingsGB_toggled(bool arg1) +{ + if (currentServerIndex >= 0) + dns.servers[currentServerIndex].QV2RAY_DNS_IS_COMPLEX_DNS = arg1; + // detailsSettingsGB->setChecked(dns.servers[currentServerIndex].QV2RAY_DNS_IS_COMPLEX_DNS); +} diff --git a/src/ui/widgets/DnsSettingsWidget.hpp b/src/ui/widgets/DnsSettingsWidget.hpp new file mode 100644 index 00000000..dbc9c473 --- /dev/null +++ b/src/ui/widgets/DnsSettingsWidget.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "base/Qv2rayBase.hpp" +#include "ui/messaging/QvMessageBus.hpp" +#include "ui_DnsSettingsWidget.h" + +namespace Qv2ray::ui::widgets +{ + class AutoCompleteTextEdit; +} + +class DnsSettingsWidget + : public QWidget + , private Ui::DnsSettingsWidget +{ + Q_OBJECT + + public: + explicit DnsSettingsWidget(QWidget *parent = nullptr); + void SetDNSObject(const DNSObject &dns); + DNSObject GetDNSObject(); + bool CheckIsValidDNS() const; + + private slots: + void on_dnsClientIPTxt_textEdited(const QString &arg1); + void on_dnsTagTxt_textEdited(const QString &arg1); + void on_addServerBtn_clicked(); + void on_removeServerBtn_clicked(); + void on_serversListbox_currentRowChanged(int currentRow); + void on_moveServerUpBtn_clicked(); + void on_moveServerDownBtn_clicked(); + void on_serverAddressTxt_textEdited(const QString &arg1); + void on_serverPortSB_valueChanged(int arg1); + void on_addStaticHostBtn_clicked(); + void on_removeStaticHostBtn_clicked(); + void on_detailsSettingsGB_toggled(bool arg1); + void on_staticResolvedDomainsTable_cellChanged(int row, int column); + + private: + void ShowCurrentDnsServerDetails(); + void ProcessDnsPortEnabledState(); + QvMessageBusSlotDecl; + DNSObject dns; + // int currentServerIndex; + // + Qv2ray::ui::widgets::AutoCompleteTextEdit *domainListTxt; + Qv2ray::ui::widgets::AutoCompleteTextEdit *ipListTxt; +}; diff --git a/src/ui/widgets/DnsSettingsWidget.ui b/src/ui/widgets/DnsSettingsWidget.ui new file mode 100644 index 00000000..89859a7d --- /dev/null +++ b/src/ui/widgets/DnsSettingsWidget.ui @@ -0,0 +1,319 @@ + + + DnsSettingsWidget + + + + 0 + 0 + 533 + 379 + + + + Form + + + + + + + + Client IP + + + + + + + The current system's IP address is used to notify the server of the client's location when querying DNS. + +It cannot be a private address. + + + + + + + Tag + + + + + + + (V2Ray 4.13+) The query traffic sent by this DNS, except for localhost and DOHL modes, will carry this identifier, which can be matched with inboundTag in the route. + + + + + + + + + 0 + + + + DNS Servers + + + + + + + + DNS List + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + :/assets/icons/ui_light/add.png:/assets/icons/ui_light/add.png + + + + + + + + + + + :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + DNS Server Settings + + + + + + + + Address + + + + + + + + + + + + Detail Settings + + + true + + + + + + Expectd IPs + + + + + + + + + + Domains + + + + + + + + + + + + Port + + + + + + + Port for DNS server. Normally it's 53. +This entry is ignored by V2Ray core when using DoH servers. + + + QAbstractSpinBox::NoButtons + + + 1 + + + 65535 + + + 53 + + + + + + + + + + + + + + + + Statically Resolved Domains + + + + + + QAbstractItemView::AllEditTriggers + + + QAbstractItemView::ScrollPerPixel + + + QAbstractItemView::ScrollPerPixel + + + + Domain + + + + + Resolved IP + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + :/assets/icons/ui_light/add.png:/assets/icons/ui_light/add.png + + + + + + + + + + + :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + diff --git a/src/ui/widgets/InboundSettingsWidget.cpp b/src/ui/widgets/InboundSettingsWidget.cpp new file mode 100644 index 00000000..2810577f --- /dev/null +++ b/src/ui/widgets/InboundSettingsWidget.cpp @@ -0,0 +1,26 @@ +#include "InboundSettingsWidget.hpp" +InboundSettingsWidget::InboundSettingsWidget(QWidget *parent) : QWidget(parent) +{ + setupUi(this); +} + +QvMessageBusSlotImpl(InboundSettingsWidget) +{ + switch (msg) + { + MBRetranslateDefaultImpl; + case HIDE_WINDOWS: + case SHOW_WINDOWS: break; + default: break; + } +} + +void InboundSettingsWidget::changeEvent(QEvent *e) +{ + QWidget::changeEvent(e); + switch (e->type()) + { + case QEvent::LanguageChange: retranslateUi(this); break; + default: break; + } +} diff --git a/src/ui/widgets/InboundSettingsWidget.hpp b/src/ui/widgets/InboundSettingsWidget.hpp new file mode 100644 index 00000000..1f1c1241 --- /dev/null +++ b/src/ui/widgets/InboundSettingsWidget.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "ui/messaging/QvMessageBus.hpp" +#include "ui_InboundSettingsWidget.h" + +class InboundSettingsWidget + : public QWidget + , private Ui::InboundSettingsWidget +{ + Q_OBJECT + QvMessageBusSlotDecl; + + public: + explicit InboundSettingsWidget(QWidget *parent = nullptr); + + protected: + void changeEvent(QEvent *e); +}; diff --git a/src/ui/widgets/InboundSettingsWidget.ui b/src/ui/widgets/InboundSettingsWidget.ui new file mode 100644 index 00000000..cb8d57f7 --- /dev/null +++ b/src/ui/widgets/InboundSettingsWidget.ui @@ -0,0 +1,21 @@ + + + + + InboundSettingsWidget + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + diff --git a/src/ui/widgets/QvAutoCompleteTextEdit.cpp b/src/ui/widgets/QvAutoCompleteTextEdit.cpp index ef793f1d..910d5d55 100644 --- a/src/ui/widgets/QvAutoCompleteTextEdit.cpp +++ b/src/ui/widgets/QvAutoCompleteTextEdit.cpp @@ -63,10 +63,10 @@ namespace Qv2ray::ui::widgets { - AutoCompleteTextEdit::AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent) : QTextEdit(parent) + AutoCompleteTextEdit::AutoCompleteTextEdit(const QString &prefix, const QStringList &sourceStrings, QWidget *parent) : QPlainTextEdit(parent) { this->prefix = prefix; - this->setLineWrapMode(QTextEdit::NoWrap); + this->setLineWrapMode(QPlainTextEdit::NoWrap); c = new QCompleter(this); c->setModel(new QStringListModel(sourceStrings, c)); c->setWidget(this); @@ -108,7 +108,7 @@ namespace Qv2ray::ui::widgets if (c) c->setWidget(this); - QTextEdit::focusInEvent(e); + QPlainTextEdit::focusInEvent(e); } void AutoCompleteTextEdit::keyPressEvent(QKeyEvent *e) @@ -142,7 +142,7 @@ namespace Qv2ray::ui::widgets } } - QTextEdit::keyPressEvent(e); + QPlainTextEdit::keyPressEvent(e); if (!c || (hasCtrlOrShiftModifier && e->text().isEmpty())) return; diff --git a/src/ui/widgets/QvAutoCompleteTextEdit.hpp b/src/ui/widgets/QvAutoCompleteTextEdit.hpp index f369cb34..e2e930c8 100644 --- a/src/ui/widgets/QvAutoCompleteTextEdit.hpp +++ b/src/ui/widgets/QvAutoCompleteTextEdit.hpp @@ -50,14 +50,14 @@ #pragma once #include -#include +#include QT_BEGIN_NAMESPACE class QCompleter; QT_END_NAMESPACE namespace Qv2ray::ui::widgets { - class AutoCompleteTextEdit : public QTextEdit + class AutoCompleteTextEdit : public QPlainTextEdit { Q_OBJECT diff --git a/src/ui/widgets/RouteSettingsMatrix.cpp b/src/ui/widgets/RouteSettingsMatrix.cpp index 1a6ff3d7..f18777ac 100644 --- a/src/ui/widgets/RouteSettingsMatrix.cpp +++ b/src/ui/widgets/RouteSettingsMatrix.cpp @@ -4,6 +4,7 @@ #include "components/geosite/QvGeositeReader.hpp" #include "components/route/RouteSchemeIO.hpp" #include "components/route/presets/RouteScheme_V2rayN.hpp" +#include "ui/common/UIBase.hpp" #include #include @@ -48,7 +49,7 @@ QList RouteSettingsMatrixWidget::getBuiltInSchemes() return list; } -QAction *RouteSettingsMatrixWidget::schemeToAction(const QString &name, const Qv2ray::base::config::Qv2rayRouteConfig &scheme) +QAction *RouteSettingsMatrixWidget::schemeToAction(const QString &name, const QvConfig_Route &scheme) { QAction *action = new QAction(this); action->setText(name); @@ -56,22 +57,22 @@ QAction *RouteSettingsMatrixWidget::schemeToAction(const QString &name, const Qv return action; } -void RouteSettingsMatrixWidget::SetRouteConfig(const Qv2rayRouteConfig &conf) +void RouteSettingsMatrixWidget::SetRouteConfig(const QvConfig_Route &conf) { domainStrategyCombo->setCurrentText(conf.domainStrategy); // - directDomainTxt->setText(conf.domains.direct.join(NEWLINE)); - proxyDomainTxt->setText(conf.domains.proxy.join(NEWLINE)); - blockDomainTxt->setText(conf.domains.block.join(NEWLINE)); + directDomainTxt->setPlainText(conf.domains.direct.join(NEWLINE)); + proxyDomainTxt->setPlainText(conf.domains.proxy.join(NEWLINE)); + blockDomainTxt->setPlainText(conf.domains.block.join(NEWLINE)); // - blockIPTxt->setText(conf.ips.block.join(NEWLINE)); - directIPTxt->setText(conf.ips.direct.join(NEWLINE)); - proxyIPTxt->setText(conf.ips.proxy.join(NEWLINE)); + blockIPTxt->setPlainText(conf.ips.block.join(NEWLINE)); + directIPTxt->setPlainText(conf.ips.direct.join(NEWLINE)); + proxyIPTxt->setPlainText(conf.ips.proxy.join(NEWLINE)); } -Qv2rayRouteConfig RouteSettingsMatrixWidget::GetRouteConfig() const +QvConfig_Route RouteSettingsMatrixWidget::GetRouteConfig() const { - config::Qv2rayRouteConfig conf; + QvConfig_Route conf; conf.domainStrategy = this->domainStrategyCombo->currentText(); conf.domains.block = SplitLines(blockDomainTxt->toPlainText().replace(" ", "")); conf.domains.direct = SplitLines(directDomainTxt->toPlainText().replace(" ", "")); @@ -104,7 +105,7 @@ void RouteSettingsMatrixWidget::on_importSchemeBtn_clicked() // read the file and parse back to struct. // if error occurred on parsing, an exception will be thrown. auto content = StringFromFile(ACCESS_OPTIONAL_VALUE(filePath)); - auto scheme = StructFromJsonString(content); + auto scheme = Qv2rayRouteScheme::fromJson(JsonFromString(content)); // show the information of this scheme to user, // and ask user if he/she wants to import and apply this. @@ -116,12 +117,12 @@ void RouteSettingsMatrixWidget::on_importSchemeBtn_clicked() return; // write the scheme onto the window - this->SetRouteConfig(static_cast(scheme)); + this->SetRouteConfig(static_cast(scheme)); // done LOG(MODULE_SETTINGS, "Imported route config: " + scheme.name + " by: " + scheme.author) } - catch (exception e) + catch (std::exception &e) { LOG(MODULE_UI, "Exception: " + QString(e.what())) // TODO: Give some error as Notification @@ -174,16 +175,15 @@ void RouteSettingsMatrixWidget::on_exportSchemeBtn_clicked() scheme.domains = config.domains; // serialize and write out - auto content = StructToJsonString(scheme); + auto content = JsonToString(scheme.toJson()); StringToFile(content, ACCESS_OPTIONAL_VALUE(savePath)); // done // TODO: Give some success as Notification QvMessageBoxInfo(this, dialogTitle, tr("Your route scheme has been successfully exported!")); } - catch (exception) + catch (...) { - // TODO: Give some error as Notification } } @@ -197,7 +197,7 @@ std::optional RouteSettingsMatrixWidget::saveFileDialog() { QFileDialog dialog; dialog.setFileMode(QFileDialog::AnyFile); - dialog.setOption(QFileDialog::Option::DontConfirmOverwrite, !true); + dialog.setOption(QFileDialog::Option::DontConfirmOverwrite, false); dialog.setNameFilter(tr("QvRoute Schemes(*.json)")); dialog.setAcceptMode(QFileDialog::AcceptMode::AcceptSave); if (!dialog.exec() || dialog.selectedFiles().length() != 1) diff --git a/src/ui/widgets/RouteSettingsMatrix.hpp b/src/ui/widgets/RouteSettingsMatrix.hpp index 248f099a..78b5a227 100644 --- a/src/ui/widgets/RouteSettingsMatrix.hpp +++ b/src/ui/widgets/RouteSettingsMatrix.hpp @@ -1,6 +1,6 @@ #pragma once #include "QvAutoCompleteTextEdit.hpp" -#include "base/models/QvSettingsObject.hpp" +#include "base/Qv2rayBase.hpp" #include "ui_RouteSettingsMatrix.h" #include @@ -15,15 +15,15 @@ class RouteSettingsMatrixWidget public: RouteSettingsMatrixWidget(const QString &assetsDirPath, QWidget *parent = nullptr); - void SetRouteConfig(const Qv2ray::base::config::Qv2rayRouteConfig &conf); - Qv2ray::base::config::Qv2rayRouteConfig GetRouteConfig() const; + void SetRouteConfig(const QvConfig_Route &conf); + QvConfig_Route GetRouteConfig() const; ~RouteSettingsMatrixWidget(); private: std::optional openFileDialog(); std::optional saveFileDialog(); QList getBuiltInSchemes(); - QAction *schemeToAction(const QString &name, const Qv2ray::base::config::Qv2rayRouteConfig &scheme); + QAction *schemeToAction(const QString &name, const QvConfig_Route &scheme); private: QMenu *builtInSchemesMenu; diff --git a/src/ui/widgets/StreamSettingsWidget.cpp b/src/ui/widgets/StreamSettingsWidget.cpp index eea7fa3e..ca7cd11c 100644 --- a/src/ui/widgets/StreamSettingsWidget.cpp +++ b/src/ui/widgets/StreamSettingsWidget.cpp @@ -13,12 +13,10 @@ QvMessageBusSlotImpl(StreamSettingsWidget) { switch (msg) { + MBRetranslateDefaultImpl; case UPDATE_COLORSCHEME: case HIDE_WINDOWS: - case SHOW_WINDOWS: - break; - // - MBRetranslateDefaultImpl + case SHOW_WINDOWS: break; } } @@ -37,11 +35,12 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso) serverNameTxt->setText(stream.tlsSettings.serverName); allowInsecureCB->setChecked(stream.tlsSettings.allowInsecure); allowInsecureCiphersCB->setChecked(stream.tlsSettings.allowInsecureCiphers); + disableSessionResumptionCB->setChecked(stream.tlsSettings.disableSessionResumption); alpnTxt->setPlainText(stream.tlsSettings.alpn.join(NEWLINE)); // TCP tcpHeaderTypeCB->setCurrentText(stream.tcpSettings.header.type); - tcpRequestTxt->setPlainText(StructToJsonString(stream.tcpSettings.header.request)); - tcpRespTxt->setPlainText(StructToJsonString(stream.tcpSettings.header.response)); + tcpRequestTxt->setPlainText(JsonToString(stream.tcpSettings.header.request.toJson())); + tcpRespTxt->setPlainText(JsonToString(stream.tcpSettings.header.response.toJson())); // HTTP httpHostTxt->setPlainText(stream.httpSettings.host.join(NEWLINE)); httpPathTxt->setText(stream.httpSettings.path); @@ -63,6 +62,7 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso) kcpUploadCapacSB->setValue(stream.kcpSettings.uplinkCapacity); kcpDownCapacitySB->setValue(stream.kcpSettings.downlinkCapacity); kcpWriteBufferSB->setValue(stream.kcpSettings.writeBufferSize); + kcpSeedTxt->setText(stream.kcpSettings.seed); // DS dsPathTxt->setText(stream.dsSettings.path); // QUIC @@ -251,18 +251,18 @@ void StreamSettingsWidget::on_dsPathTxt_textEdited(const QString &arg1) void StreamSettingsWidget::on_tcpRequestEditBtn_clicked() { JsonEditor w(JsonFromString(tcpRequestTxt->toPlainText()), this); - auto rString = JsonToString(w.OpenEditor()); - tcpRequestTxt->setPlainText(rString); - auto tcpReqObject = StructFromJsonString(rString); + auto rJson = w.OpenEditor(); + tcpRequestTxt->setPlainText(JsonToString(rJson)); + auto tcpReqObject = HTTPRequestObject::fromJson(rJson); stream.tcpSettings.header.request = tcpReqObject; } void StreamSettingsWidget::on_tcpResponseEditBtn_clicked() { JsonEditor w(JsonFromString(tcpRespTxt->toPlainText()), this); - auto rString = JsonToString(w.OpenEditor()); - tcpRespTxt->setPlainText(rString); - auto tcpRspObject = StructFromJsonString(rString); + auto rJson = w.OpenEditor(); + tcpRespTxt->setPlainText(JsonToString(rJson)); + auto tcpRspObject = HTTPResponseObject::fromJson(rJson); stream.tcpSettings.header.response = tcpRspObject; } @@ -290,3 +290,13 @@ void StreamSettingsWidget::on_allowInsecureCiphersCB_stateChanged(int arg1) { stream.tlsSettings.allowInsecureCiphers = arg1 == Qt::Checked; } + +void StreamSettingsWidget::on_disableSessionResumptionCB_stateChanged(int arg1) +{ + stream.tlsSettings.disableSessionResumption = arg1 == Qt::Checked; +} + +void StreamSettingsWidget::on_kcpSeedTxt_textEdited(const QString &arg1) +{ + stream.kcpSettings.seed = arg1; +} diff --git a/src/ui/widgets/StreamSettingsWidget.hpp b/src/ui/widgets/StreamSettingsWidget.hpp index 8393a9a4..ef285b9c 100644 --- a/src/ui/widgets/StreamSettingsWidget.hpp +++ b/src/ui/widgets/StreamSettingsWidget.hpp @@ -1,10 +1,11 @@ #pragma once -#include "QWidget" #include "base/Qv2rayBase.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_StreamSettingsWidget.h" +#include + class StreamSettingsWidget : public QWidget , private Ui::StreamSettingsWidget @@ -79,6 +80,10 @@ class StreamSettingsWidget void on_allowInsecureCiphersCB_stateChanged(int arg1); + void on_disableSessionResumptionCB_stateChanged(int arg1); + + void on_kcpSeedTxt_textEdited(const QString &arg1); + private: QvMessageBusSlotDecl; StreamSettingsObject stream; diff --git a/src/ui/widgets/StreamSettingsWidget.ui b/src/ui/widgets/StreamSettingsWidget.ui index 0b401485..a22ef2dc 100644 --- a/src/ui/widgets/StreamSettingsWidget.ui +++ b/src/ui/widgets/StreamSettingsWidget.ui @@ -6,8 +6,8 @@ 0 0 - 384 - 422 + 455 + 501 @@ -437,18 +437,15 @@ - - - - Qt::Vertical + + + + Seed (Experimental) - - - 20 - 40 - - - + + + + @@ -628,33 +625,6 @@ TLS Settings - - - - Allow Insecure Certificates - - - - - - - Server - - - - - - - - - - ALPN - - - - - - @@ -662,6 +632,13 @@ + + + + Allow Insecure Certificates + + + @@ -669,6 +646,36 @@ + + + + Server + + + + + + + + + + ALPN + + + + + + + + + + Disable Session Resumption + + + false + + + diff --git a/src/ui/windows/w_GroupManager.cpp b/src/ui/windows/w_GroupManager.cpp new file mode 100644 index 00000000..823a65eb --- /dev/null +++ b/src/ui/windows/w_GroupManager.cpp @@ -0,0 +1,479 @@ +#include "w_GroupManager.hpp" + +#include "common/QvHelpers.hpp" +#include "core/connection/Generation.hpp" +#include "core/handler/ConfigHandler.hpp" +#include "core/handler/RouteHandler.hpp" +#include "core/settings/SettingsBackend.hpp" +#include "ui/widgets/DnsSettingsWidget.hpp" +#include "ui/widgets/RouteSettingsMatrix.hpp" + +#include +#include + +#define SELECTED_ROWS_INDEX \ + ([&]() { \ + const auto &__selection = connectionsTable->selectedItems(); \ + QSet rows; \ + for (const auto &selection : __selection) \ + { \ + rows.insert(connectionsTable->row(selection)); \ + } \ + return rows; \ + }()) + +#define GET_SELECTED_CONNECTION_IDS(connectionIdList) \ + ([&]() { \ + QList _list; \ + for (const auto &i : connectionIdList) \ + { \ + _list.push_back(ConnectionId(connectionsTable->item(i, 0)->data(Qt::UserRole).toString())); \ + } \ + return _list; \ + }()) +GroupManager::GroupManager(QWidget *parent) : QvDialog(parent) +{ + setupUi(this); + QvMessageBusConnect(GroupManager); + // + dnsSettingsWidget = new DnsSettingsWidget(this); + routeSettingsWidget = new RouteSettingsMatrixWidget(GlobalConfig.kernelConfig.AssetsPath(), this); + // + dnsSettingsGB->setLayout(new QGridLayout(dnsSettingsGB)); + dnsSettingsGB->layout()->addWidget(dnsSettingsWidget); + // + routeSettingsGB->setLayout(new QGridLayout(routeSettingsGB)); + routeSettingsGB->layout()->addWidget(routeSettingsWidget); + // + updateColorScheme(); + connectionListRCMenu->addSection(tr("Connection Management")); + connectionListRCMenu->addAction(exportConnectionAction); + connectionListRCMenu->addAction(deleteConnectionAction); + connectionListRCMenu->addSeparator(); + connectionListRCMenu->addMenu(connectionListRCMenu_CopyToMenu); + connectionListRCMenu->addMenu(connectionListRCMenu_MoveToMenu); + connectionListRCMenu->addMenu(connectionListRCMenu_LinkToMenu); + // + connect(exportConnectionAction, &QAction::triggered, this, &GroupManager::onRCMExportConnectionTriggered); + connect(deleteConnectionAction, &QAction::triggered, this, &GroupManager::onRCMDeleteConnectionTriggered); + // + connect(ConnectionManager, &QvConfigHandler::OnConnectionLinkedWithGroup, this, &GroupManager::reloadCurrentGroup); + // + connect(ConnectionManager, &QvConfigHandler::OnGroupCreated, this, &GroupManager::reloadGroupRCMActions); + connect(ConnectionManager, &QvConfigHandler::OnGroupDeleted, this, &GroupManager::reloadGroupRCMActions); + connect(ConnectionManager, &QvConfigHandler::OnGroupRenamed, this, &GroupManager::reloadGroupRCMActions); + // + for (const auto &group : ConnectionManager->AllGroups()) + { + auto item = new QListWidgetItem(GetDisplayName(group)); + item->setData(Qt::UserRole, group.toString()); + groupList->addItem(item); + } + if (groupList->count() > 0) + { + groupList->setCurrentItem(groupList->item(0)); + } + else + { + groupInfoGroupBox->setEnabled(false); + tabWidget->setEnabled(false); + } + reloadGroupRCMActions(); +} + +void GroupManager::onRCMDeleteConnectionTriggered() +{ + const auto list = GET_SELECTED_CONNECTION_IDS(SELECTED_ROWS_INDEX); + for (const auto &item : list) + { + ConnectionManager->RemoveConnectionFromGroup(ConnectionId(item), currentGroupId); + } + reloadConnectionsList(currentGroupId); +} + +void GroupManager::onRCMExportConnectionTriggered() +{ + const auto &list = GET_SELECTED_CONNECTION_IDS(SELECTED_ROWS_INDEX); + QFileDialog d; + switch (list.count()) + { + case 0: return; + case 1: + { + const auto id = ConnectionId(list.first()); + auto filePath = d.getSaveFileName(this, GetDisplayName(id)); + if (filePath.isEmpty()) + return; + auto root = RouteManager->GenerateFinalConfig({ id, currentGroupId }); + // + // Apply export filter + ExportConnectionFilter(root); + // + if (filePath.endsWith(".json")) + { + filePath += ".json"; + } + // + StringToFile(JsonToString(root), filePath); + QDesktopServices::openUrl(QUrl::fromLocalFile(QFileInfo(filePath).absoluteDir().absolutePath())); + break; + } + default: + { + const auto path = d.getExistingDirectory(); + if (path.isEmpty()) + return; + for (const auto &connId : list) + { + ConnectionId id(connId); + auto root = RouteManager->GenerateFinalConfig({ id, currentGroupId }); + // + // Apply export filter + ExportConnectionFilter(root); + // + const auto fileName = RemoveInvalidFileName(GetDisplayName(id)) + ".json"; + StringToFile(JsonToString(root), path + "/" + fileName); + } + QDesktopServices::openUrl(QUrl::fromLocalFile(path)); + break; + } + } +} + +void GroupManager::reloadGroupRCMActions() +{ + connectionListRCMenu_CopyToMenu->clear(); + connectionListRCMenu_MoveToMenu->clear(); + connectionListRCMenu_LinkToMenu->clear(); + for (const auto &group : ConnectionManager->AllGroups()) + { + auto cpAction = new QAction(GetDisplayName(group), connectionListRCMenu_CopyToMenu); + auto mvAction = new QAction(GetDisplayName(group), connectionListRCMenu_MoveToMenu); + auto lnAction = new QAction(GetDisplayName(group), connectionListRCMenu_LinkToMenu); + // + cpAction->setData(group.toString()); + mvAction->setData(group.toString()); + lnAction->setData(group.toString()); + // + connectionListRCMenu_CopyToMenu->addAction(cpAction); + connectionListRCMenu_MoveToMenu->addAction(mvAction); + connectionListRCMenu_LinkToMenu->addAction(lnAction); + // + connect(cpAction, &QAction::triggered, this, &GroupManager::onRCMActionTriggered_Copy); + connect(mvAction, &QAction::triggered, this, &GroupManager::onRCMActionTriggered_Move); + connect(lnAction, &QAction::triggered, this, &GroupManager::onRCMActionTriggered_Link); + } +} + +void GroupManager::reloadConnectionsList(const GroupId &group) +{ + if (group == NullGroupId) + return; + connectionsTable->clearContents(); + connectionsTable->model()->removeRows(0, connectionsTable->rowCount()); + const auto &connections = ConnectionManager->Connections(group); + for (auto i = 0; i < connections.count(); i++) + { + const auto &conn = connections.at(i); + connectionsTable->insertRow(i); + auto displayNameItem = new QTableWidgetItem(GetDisplayName(conn)); + displayNameItem->setData(Qt::UserRole, conn.toString()); + auto typeItem = new QTableWidgetItem(GetConnectionProtocolString(conn)); + const auto [type, host, port] = GetConnectionInfo(conn); + auto hostPortItem = new QTableWidgetItem(host + ":" + QSTRN(port)); + // + QStringList groupsNamesString; + for (const auto &group : ConnectionManager->GetGroupId(conn)) + { + groupsNamesString.append(GetDisplayName(group)); + } + auto groupsItem = new QTableWidgetItem(groupsNamesString.join(";")); + connectionsTable->setItem(i, 0, displayNameItem); + connectionsTable->setItem(i, 1, typeItem); + connectionsTable->setItem(i, 2, hostPortItem); + connectionsTable->setItem(i, 3, groupsItem); + } + connectionsTable->resizeColumnsToContents(); +} + +void GroupManager::onRCMActionTriggered_Copy() +{ + const auto _sender = qobject_cast(sender()); + const GroupId groupId{ _sender->data().toString() }; + // + const auto list = GET_SELECTED_CONNECTION_IDS(SELECTED_ROWS_INDEX); + for (const auto &connId : list) + { + const auto &connectionId = ConnectionId(connId); + ConnectionManager->CreateConnection(ConnectionManager->GetConnectionRoot(connectionId), GetDisplayName(connectionId), groupId, true); + } + reloadConnectionsList(currentGroupId); +} + +void GroupManager::onRCMActionTriggered_Link() +{ + const auto _sender = qobject_cast(sender()); + const GroupId groupId{ _sender->data().toString() }; + // + const auto list = GET_SELECTED_CONNECTION_IDS(SELECTED_ROWS_INDEX); + for (const auto &connId : list) + { + ConnectionManager->LinkConnectionWithGroup(ConnectionId(connId), groupId); + } + reloadConnectionsList(currentGroupId); +} + +void GroupManager::onRCMActionTriggered_Move() +{ + const auto _sender = qobject_cast(sender()); + const GroupId groupId{ _sender->data().toString() }; + // + const auto list = GET_SELECTED_CONNECTION_IDS(SELECTED_ROWS_INDEX); + for (const auto &connId : list) + { + ConnectionManager->MoveConnectionFromToGroup(ConnectionId(connId), currentGroupId, groupId); + } + reloadConnectionsList(currentGroupId); +} + +void GroupManager::updateColorScheme() +{ + addGroupButton->setIcon(QICON_R("add.png")); + removeGroupButton->setIcon(QICON_R("delete.png")); +} + +QvMessageBusSlotImpl(GroupManager) +{ + switch (msg) + { + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + MBUpdateColorSchemeDefaultImpl + } +} + +std::tuple GroupManager::GetSelectedConfig() +{ + return { GetDisplayName(currentConnectionId), ConnectionManager->GetConnectionRoot(currentConnectionId) }; +} + +GroupManager::~GroupManager() +{ + DEBUG(MODULE_UI, "Group window destructor.") +} + +void GroupManager::on_addGroupButton_clicked() +{ + auto const key = tr("New Group") + " - " + GenerateRandomString(5); + auto id = ConnectionManager->CreateGroup(key, false); + // + auto item = new QListWidgetItem(key); + item->setData(Qt::UserRole, id.toString()); + groupList->addItem(item); +} + +void GroupManager::on_updateButton_clicked() +{ + if (QvMessageBoxAsk(this, tr("Update Subscription"), tr("Would you like to update the subscription?")) == QMessageBox::Yes) + { + this->setEnabled(false); + ConnectionManager->UpdateSubscription(currentGroupId); + this->setEnabled(true); + on_groupList_itemClicked(groupList->currentItem()); + } +} + +void GroupManager::on_removeGroupButton_clicked() +{ + if (QvMessageBoxAsk(this, tr("Deleting a subscription"), tr("All connections will be moved to default group, do you want to continue?")) == + QMessageBox::Yes) + { + ConnectionManager->DeleteGroup(currentGroupId); + auto item = groupList->currentItem(); + groupList->removeItemWidget(item); + delete item; + if (groupList->count() > 0) + { + groupList->setCurrentItem(groupList->item(0)); + on_groupList_itemClicked(groupList->item(0)); + } + else + { + groupInfoGroupBox->setEnabled(false); + tabWidget->setEnabled(false); + } + } +} + +void GroupManager::on_buttonBox_accepted() +{ + if (currentGroupId != NullGroupId) + { + const auto routeId = ConnectionManager->GetGroupRoutingId(currentGroupId); + RouteManager->SetDNSSettings(routeId, dnsSettingsGB->isChecked(), dnsSettingsWidget->GetDNSObject()); + RouteManager->SetAdvancedRouteSettings(routeId, routeSettingsGB->isChecked(), routeSettingsWidget->GetRouteConfig()); + } + // Nothing? +} + +void GroupManager::on_groupList_itemSelectionChanged() +{ + groupInfoGroupBox->setEnabled(groupList->selectedItems().count() > 0); +} + +void GroupManager::on_groupList_itemClicked(QListWidgetItem *item) +{ + if (item == nullptr) + { + return; + } + groupInfoGroupBox->setEnabled(true); + currentGroupId = GroupId(item->data(Qt::UserRole).toString()); + // + groupNameTxt->setText(GetDisplayName(currentGroupId)); + const auto &groupMetaObject = ConnectionManager->GetGroupMetaObject(currentGroupId); + groupIsSubscriptionGroup->setChecked(groupMetaObject.isSubscription); + subAddrTxt->setText(groupMetaObject.subscriptionOption.address); + lastUpdatedLabel->setText(timeToString(groupMetaObject.lastUpdatedDate)); + createdAtLabel->setText(timeToString(groupMetaObject.creationDate)); + updateIntervalSB->setValue(groupMetaObject.subscriptionOption.updateInterval); + IncludeKeywords->clear(); + for (const auto &key : groupMetaObject.subscriptionOption.IncludeKeywords) + { + auto str = key.trimmed(); + if (!str.isEmpty()) + { + IncludeKeywords->appendPlainText(str); + } + } + ExcludeKeywords->clear(); + for (const auto &key : groupMetaObject.subscriptionOption.ExcludeKeywords) + { + auto str = key.trimmed(); + if (!str.isEmpty()) + { + ExcludeKeywords->appendPlainText(str); + } + } + IncludeRelation->setCurrentIndex(groupMetaObject.subscriptionOption.IncludeRelation); + ExcludeRelation->setCurrentIndex(groupMetaObject.subscriptionOption.ExcludeRelation); + // + // Load DNS / Route config + const auto routeId = ConnectionManager->GetGroupRoutingId(currentGroupId); + { + const auto &[overrideDns, dns] = RouteManager->GetDNSSettings(routeId); + dnsSettingsWidget->SetDNSObject(dns); + dnsSettingsGB->setChecked(overrideDns); + } + { + const auto &[overrideRoute, route] = RouteManager->GetAdvancedRoutingSettings(routeId); + routeSettingsWidget->SetRouteConfig(route); + routeSettingsGB->setChecked(overrideRoute); + } + reloadConnectionsList(currentGroupId); +} + +void GroupManager::on_IncludeRelation_currentTextChanged(const QString &) +{ + ConnectionManager->SetSubscriptionIncludeRelation(currentGroupId, (SubscriptionFilterRelation) IncludeRelation->currentIndex()); +} + +void GroupManager::on_ExcludeRelation_currentTextChanged(const QString &) +{ + ConnectionManager->SetSubscriptionExcludeRelation(currentGroupId, (SubscriptionFilterRelation) ExcludeRelation->currentIndex()); +} + +void GroupManager::on_IncludeKeywords_textChanged() +{ + QStringList keywords = IncludeKeywords->toPlainText().replace("\r", "").split("\n"); + ConnectionManager->SetSubscriptionIncludeKeywords(currentGroupId, keywords); +} + +void GroupManager::on_ExcludeKeywords_textChanged() +{ + QStringList keywords = ExcludeKeywords->toPlainText().replace("\r", "").split("\n"); + ConnectionManager->SetSubscriptionExcludeKeywords(currentGroupId, keywords); +} + +void GroupManager::on_groupList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *priv) +{ + if (priv) + { + const auto group = ConnectionManager->GetGroupMetaObject(currentGroupId); + RouteManager->SetDNSSettings(group.routeConfigId, dnsSettingsGB->isChecked(), dnsSettingsWidget->GetDNSObject()); + RouteManager->SetAdvancedRouteSettings(group.routeConfigId, routeSettingsGB->isChecked(), routeSettingsWidget->GetRouteConfig()); + } + if (current) + { + on_groupList_itemClicked(current); + } +} + +void GroupManager::on_subAddrTxt_textEdited(const QString &arg1) +{ + ConnectionManager->SetSubscriptionData(currentGroupId, std::nullopt, arg1); +} + +void GroupManager::on_updateIntervalSB_valueChanged(double arg1) +{ + ConnectionManager->SetSubscriptionData(currentGroupId, std::nullopt, std::nullopt, arg1); +} + +void GroupManager::on_connectionsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + Q_UNUSED(previous) + if (current != nullptr) + { + currentConnectionId = ConnectionId(current->data(Qt::UserRole).toString()); + } +} + +void GroupManager::on_groupIsSubscriptionGroup_clicked(bool checked) +{ + ConnectionManager->SetSubscriptionData(currentGroupId, checked); +} + +void GroupManager::on_groupNameTxt_textEdited(const QString &arg1) +{ + groupList->selectedItems().first()->setText(arg1); + ConnectionManager->RenameGroup(currentGroupId, arg1.trimmed()); +} + +void GroupManager::on_deleteSelectedConnBtn_clicked() +{ + onRCMDeleteConnectionTriggered(); +} + +void GroupManager::on_exportSelectedConnBtn_clicked() +{ + if (connectionsTable->selectedItems().isEmpty()) + { + connectionsTable->selectAll(); + } + onRCMExportConnectionTriggered(); +} + +void GroupManager::ExportConnectionFilter(CONFIGROOT &root) +{ + root.remove("api"); + root.remove("stats"); + QJsonArray inbounds = root["inbounds"].toArray(); + for (int i = root["inbounds"].toArray().count() - 1; i >= 0; i--) + { + auto obj = root["inbounds"].toArray().at(i).toObject(); + if (obj["tag"] == API_TAG_INBOUND) + { + inbounds.removeAt(i); + } + } + root["inbounds"] = inbounds; +} + +#undef GET_SELECTED_CONNECTION_IDS + +void GroupManager::on_connectionsTable_customContextMenuRequested(const QPoint &pos) +{ + Q_UNUSED(pos) + connectionListRCMenu->popup(QCursor::pos()); +} diff --git a/src/ui/windows/w_GroupManager.hpp b/src/ui/windows/w_GroupManager.hpp new file mode 100644 index 00000000..6488b87f --- /dev/null +++ b/src/ui/windows/w_GroupManager.hpp @@ -0,0 +1,90 @@ +#pragma once + +#include "base/Qv2rayBase.hpp" +#include "ui/common/QvDialog.hpp" +#include "ui/messaging/QvMessageBus.hpp" +#include "ui_w_GroupManager.h" + +#include + +class DnsSettingsWidget; +class RouteSettingsMatrixWidget; + +class GroupManager + : public QvDialog + , private Ui::w_GroupManager +{ + Q_OBJECT + + public: + explicit GroupManager(QWidget *parent = nullptr); + ~GroupManager(); + std::tuple GetSelectedConfig(); + void processCommands(QString command, QStringList commands, QMap) override + { + const static QMap indexMap{ { "connection", 0 }, // + { "subscription", 1 }, // + { "dns", 2 }, // + { "route", 3 } }; + if (commands.isEmpty()) + return; + if (command == "open") + { + const auto c = commands.takeFirst(); + tabWidget->setCurrentIndex(indexMap[c]); + } + } + + private: + QvMessageBusSlotDecl; + + private slots: + void on_addGroupButton_clicked(); + void on_updateButton_clicked(); + void on_removeGroupButton_clicked(); + void on_buttonBox_accepted(); + void on_groupList_itemSelectionChanged(); + void on_IncludeRelation_currentTextChanged(const QString &arg1); + void on_ExcludeRelation_currentTextChanged(const QString &arg1); + void on_IncludeKeywords_textChanged(); + void on_ExcludeKeywords_textChanged(); + void on_groupList_itemClicked(QListWidgetItem *item); + void on_groupList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_subAddrTxt_textEdited(const QString &arg1); + void on_updateIntervalSB_valueChanged(double arg1); + void on_connectionsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_groupIsSubscriptionGroup_clicked(bool checked); + void on_groupNameTxt_textEdited(const QString &arg1); + void onRCMDeleteConnectionTriggered(); + void onRCMExportConnectionTriggered(); + void on_deleteSelectedConnBtn_clicked(); + void on_exportSelectedConnBtn_clicked(); + void on_connectionsTable_customContextMenuRequested(const QPoint &pos); + + private: + void reloadCurrentGroup() + { + this->reloadConnectionsList(currentGroupId); + } + void updateColorScheme() override; + void reloadConnectionsList(const GroupId &group); + void onRCMActionTriggered_Move(); + void onRCMActionTriggered_Copy(); + void onRCMActionTriggered_Link(); + void reloadGroupRCMActions(); + // + void ExportConnectionFilter(CONFIGROOT &root); + // + DnsSettingsWidget *dnsSettingsWidget; + RouteSettingsMatrixWidget *routeSettingsWidget; + // + QMenu *connectionListRCMenu = new QMenu(this); + QAction *exportConnectionAction = new QAction(tr("Export Connection(s)"), connectionListRCMenu); + QAction *deleteConnectionAction = new QAction(tr("Delete Connection(s)"), connectionListRCMenu); + QMenu *connectionListRCMenu_CopyToMenu = new QMenu(tr("Copy to...")); + QMenu *connectionListRCMenu_MoveToMenu = new QMenu(tr("Move to...")); + QMenu *connectionListRCMenu_LinkToMenu = new QMenu(tr("Link to...")); + bool isUpdateInProgress = false; + GroupId currentGroupId = NullGroupId; + ConnectionId currentConnectionId = NullConnectionId; +}; diff --git a/src/ui/windows/w_GroupManager.ui b/src/ui/windows/w_GroupManager.ui new file mode 100644 index 00000000..eb99d5d7 --- /dev/null +++ b/src/ui/windows/w_GroupManager.ui @@ -0,0 +1,483 @@ + + + w_GroupManager + + + + 0 + 0 + 936 + 621 + + + + Group Editor + + + false + + + true + + + + + + Group Info + + + + + + Group Name + + + + + + + + + + Created At + + + + + + + + + + + + + + + + + Group List + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Add Subscription + + + + + + + :/assets/icons/ui_light/add.png:/assets/icons/ui_light/add.png + + + + + + + + 0 + 0 + + + + Remove Subscription + + + + + + + :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + 0 + + + + Connections + + + + + + Delete Selection + + + + + + + Qt::CustomContextMenu + + + QAbstractItemView::SelectRows + + + + Name + + + + + Type + + + + + Host / Port + + + + + Groups + + + + + + + + Export Selection + + + + + + + + Subscription Settings + + + + + + This group is a subscription + + + true + + + false + + + + QLayout::SetDefaultConstraint + + + QFormLayout::ExpandingFieldsGrow + + + QFormLayout::DontWrapRows + + + + + Subscription Address + + + + + + + + + + Last Updated + + + + + + + + + + + + + + Update Notify Interval + + + + + + + + + 365.000000000000000 + + + 0.500000000000000 + + + 5.000000000000000 + + + + + + + Days + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Import Filters + + + + + + Leave blank to exclude nothing + + + + + + + + And + + + + + Or + + + + + + + + Relation + + + + + + + Leave blank to include all + + + + + + + + And + + + + + Or + + + + + + + + Relation + + + + + + + Only import when containing... + + + + + + + Only import when NOT containing... + + + + + + + + + + + + + Update Subscription + + + + + + + + DNS Settings + + + + + + Override Global DNS Settings + + + true + + + false + + + + + + + + Advanced Route Settings + + + + + + Override Global Advanced Route Settings + + + true + + + false + + + + + + + + + + + groupList + addGroupButton + removeGroupButton + updateIntervalSB + + + + + + + buttonBox + accepted() + w_GroupManager + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + w_GroupManager + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui/w_ImportConfig.cpp b/src/ui/windows/w_ImportConfig.cpp similarity index 69% rename from src/ui/w_ImportConfig.cpp rename to src/ui/windows/w_ImportConfig.cpp index 95f6d991..18313dc1 100644 --- a/src/ui/w_ImportConfig.cpp +++ b/src/ui/windows/w_ImportConfig.cpp @@ -1,34 +1,33 @@ -#include "w_ImportConfig.hpp" +#include "w_ImportConfig.hpp" -#include "common/QRCodeHelper.hpp" -#include "core/CoreUtils.hpp" -#include "core/connection/ConnectionIO.hpp" #include "core/connection/Serialization.hpp" #include "core/handler/ConfigHandler.hpp" -#include "core/kernel/V2rayKernelInteractions.hpp" +#include "ui/common/QRCodeHelper.hpp" #include "ui/editors/w_JsonEditor.hpp" #include "ui/editors/w_OutboundEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp" -#include "ui/w_SubscriptionManager.hpp" +#include "ui/windows/w_GroupManager.hpp" #include "w_ScreenShot_Core.hpp" -#include -#include -#include -#include -#include -#include -#include +constexpr auto LINK_PAGE = 0; +constexpr auto QRCODE_PAGE = 1; +constexpr auto MANUAL_PAGE = 2; +constexpr auto ADVANCED_PAGE = 3; -ImportConfigWindow::ImportConfigWindow(QWidget *parent) : QDialog(parent) +ImportConfigWindow::ImportConfigWindow(QWidget *parent) : QvDialog(parent) { setupUi(this); - // nameTxt->setText(tr("My Connection Imported at: ") + QDateTime::currentDateTime().toString("MM-dd hh:mm")); + // nameTxt->setText(tr("New Connection") + QDateTime::currentDateTime().toString("MM-dd hh:mm")); QvMessageBusConnect(ImportConfigWindow); RESTORE_RUNTIME_CONFIG(screenShotHideQv2ray, hideQv2rayCB->setChecked) + // + for (const auto &gid : ConnectionManager->AllGroups()) + { + groupCombo->addItem(GetDisplayName(gid), gid.toString()); + } } -void ImportConfigWindow::UpdateColorScheme() +void ImportConfigWindow::updateColorScheme() { // Stub } @@ -46,6 +45,7 @@ QvMessageBusSlotImpl(ImportConfigWindow) ImportConfigWindow::~ImportConfigWindow() { + DEBUG(MODULE_UI, "Import window destructor."); } QMultiHash ImportConfigWindow::SelectConnection(bool outboundsOnly) @@ -54,23 +54,28 @@ QMultiHash ImportConfigWindow::SelectConnection(bool outbou // false and disable the checkbox keepImportedInboundCheckBox->setEnabled(!outboundsOnly); routeEditBtn->setEnabled(!outboundsOnly); + groupCombo->setEnabled(false); this->exec(); QMultiHash conn; - for (const auto &connEntry : connections.values()) + for (const auto &connEntry : connectionsToNewGroup.values()) + { + conn += connEntry; + } + for (const auto &connEntry : connectionsToExistingGroup.values()) { conn += connEntry; } return result() == Accepted ? conn : QMultiHash{}; } -int ImportConfigWindow::ImportConnection() +int ImportConfigWindow::PerformImportConnection() { this->exec(); int count = 0; - for (const auto &groupName : connections.keys()) + for (const auto &groupObject : connectionsToNewGroup) { - GroupId groupId = groupName.isEmpty() ? DefaultGroupId : ConnectionManager->CreateGroup(groupName, false); - const auto groupObject = connections[groupName]; + const auto groupName = connectionsToNewGroup.key(groupObject); + GroupId groupId = ConnectionManager->CreateGroup(groupName, false); for (const auto &connConf : groupObject) { auto connName = groupObject.key(connConf); @@ -80,7 +85,22 @@ int ImportConfigWindow::ImportConnection() { connName = protocol + "/" + host + ":" + QSTRN(port) + "-" + GenerateRandomString(5); } - ConnectionManager->CreateConnection(connName, groupId, connConf, true); + ConnectionManager->CreateConnection(connConf, connName, groupId, true); + } + } + + for (const auto &groupObject : connectionsToExistingGroup) + { + const auto groupId = connectionsToExistingGroup.key(groupObject); + for (const auto &connConf : groupObject) + { + auto connName = groupObject.key(connConf); + auto [protocol, host, port] = GetConnectionInfo(connConf); + if (connName.isEmpty()) + { + connName = protocol + "/" + host + ":" + QSTRN(port) + "-" + GenerateRandomString(5); + } + ConnectionManager->CreateConnection(connConf, connName, groupId, true); } } @@ -116,7 +136,6 @@ void ImportConfigWindow::on_qrFromScreenBtn_clicked() if (_r == QDialog::Accepted) { auto str = DecodeQRCode(pix); - if (str.trimmed().isEmpty()) { LOG(MODULE_UI, "Cannot decode QR Code from an image, size: h=" + QSTRN(pix.width()) + ", v=" + QSTRN(pix.height())) @@ -124,7 +143,7 @@ void ImportConfigWindow::on_qrFromScreenBtn_clicked() } else { - vmessConnectionStringTxt->appendPlainText(str.trimmed() + NEWLINE); + qrCodeLinkTxt->setText(str.trimmed()); } } } @@ -135,7 +154,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked() switch (tabWidget->currentIndex()) { - case 0: + case LINK_PAGE: { QStringList linkList = SplitLines(vmessConnectionStringTxt->toPlainText()); // @@ -165,11 +184,18 @@ void ImportConfigWindow::on_beginImportBtn_clicked() linkErrors[link] = QSTRN(linkErrors.count() + 1) + ": " + errMessage; continue; } + else if (newGroupName.isEmpty()) + { + for (const auto &conf : config) + { + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(conf.first, conf.second); + } + } else { for (const auto &conf : config) { - AddToGroup(newGroupName, config.key(conf), conf); + connectionsToNewGroup[newGroupName].insert(conf.first, conf.second); } } } @@ -189,7 +215,26 @@ void ImportConfigWindow::on_beginImportBtn_clicked() break; } - case 2: + case QRCODE_PAGE: + { + QString errorMsg; + const auto root = ConvertConfigFromString(qrCodeLinkTxt->text(), &aliasPrefix, &errorMsg); + if (!errorMsg.isEmpty()) + { + QvMessageBoxWarn(this, tr("Failed to import connection"), errorMsg); + break; + } + for (const auto &conf : root) + { + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(conf.first, conf.second); + } + break; + } + case MANUAL_PAGE: + { + break; + } + case ADVANCED_PAGE: { // From File... bool ImportAsComplex = keepImportedInboundCheckBox->isChecked(); @@ -203,7 +248,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked() aliasPrefix += "_" + QFileInfo(path).fileName(); CONFIGROOT config = ConvertConfigFromFile(path, ImportAsComplex); - AddToGroup("", aliasPrefix, config); + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(aliasPrefix, config); break; } } @@ -275,7 +320,7 @@ void ImportConfigWindow::on_connectionEditBtn_clicked() CONFIGROOT root; root.insert("outbounds", outboundsList); // - AddToGroup("", alias, root); + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, root); accept(); } } @@ -288,15 +333,16 @@ void ImportConfigWindow::on_cancelImportBtn_clicked() void ImportConfigWindow::on_subscriptionButton_clicked() { hide(); - SubscriptionEditor w(this); + GroupManager w(this); w.exec(); auto importToComplex = !keepImportedInboundCheckBox->isEnabled(); - connections.clear(); + connectionsToNewGroup.clear(); + connectionsToExistingGroup.clear(); if (importToComplex) { auto [alias, conf] = w.GetSelectedConfig(); - AddToGroup("", alias, conf); + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, conf); } accept(); @@ -311,7 +357,7 @@ void ImportConfigWindow::on_routeEditBtn_clicked() if (isChanged) { - AddToGroup("", alias, result); + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, result); accept(); } } @@ -331,7 +377,7 @@ void ImportConfigWindow::on_jsonEditBtn_clicked() if (isChanged) { - AddToGroup("", alias, CONFIGROOT(result)); + connectionsToExistingGroup[GroupId{ groupCombo->currentData().toString() }].insert(alias, CONFIGROOT(result)); accept(); } } diff --git a/src/ui/w_ImportConfig.hpp b/src/ui/windows/w_ImportConfig.hpp similarity index 51% rename from src/ui/w_ImportConfig.hpp rename to src/ui/windows/w_ImportConfig.hpp index 44273438..cb5aa85e 100644 --- a/src/ui/w_ImportConfig.hpp +++ b/src/ui/windows/w_ImportConfig.hpp @@ -1,15 +1,12 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" +#include "ui/common/QvDialog.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_w_ImportConfig.h" -#include -#include -#include - class ImportConfigWindow - : public QDialog + : public QvDialog , private Ui::ImportConfigWindow { Q_OBJECT @@ -17,47 +14,43 @@ class ImportConfigWindow public: explicit ImportConfigWindow(QWidget *parent = nullptr); ~ImportConfigWindow(); - int ImportConnection(); + int PerformImportConnection(); QMultiHash SelectConnection(bool outboundsOnly); + void processCommands(QString command, QStringList commands, QMap args) override + { + const static QMap indexMap{ { "link", 0 }, // + { "qr", 1 }, // + { "manual", 2 }, // + { "advanced", 3 } }; + nameTxt->setText(args["name"]); + if (commands.isEmpty()) + return; + if (command == "open") + { + const auto c = commands.takeFirst(); + tabWidget->setCurrentIndex(indexMap[c]); + } + } private: QvMessageBusSlotDecl; private slots: - void on_selectFileBtn_clicked(); - void on_qrFromScreenBtn_clicked(); void on_beginImportBtn_clicked(); void on_selectImageBtn_clicked(); void on_errorsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_connectionEditBtn_clicked(); - void on_cancelImportBtn_clicked(); - void on_subscriptionButton_clicked(); - void on_routeEditBtn_clicked(); - void on_hideQv2rayCB_stateChanged(int arg1); - void on_jsonEditBtn_clicked(); private: - void UpdateColorScheme(); - QMap> connections; + void updateColorScheme() override; QMap linkErrors; - void AddToGroup(const QString &groupName, const QString &alias, const CONFIGROOT &root) - { - if (connections.contains(groupName)) - { - connections[groupName].insert(alias, root); - } - else - { - QMultiHash temp; - temp.insert(alias, root); - connections.insert(groupName, temp); - } - } + // + QHash> connectionsToExistingGroup; + QHash> connectionsToNewGroup; }; diff --git a/src/ui/w_ImportConfig.ui b/src/ui/windows/w_ImportConfig.ui similarity index 53% rename from src/ui/w_ImportConfig.ui rename to src/ui/windows/w_ImportConfig.ui index 103d1396..ebd2ea4c 100644 --- a/src/ui/w_ImportConfig.ui +++ b/src/ui/windows/w_ImportConfig.ui @@ -6,8 +6,8 @@ 0 0 - 522 - 460 + 549 + 482 @@ -42,15 +42,18 @@ + + + + Import To Group + + + + + + - - - - Import Source - - - @@ -58,197 +61,183 @@ - VMess / QRCode + Link - - - + + + - Connection Share Link + Share Link - - - - - - Qt::ScrollBarAlwaysOn - - - QPlainTextEdit::NoWrap - - - Paste share link here, one line for each. - - - - - - - - - - Error List - - - - - - - Share Link - - - - + + + + Error List + + - - - - - - QRCode File - - - - - - - - - - - - true - - - - - - - Select - - - - - - - - - Screenshot - - - - - - - - - secs - - - 5.000000000000000 - - - 0.500000000000000 - - - 0.000000000000000 - - - - - - - Delay - - - - - - - Hide Qv2ray - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - Go - - - - - - + + + + Qt::ScrollBarAlwaysOn + + + QPlainTextEdit::NoWrap + + + Paste share link here, one line for each. + + + + + - + - Subscriptions / Manually Input + QR Code - - - + + + - Open Subscription Manager + Screenshot - - - - - 0 - 0 - + + + + + + Hide Qv2ray + + + + + + + secs + + + 5.000000000000000 + + + 0.500000000000000 + + + 0.000000000000000 + + + + + + + Delay + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + Go + + + + + + + + + QRCode File - + + + + + + + + + + + true + + + + + + + Select + + + + + + + + + Detected Link + + + + + + + true + + + + + + + Qt::Vertical + + - 0 - 10 + 20 + 40 - - Qt::Horizontal - - + - - - - Manually Input Connections - - - - + + + + + Input Manually + + + - Connection Editor + Simple Editor - + Open Connection Editor @@ -258,51 +247,27 @@ - + - Route Editor + Complex Editor - + - Open Route Editor + Open Route / Complex Connection Editor false - - - - Json Editor - - - - - - - Open JSON Editor - - - false - - - - - - - Subscription Manager - - - - + - Subscription Link + You can manually input connection here. @@ -310,17 +275,24 @@ - Existing File + Advanced - + + + + Manually Input Connections + + + + Path - + @@ -338,13 +310,30 @@ - + Import as Complex Config (Manually edit route rules and inbounds) + + + + Json Editor + + + + + + + Open JSON Editor + + + false + + + @@ -384,21 +373,10 @@ nameTxt - tabWidget vmessConnectionStringTxt errorsList - imageFileEdit - selectImageBtn - doubleSpinBox - hideQv2rayCB - qrFromScreenBtn - subscriptionButton - connectionEditBtn - routeEditBtn - jsonEditBtn fileLineTxt selectFileBtn - keepImportedInboundCheckBox beginImportBtn cancelImportBtn diff --git a/src/ui/w_MainWindow.cpp b/src/ui/windows/w_MainWindow.cpp similarity index 66% rename from src/ui/w_MainWindow.cpp rename to src/ui/windows/w_MainWindow.cpp index 16a6a2d9..ce9257d2 100644 --- a/src/ui/w_MainWindow.cpp +++ b/src/ui/windows/w_MainWindow.cpp @@ -1,30 +1,18 @@ #include "w_MainWindow.hpp" -#include "components/plugins/QvPluginHost.hpp" -#include "components/plugins/toolbar/QvToolbar.hpp" -#include "components/proxy/QvProxyConfigurator.hpp" #include "components/update/UpdateChecker.hpp" +#include "core/handler/ConfigHandler.hpp" #include "core/settings/SettingsBackend.hpp" +#include "src/Qv2rayApplication.hpp" +#include "ui/common/UIBase.hpp" #include "ui/editors/w_JsonEditor.hpp" #include "ui/editors/w_OutboundEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp" -#include "ui/w_ImportConfig.hpp" -#include "ui/w_PluginManager.hpp" -#include "ui/w_PreferencesWindow.hpp" -#include "ui/w_SubscriptionManager.hpp" #include "ui/widgets/ConnectionInfoWidget.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "ui/windows/w_GroupManager.hpp" +#include "ui/windows/w_ImportConfig.hpp" +#include "ui/windows/w_PluginManager.hpp" +#include "ui/windows/w_PreferencesWindow.hpp" #ifdef Q_OS_MAC #include @@ -39,19 +27,20 @@ #define GetItemWidget(item) (qobject_cast(connectionListWidget->itemWidget(item, 0))) #define NumericString(i) (QString("%1").arg(i, 30, 10, QLatin1Char('0'))) -MainWindow *MainWindow::MainWindowInstance = nullptr; - QvMessageBusSlotImpl(MainWindow) { switch (msg) { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl MBUpdateColorSchemeDefaultImpl + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + MBUpdateColorSchemeDefaultImpl; } } -void MainWindow::UpdateColorScheme() +void MainWindow::updateColorScheme() { - hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png")); + qvAppTrayIcon->setIcon(KernelInstance->CurrentConnection().isEmpty() ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png")); // importConfigButton->setIcon(QICON_R("import.png")); updownImageBox->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)"); @@ -74,31 +63,31 @@ void MainWindow::UpdateColorScheme() sortBtn->setIcon(QICON_R("sort.png")); } -void MainWindow::MWAddConnectionItem_p(const ConnectionId &connection, const GroupId &groupId) +void MainWindow::MWAddConnectionItem_p(const ConnectionGroupPair &id) { - if (!groupNodes.contains(groupId)) + if (!groupNodes.contains(id.groupId)) { - MWAddGroupItem_p(groupId); + MWAddGroupItem_p(id.groupId); } - auto groupItem = groupNodes.value(groupId); - auto connectionItem = make_shared(QStringList{ - "", // - GetDisplayName(connection), // - NumericString(GetConnectionLatency(connection)), // - "IMPORTTIME_NOT_SUPPORTED", // - "LAST_CONNECTED_NOT_SUPPORTED", // - NumericString(GetConnectionTotalData(connection)) // + auto groupItem = groupNodes.value(id.groupId); + auto connectionItem = std::make_shared(QStringList{ + "", // + GetDisplayName(id.connectionId), // + NumericString(GetConnectionLatency(id.connectionId)), // + "IMPORTTIME_NOT_SUPPORTED", // + "LAST_CONNECTED_NOT_SUPPORTED", // + NumericString(GetConnectionTotalData(id.connectionId)) // }); - connectionNodes.insert(connection, connectionItem); + connectionNodes.insert(id, connectionItem); groupItem->addChild(connectionItem.get()); - auto widget = new ConnectionItemWidget(connection, connectionListWidget); + auto widget = new ConnectionItemWidget(id, connectionListWidget); connect(widget, &ConnectionItemWidget::RequestWidgetFocus, this, &MainWindow::OnConnectionWidgetFocusRequested); connectionListWidget->setItemWidget(connectionItem.get(), 0, widget); } void MainWindow::MWAddGroupItem_p(const GroupId &groupId) { - auto groupItem = make_shared(QStringList{ "", GetDisplayName(groupId) }); + auto groupItem = std::make_shared(QStringList{ "", GetDisplayName(groupId) }); groupNodes.insert(groupId, groupItem); connectionListWidget->addTopLevelItem(groupItem.get()); connectionListWidget->setItemWidget(groupItem.get(), 0, new ConnectionItemWidget(groupId, connectionListWidget)); @@ -114,38 +103,36 @@ void MainWindow::SortConnectionList(MW_ITEM_COL byCol, bool asending) on_locateBtn_clicked(); } -void MainWindow::ReloadRecentConnectionList(const QList &items) +void MainWindow::ReloadRecentConnectionList() { - QList newActions; - for (const auto &item : items) + QList newRecentConnections; + const auto iterateRange = std::min(GlobalConfig.uiConfig.maxJumpListCount, GlobalConfig.uiConfig.recentConnections.count()); + for (auto i = 0; i < iterateRange; i++) { - auto action = new QAction(tray_RecentConnectionsMenu); - action->setText(GetDisplayName(ConnectionId{ item })); - action->setData(item); - connect(ConnectionManager, &QvConfigHandler::OnConnectionRenamed, - [action](const ConnectionId &_t1, const QString &, const QString &_t3) { - if (_t1.toString() == action->data().toString()) - { - action->setText(_t3); - } - }); - connect(action, &QAction::triggered, [action]() { // - emit ConnectionManager->StartConnection(ConnectionId{ action->data().toString() }); - }); - newActions << action; + const auto &item = GlobalConfig.uiConfig.recentConnections.at(i); + if (newRecentConnections.contains(item) || item.isEmpty()) + continue; + newRecentConnections << item; } - for (const auto action : tray_RecentConnectionsMenu->actions()) + GlobalConfig.uiConfig.recentConnections = newRecentConnections; +} + +void MainWindow::OnRecentConnectionsMenuReadyToShow() +{ + tray_RecentConnectionsMenu->clear(); + tray_RecentConnectionsMenu->addAction(tray_ClearRecentConnectionsAction); + tray_RecentConnectionsMenu->addSeparator(); + for (const auto &conn : GlobalConfig.uiConfig.recentConnections) { - tray_RecentConnectionsMenu->removeAction(action); - action->deleteLater(); + if (ConnectionManager->IsValidId(conn)) + tray_RecentConnectionsMenu->addAction(GetDisplayName(conn.connectionId) + " (" + GetDisplayName(conn.groupId) + ")", + [=]() { emit ConnectionManager->StartConnection(conn); }); } - tray_RecentConnectionsMenu->addActions(newActions); } MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUi(this); - MainWindow::MainWindowInstance = this; QvMessageBusConnect(MainWindow); // infoWidget = new ConnectionInfoWidget(this); @@ -158,10 +145,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) speedChart->addWidget(speedChartWidget); // this->setWindowIcon(QIcon(":/assets/icons/qv2ray.png")); - UpdateColorScheme(); + updateColorScheme(); // // - connect(ConnectionManager, &QvConfigHandler::OnKernelCrashed, [&](const ConnectionId &, const QString &reason) { + connect(ConnectionManager, &QvConfigHandler::OnKernelCrashed, [this](const ConnectionGroupPair &, const QString &reason) { this->show(); QvMessageBoxWarn(this, tr("Kernel terminated."), tr("The kernel terminated unexpectedly:") + NEWLINE + reason + NEWLINE + NEWLINE + @@ -173,20 +160,32 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) connect(ConnectionManager, &QvConfigHandler::OnStatsAvailable, this, &MainWindow::OnStatsAvailable); connect(ConnectionManager, &QvConfigHandler::OnKernelLogAvailable, this, &MainWindow::OnVCoreLogAvailable); // - connect(ConnectionManager, &QvConfigHandler::OnConnectionDeleted, this, &MainWindow::OnConnectionDeleted); + connect(ConnectionManager, &QvConfigHandler::OnConnectionRemovedFromGroup, this, &MainWindow::OnConnectionDeleted); connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &MainWindow::OnConnectionCreated); - connect(ConnectionManager, &QvConfigHandler::OnConnectionGroupChanged, this, &MainWindow::OnConnectionGroupChanged); + connect(ConnectionManager, &QvConfigHandler::OnConnectionLinkedWithGroup, this, &MainWindow::OnConnectionLinkedWithGroup); // connect(ConnectionManager, &QvConfigHandler::OnGroupCreated, this, &MainWindow::OnGroupCreated); connect(ConnectionManager, &QvConfigHandler::OnGroupDeleted, this, &MainWindow::OnGroupDeleted); // - connect(ConnectionManager, &QvConfigHandler::OnConnectionRenamed, [&](const ConnectionId &id, const QString &, const QString &newName) { - if (connectionNodes.contains(id)) - connectionNodes.value(id)->setText(MW_ITEM_COL_NAME, newName); // + connect(ConnectionManager, &QvConfigHandler::OnSubscriptionAsyncUpdateFinished, [](const GroupId &gid) { + qvApp->showMessage(tr("Subscription \"%1\" has been updated").arg(GetDisplayName(gid))); // }); - connect(ConnectionManager, &QvConfigHandler::OnLatencyTestFinished, [&](const ConnectionId &id, const uint avg) { - if (connectionNodes.contains(id)) - connectionNodes.value(id)->setText(MW_ITEM_COL_PING, NumericString(avg)); // + // + connect(ConnectionManager, &QvConfigHandler::OnConnectionRenamed, [this](const ConnectionId &id, const QString &, const QString &newName) { + for (const auto &gid : ConnectionManager->GetGroupId(id)) + { + ConnectionGroupPair pair{ id, gid }; + if (connectionNodes.contains(pair)) + connectionNodes.value(pair)->setText(MW_ITEM_COL_NAME, newName); + } + }); + connect(ConnectionManager, &QvConfigHandler::OnLatencyTestFinished, [this](const ConnectionId &id, const int avg) { + for (const auto &gid : ConnectionManager->GetGroupId(id)) + { + ConnectionGroupPair pair{ id, gid }; + if (connectionNodes.contains(pair)) + connectionNodes.value(pair)->setText(MW_ITEM_COL_PING, NumericString(avg)); // + } }); // connect(infoWidget, &ConnectionInfoWidget::OnEditRequested, this, &MainWindow::OnEditRequested); @@ -194,10 +193,10 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) // // // Setup System tray icons and menus - hTray.setToolTip(TRAY_TOOLTIP_PREFIX); + qvAppTrayIcon->setToolTip(TRAY_TOOLTIP_PREFIX); + qvAppTrayIcon->show(); // // Basic tray actions - hTray.show(); tray_action_Start->setEnabled(true); tray_action_Stop->setEnabled(false); tray_action_Restart->setEnabled(false); @@ -210,32 +209,42 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) tray_RootMenu->addSeparator(); tray_RootMenu->addAction(tray_action_ShowPreferencesWindow); tray_RootMenu->addMenu(tray_SystemProxyMenu); - // This feature is not ready - // tray_RootMenu->addSeparator(); - // tray_RootMenu->addMenu(tray_RecentConnectionsMenu); + // + tray_RootMenu->addSeparator(); + tray_RootMenu->addMenu(tray_RecentConnectionsMenu); + connect(tray_RecentConnectionsMenu, &QMenu::aboutToShow, this, &MainWindow::OnRecentConnectionsMenuReadyToShow); + // tray_RootMenu->addSeparator(); tray_RootMenu->addAction(tray_action_Start); tray_RootMenu->addAction(tray_action_Stop); tray_RootMenu->addAction(tray_action_Restart); tray_RootMenu->addSeparator(); tray_RootMenu->addAction(tray_action_Quit); - hTray.setContextMenu(tray_RootMenu); + qvAppTrayIcon->setContextMenu(tray_RootMenu); // connect(tray_action_ShowHide, &QAction::triggered, this, &MainWindow::ToggleVisibility); connect(tray_action_ShowPreferencesWindow, &QAction::triggered, this, &MainWindow::on_preferencesBtn_clicked); - connect(tray_action_Start, &QAction::triggered, [&] { ConnectionManager->StartConnection(lastConnectedId); }); + connect(tray_action_Start, &QAction::triggered, [this] { ConnectionManager->StartConnection(lastConnectedIdentifier); }); connect(tray_action_Stop, &QAction::triggered, ConnectionManager, &QvConfigHandler::StopConnection); connect(tray_action_Restart, &QAction::triggered, ConnectionManager, &QvConfigHandler::RestartConnection); connect(tray_action_Quit, &QAction::triggered, this, &MainWindow::on_actionExit_triggered); connect(tray_action_SetSystemProxy, &QAction::triggered, this, &MainWindow::MWSetSystemProxy); connect(tray_action_ClearSystemProxy, &QAction::triggered, this, &MainWindow::MWClearSystemProxy); - connect(&hTray, &QSystemTrayIcon::activated, this, &MainWindow::on_activatedTray); + connect(tray_ClearRecentConnectionsAction, &QAction::triggered, [this]() { + GlobalConfig.uiConfig.recentConnections.clear(); + ReloadRecentConnectionList(); + if (!GlobalConfig.uiConfig.quietMode) + { + qvApp->showMessage(tr("Recent connections' jump list cleared.")); + } + }); + connect(qvAppTrayIcon, &QSystemTrayIcon::activated, this, &MainWindow::on_activatedTray); // // Actions for right click the log text browser // logRCM_Menu->addAction(action_RCM_tovCoreLog); logRCM_Menu->addAction(action_RCM_toQvLog); - connect(masterLogBrowser, &QTextBrowser::customContextMenuRequested, [&](const QPoint &) { logRCM_Menu->popup(QCursor::pos()); }); + connect(masterLogBrowser, &QTextBrowser::customContextMenuRequested, [this](const QPoint &) { logRCM_Menu->popup(QCursor::pos()); }); connect(action_RCM_tovCoreLog, &QAction::triggered, this, &MainWindow::on_action_RCM_tovCoreLog_triggered); connect(action_RCM_toQvLog, &QAction::triggered, this, &MainWindow::on_action_RCM_toQvLog_triggered); // @@ -260,6 +269,7 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) connectionListRCM_Menu->addAction(action_RCM_Rename); connectionListRCM_Menu->addAction(action_RCM_Duplicate); connectionListRCM_Menu->addAction(action_RCM_ClearUsage); + connectionListRCM_Menu->addAction(action_RCM_UpdateSubscription); connectionListRCM_Menu->addSeparator(); connectionListRCM_Menu->addAction(action_RCM_Delete); connect(action_RCM_Start, &QAction::triggered, this, &MainWindow::on_action_StartThis_triggered); @@ -271,16 +281,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) connect(action_RCM_Rename, &QAction::triggered, this, &MainWindow::on_action_RCM_RenameThis_triggered); connect(action_RCM_Duplicate, &QAction::triggered, this, &MainWindow::on_action_RCM_DuplicateThese_triggered); connect(action_RCM_ClearUsage, &QAction::triggered, this, &MainWindow::on_action_RCM_ClearUsage_triggered); + connect(action_RCM_UpdateSubscription, &QAction::triggered, this, &MainWindow::on_action_RCM_UpdateSubscription_triggered); connect(action_RCM_Delete, &QAction::triggered, this, &MainWindow::on_action_RCM_DeleteThese_triggered); // // Sort Menu // - connect(sortAction_SortByName_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_NAME, true); }); - connect(sortAction_SortByName_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_NAME, false); }); - connect(sortAction_SortByData_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DATA, true); }); - connect(sortAction_SortByData_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_DATA, false); }); - connect(sortAction_SortByPing_Asc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_PING, true); }); - connect(sortAction_SortByPing_Dsc, &QAction::triggered, [&] { SortConnectionList(MW_ITEM_COL_PING, false); }); + connect(sortAction_SortByName_Asc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_NAME, true); }); + connect(sortAction_SortByName_Dsc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_NAME, false); }); + connect(sortAction_SortByData_Asc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_DATA, true); }); + connect(sortAction_SortByData_Dsc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_DATA, false); }); + connect(sortAction_SortByPing_Asc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_PING, true); }); + connect(sortAction_SortByPing_Dsc, &QAction::triggered, [this] { SortConnectionList(MW_ITEM_COL_PING, false); }); // sortMenu->addAction(sortAction_SortByName_Asc); sortMenu->addAction(sortAction_SortByName_Dsc); @@ -293,57 +304,64 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) // sortBtn->setMenu(sortMenu); // - LOG(MODULE_UI, "Loading data...") // - auto groups = ConnectionManager->AllGroups(); - - for (auto group : groups) + LOG(MODULE_UI, "Loading data...") + for (const auto &group : ConnectionManager->AllGroups()) { MWAddGroupItem_p(group); - auto connections = ConnectionManager->Connections(group); - - for (auto connection : connections) + for (const auto &connection : ConnectionManager->Connections(group)) { - MWAddConnectionItem_p(connection, group); + MWAddConnectionItem_p({ connection, group }); } } // // Find and start if there is an auto-connection - auto needShowWindow = true; - - if (!GlobalConfig.autoStartId.isEmpty()) - { - auto id = ConnectionId(GlobalConfig.autoStartId); - // Empty means we are connected, so has_value is false. - // So no need to show is false. - needShowWindow = ConnectionManager->StartConnection(id).has_value(); - } - if (needShowWindow && connectionListWidget->topLevelItemCount() > 0) + const auto connectionStarted = StartAutoConnectionEntry(); + if (!connectionStarted && connectionListWidget->topLevelItemCount() > 0) { + ReloadRecentConnectionList(); // Select the first connection. - auto item = (connectionListWidget->topLevelItem(0)->childCount() > 0) ? connectionListWidget->topLevelItem(0)->child(0) : - connectionListWidget->topLevelItem(0); + const auto &topLevelItem = connectionListWidget->topLevelItem(0); + const auto &item = (topLevelItem->childCount() > 0) ? topLevelItem->child(0) : topLevelItem; connectionListWidget->setCurrentItem(item); on_connectionListWidget_itemClicked(item, 0); } - ReloadRecentConnectionList(GlobalConfig.uiConfig.recentConnections); // - if (needShowWindow) + // + tray_action_ShowHide->setText(!connectionStarted ? tr("Hide") : tr("Show")); + if (!connectionStarted) this->show(); // - tray_action_ShowHide->setText(needShowWindow ? tr("Hide") : tr("Show")); - // - if (StartupOption.enableToolbarPlguin) - { - LOG(MODULE_UI, "Plugin daemon is enabled.") - StartProcessingPlugins(); - } - CheckSubscriptionsUpdate(); // - splitter->setSizes(QList() << 100 << 300); + splitter->setSizes({ 100, 300 }); qvLogTimerId = startTimer(1000); // - UpdateChecker.CheckUpdate(); + auto checker = new QvUpdateChecker(this); + checker->CheckUpdate(); +} + +void MainWindow::ProcessCommand(QString command, QStringList commands, QMap args) +{ + if (commands.isEmpty()) + return; + if (command == "open") + { + const auto subcommand = commands.takeFirst(); + QvDialog *w; + if (subcommand == "preference") + w = new PreferencesWindow(); + else if (subcommand == "plugin") + w = new PluginManageWindow(); + else if (subcommand == "group") + w = new GroupManager(); + else if (subcommand == "import") + w = new ImportConfigWindow(); + else + return; + w->processCommands(command, commands, args); + w->open(); + QvAutoDelete(w); + } } void MainWindow::timerEvent(QTimerEvent *event) @@ -380,6 +398,10 @@ void MainWindow::keyPressEvent(QKeyEvent *e) { widget->BeginRename(); } + else if (e->key() == Qt::Key_Delete) + { + on_action_RCM_DeleteThese_triggered(); + } } if (e->key() == Qt::Key_Escape) @@ -398,10 +420,8 @@ void MainWindow::keyPressEvent(QKeyEvent *e) } else if (e->modifiers() & Qt::ControlModifier && e->key() == Qt::Key_Q) { - if (QvMessageBoxAsk(this, tr("Quit Qv2ray"), tr("Are you sure to exit Qv2ray?"), QMessageBox::No) == QMessageBox::Yes) - { - ExitQv2ray(); - } + if (QvMessageBoxAsk(this, tr("Quit Qv2ray"), tr("Are you sure to exit Qv2ray?")) == QMessageBox::Yes) + on_actionExit_triggered(); } } @@ -429,7 +449,6 @@ void MainWindow::on_action_StartThis_triggered() MainWindow::~MainWindow() { - hTray.hide(); } void MainWindow::closeEvent(QCloseEvent *event) @@ -496,17 +515,13 @@ void MainWindow::ToggleVisibility() void MainWindow::on_actionExit_triggered() { ConnectionManager->StopConnection(); - if (StartupOption.enableToolbarPlguin) - { - StopProcessingPlugins(); - } - - ExitQv2ray(); + qvApp->QuitApplication(); } void MainWindow::on_preferencesBtn_clicked() { - PreferencesWindow(this).exec(); + PreferencesWindow{ this }.exec(); + // ProcessCommand("open", { "preference", "general" }, {}); } void MainWindow::on_clearlogButton_clicked() { @@ -528,26 +543,34 @@ void MainWindow::on_connectionListWidget_customContextMenuRequested(const QPoint action_RCM_EditComplex->setEnabled(isConnection); action_RCM_Rename->setEnabled(isConnection); action_RCM_Duplicate->setEnabled(isConnection); + action_RCM_UpdateSubscription->setEnabled(!isConnection); connectionListRCM_Menu->popup(_pos); } } void MainWindow::on_action_RCM_DeleteThese_triggered() { - QList connlist; + QList connlist; - for (auto item : connectionListWidget->selectedItems()) + for (const auto &item : connectionListWidget->selectedItems()) { auto widget = GetItemWidget(item); if (widget) { + const auto identifier = widget->Identifier(); if (widget->IsConnection()) { - connlist.append(get<1>(widget->Identifier())); + connlist.append(identifier); } else { - connlist.append(ConnectionManager->GetGroupMetaObject(get<0>(widget->Identifier())).connections); + for (const auto &conns : ConnectionManager->GetGroupMetaObject(identifier.groupId).connections) + { + ConnectionGroupPair i; + i.connectionId = conns; + i.groupId = identifier.groupId; + connlist.append(i); + } } } } @@ -560,26 +583,28 @@ void MainWindow::on_action_RCM_DeleteThese_triggered() return; } - if (QvMessageBoxAsk(this, tr("Removing Connection(s)"), tr("Are you sure to remove selected connection(s)?")) != QMessageBox::Yes) + const auto strRemoveConnTitle = tr("Removing Connection(s)", "", connlist.count()); + const auto strRemoveConnContent = tr("Are you sure to remove selected connection(s)?", "", connlist.count()); + if (QvMessageBoxAsk(this, strRemoveConnTitle, strRemoveConnContent) != QMessageBox::Yes) { return; } - for (auto conn : connlist) + for (const auto &conn : connlist) { if (ConnectionManager->IsConnected(conn)) ConnectionManager->StopConnection(); - if (GlobalConfig.autoStartId == conn.toString()) + if (GlobalConfig.autoStartId == conn) GlobalConfig.autoStartId.clear(); - ConnectionManager->DeleteConnection(conn); + ConnectionManager->RemoveConnectionFromGroup(conn.connectionId, conn.groupId); } } void MainWindow::on_importConfigButton_clicked() { ImportConfigWindow w(this); - w.ImportConnection(); + w.PerformImportConnection(); } void MainWindow::on_action_RCM_EditAsComplex_triggered() @@ -587,8 +612,8 @@ void MainWindow::on_action_RCM_EditAsComplex_triggered() CheckCurrentWidget; if (widget->IsConnection()) { - auto id = get<1>(widget->Identifier()); - CONFIGROOT root = ConnectionManager->GetConnectionRoot(id); + auto id = widget->Identifier(); + CONFIGROOT root = ConnectionManager->GetConnectionRoot(id.connectionId); bool isChanged = false; // LOG(MODULE_UI, "INFO: Opening route editor.") @@ -597,14 +622,14 @@ void MainWindow::on_action_RCM_EditAsComplex_triggered() isChanged = routeWindow.result() == QDialog::Accepted; if (isChanged) { - ConnectionManager->UpdateConnection(id, root); + ConnectionManager->UpdateConnection(id.connectionId, root); } } } void MainWindow::on_subsButton_clicked() { - SubscriptionEditor().exec(); + GroupManager().exec(); } void MainWindow::on_connectionListWidget_itemDoubleClicked(QTreeWidgetItem *item, int column) @@ -619,51 +644,55 @@ void MainWindow::on_connectionListWidget_itemDoubleClicked(QTreeWidgetItem *item } } -void MainWindow::OnDisconnected(const ConnectionId &id) +void MainWindow::OnDisconnected(const ConnectionGroupPair &id) { Q_UNUSED(id) - hTray.setIcon(Q_TRAYICON("tray.png")); + qvAppTrayIcon->setIcon(Q_TRAYICON("tray.png")); tray_action_Start->setEnabled(true); tray_action_Stop->setEnabled(false); tray_action_Restart->setEnabled(false); tray_SystemProxyMenu->setEnabled(false); - lastConnectedId = id; + lastConnectedIdentifier = id; locateBtn->setEnabled(false); if (!GlobalConfig.uiConfig.quietMode) { - this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon()); + qvApp->showMessage(tr("Disconnected from: ") + GetDisplayName(id.connectionId), this->windowIcon()); } - hTray.setToolTip(TRAY_TOOLTIP_PREFIX); + qvAppTrayIcon->setToolTip(TRAY_TOOLTIP_PREFIX); netspeedLabel->setText("0.00 B/s" NEWLINE "0.00 B/s"); dataamountLabel->setText("0.00 B" NEWLINE "0.00 B"); connetionStatusLabel->setText(tr("Not Connected")); - if (GlobalConfig.inboundConfig.setSystemProxy) + if (GlobalConfig.inboundConfig.systemProxySettings.setSystemProxy) { - ClearSystemProxy(); + MWClearSystemProxy(); } } -void MainWindow::OnConnected(const ConnectionId &id) +void MainWindow::OnConnected(const ConnectionGroupPair &id) { Q_UNUSED(id) - hTray.setIcon(Q_TRAYICON("tray-connected.png")); + qvAppTrayIcon->setIcon(Q_TRAYICON("tray-connected.png")); tray_action_Start->setEnabled(false); tray_action_Stop->setEnabled(true); tray_action_Restart->setEnabled(true); tray_SystemProxyMenu->setEnabled(true); - lastConnectedId = id; + lastConnectedIdentifier = id; locateBtn->setEnabled(true); on_clearlogButton_clicked(); - auto name = GetDisplayName(id); + auto name = GetDisplayName(id.connectionId); if (!GlobalConfig.uiConfig.quietMode) { - this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon()); + qvApp->showMessage(tr("Connected: ") + name, this->windowIcon()); } - hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name); + qvAppTrayIcon->setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name); connetionStatusLabel->setText(tr("Connected: ") + name); // - ConnectionManager->StartLatencyTest(id); - if (GlobalConfig.inboundConfig.setSystemProxy) + GlobalConfig.uiConfig.recentConnections.removeAll(id); + GlobalConfig.uiConfig.recentConnections.push_front(id); + ReloadRecentConnectionList(); + // + ConnectionManager->StartLatencyTest(id.connectionId); + if (GlobalConfig.inboundConfig.systemProxySettings.setSystemProxy) { MWSetSystemProxy(); } @@ -733,7 +762,7 @@ void MainWindow::on_connectionListWidget_itemClicked(QTreeWidgetItem *item, int infoWidget->ShowDetails(widget->Identifier()); } -void MainWindow::OnStatsAvailable(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD) +void MainWindow::OnStatsAvailable(const ConnectionGroupPair &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD) { if (!ConnectionManager->IsConnected(id)) return; @@ -749,17 +778,17 @@ void MainWindow::OnStatsAvailable(const ConnectionId &id, const quint64 upS, con netspeedLabel->setText(totalSpeedUp + NEWLINE + totalSpeedDown); dataamountLabel->setText(totalDataUp + NEWLINE + totalDataDown); // - hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + GetDisplayName(id) + // - NEWLINE "Up: " + totalSpeedUp + " Down: " + totalSpeedDown); + qvAppTrayIcon->setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + GetDisplayName(id.connectionId) + // + NEWLINE "Up: " + totalSpeedUp + " Down: " + totalSpeedDown); // // Set data accordingly if (connectionNodes.contains(id)) { - connectionNodes.value(id)->setText(MW_ITEM_COL_DATA, NumericString(GetConnectionTotalData(id))); + connectionNodes.value(id)->setText(MW_ITEM_COL_DATA, NumericString(GetConnectionTotalData(id.connectionId))); } } -void MainWindow::OnVCoreLogAvailable(const ConnectionId &id, const QString &log) +void MainWindow::OnVCoreLogAvailable(const ConnectionGroupPair &id, const QString &log) { Q_UNUSED(id); FastAppendTextDocument(log.trimmed(), vCoreLogDocument); @@ -788,7 +817,7 @@ void MainWindow::OnEditRequested(const ConnectionId &id) { auto outBoundRoot = ConnectionManager->GetConnectionRoot(id); CONFIGROOT root; - bool isChanged = false; + bool isChanged; if (IsComplexConfig(outBoundRoot)) { @@ -825,22 +854,19 @@ void MainWindow::OnEditJsonRequested(const ConnectionId &id) } } -void MainWindow::OnConnectionCreated(const ConnectionId &id, const QString &displayName) +void MainWindow::OnConnectionCreated(const ConnectionGroupPair &id, const QString &displayName) { Q_UNUSED(displayName) - MWAddConnectionItem_p(id, GetConnectionGroupId(id)); + MWAddConnectionItem_p(id); } -void MainWindow::OnConnectionDeleted(const ConnectionId &id, const GroupId &groupId) +void MainWindow::OnConnectionDeleted(const ConnectionGroupPair &id) { auto child = connectionNodes.take(id); - groupNodes.value(groupId)->removeChild(child.get()); + groupNodes.value(id.groupId)->removeChild(child.get()); } -void MainWindow::OnConnectionGroupChanged(const ConnectionId &id, const GroupId &originalGroup, const GroupId &newGroup) +void MainWindow::OnConnectionLinkedWithGroup(const ConnectionGroupPair &pairId) { - delete GetItemWidget(connectionNodes.value(id).get()); - groupNodes.value(originalGroup)->removeChild(connectionNodes.value(id).get()); - connectionNodes.remove(id); - MWAddConnectionItem_p(id, newGroup); + MWAddConnectionItem_p(pairId); } void MainWindow::OnGroupCreated(const GroupId &id, const QString &displayName) { @@ -849,9 +875,9 @@ void MainWindow::OnGroupCreated(const GroupId &id, const QString &displayName) } void MainWindow::OnGroupDeleted(const GroupId &id, const QList &connections) { - for (auto conn : connections) + for (const auto &conn : connections) { - groupNodes.value(id)->removeChild(connectionNodes.value(conn).get()); + groupNodes.value(id)->removeChild(connectionNodes.value({ conn, id }).get()); } groupNodes.remove(id); } @@ -859,7 +885,7 @@ void MainWindow::OnGroupDeleted(const GroupId &id, const QList &co void MainWindow::on_locateBtn_clicked() { auto id = KernelInstance->CurrentConnection(); - if (id != NullConnectionId) + if (!id.isEmpty()) { connectionListWidget->setCurrentItem(connectionNodes.value(id).get()); connectionListWidget->scrollToItem(connectionNodes.value(id).get()); @@ -875,43 +901,43 @@ void MainWindow::on_action_RCM_RenameThis_triggered() void MainWindow::on_action_RCM_DuplicateThese_triggered() { - QList connlist; + QList connlist; - for (auto item : connectionListWidget->selectedItems()) + for (const auto &item : connectionListWidget->selectedItems()) { auto widget = GetItemWidget(item); if (widget->IsConnection()) { - connlist.append(get<1>(widget->Identifier())); + connlist.append(widget->Identifier()); } } LOG(MODULE_UI, "Selected " + QSTRN(connlist.count()) + " items") - if (connlist.count() > 1 && QvMessageBoxAsk(this, tr("Duplicating Connection(s)"), // - tr("Are you sure to duplicate these connection(s)?")) != QMessageBox::Yes) + const auto strDupConnTitle = tr("Duplicating Connection(s)", "", connlist.count()); + const auto strDupConnContent = tr("Are you sure to duplicate these connection(s)?", "", connlist.count()); + if (connlist.count() > 1 && QvMessageBoxAsk(this, strDupConnTitle, strDupConnContent) != QMessageBox::Yes) { return; } - for (auto conn : connlist) + for (const auto &conn : connlist) { - ConnectionManager->CreateConnection(GetDisplayName(conn) + tr(" (Copy)"), // - GetConnectionGroupId(conn), // - ConnectionManager->GetConnectionRoot(conn)); + ConnectionManager->CreateConnection(ConnectionManager->GetConnectionRoot(conn.connectionId), + GetDisplayName(conn.connectionId) + tr(" (Copy)"), conn.groupId); } } void MainWindow::on_action_RCM_EditThis_triggered() { CheckCurrentWidget; - OnEditRequested(get<1>(widget->Identifier())); + OnEditRequested(widget->Identifier().connectionId); } void MainWindow::on_action_RCM_EditAsJson_triggered() { CheckCurrentWidget; - OnEditJsonRequested(get<1>(widget->Identifier())); + OnEditJsonRequested(widget->Identifier().connectionId); } void MainWindow::on_chartVisibilityBtn_clicked() @@ -960,11 +986,12 @@ void MainWindow::on_action_RCM_SetAutoConnection_triggered() if (current != nullptr) { auto widget = GetItemWidget(current); - auto &conn = get<1>(widget->Identifier()); - GlobalConfig.autoStartId = conn.toString(); + const auto identifier = widget->Identifier(); + GlobalConfig.autoStartId = identifier; + GlobalConfig.autoStartBehavior = AUTO_CONNECTION_FIXED; if (!GlobalConfig.uiConfig.quietMode) { - hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn))); + qvApp->showMessage(tr("%1 has been set as auto connect.").arg(GetDisplayName(identifier.connectionId))); } SaveGlobalSettings(); } @@ -979,14 +1006,14 @@ void MainWindow::on_action_RCM_ClearUsage_triggered() if (widget) { if (widget->IsConnection()) - ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier())); + ConnectionManager->ClearConnectionUsage(widget->Identifier()); else - ConnectionManager->ClearGroupUsage(get<0>(widget->Identifier())); + ConnectionManager->ClearGroupUsage(widget->Identifier().groupId); } } } -void MainWindow::on_action_RCM_LatencyTest_triggered() +void MainWindow::on_action_RCM_UpdateSubscription_triggered() { auto current = connectionListWidget->currentItem(); if (current != nullptr) @@ -995,13 +1022,32 @@ void MainWindow::on_action_RCM_LatencyTest_triggered() if (widget) { if (widget->IsConnection()) - ConnectionManager->StartLatencyTest(get<1>(widget->Identifier())); + return; + const auto gid = widget->Identifier().groupId; + if (ConnectionManager->GetGroupMetaObject(gid).isSubscription) + ConnectionManager->UpdateSubscriptionAsync(gid); else - ConnectionManager->StartLatencyTest(get<0>(widget->Identifier())); + QvMessageBoxInfo(this, tr("Update Subscription"), tr("Selected group is not a subscription")); } } } +void MainWindow::on_action_RCM_LatencyTest_triggered() +{ + for (const auto ¤t : connectionListWidget->selectedItems()) + { + if (!current) + continue; + const auto widget = GetItemWidget(current); + if (!widget) + continue; + if (widget->IsConnection()) + ConnectionManager->StartLatencyTest(widget->Identifier().connectionId); + else + ConnectionManager->StartLatencyTest(widget->Identifier().groupId); + } +} + void MainWindow::on_pluginsBtn_clicked() { PluginManageWindow(this).exec(); diff --git a/src/ui/w_MainWindow.hpp b/src/ui/windows/w_MainWindow.hpp similarity index 80% rename from src/ui/w_MainWindow.hpp rename to src/ui/windows/w_MainWindow.hpp index cacd61b2..c090a97f 100644 --- a/src/ui/w_MainWindow.hpp +++ b/src/ui/windows/w_MainWindow.hpp @@ -1,16 +1,12 @@ -#pragma once +#pragma once #include "common/HTTPRequestHelper.hpp" -#include "common/LogHighlighter.hpp" -#include "components/speedchart/speedwidget.hpp" -#include "core/handler/ConfigHandler.hpp" +#include "src/components/speedchart/speedwidget.hpp" +#include "ui/common/LogHighlighter.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui_w_MainWindow.h" #include -#include -#include -#include // ========================================================================================== #include "ui/widgets/ConnectionInfoWidget.hpp" @@ -33,7 +29,8 @@ class MainWindow public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; - static MainWindow *MainWindowInstance; + void ProcessCommand(QString command, QStringList commands, QMap args); + signals: void StartConnection() const; void StopConnection() const; @@ -62,13 +59,14 @@ class MainWindow void on_pluginsBtn_clicked(); - private: + private slots: void on_actionExit_triggered(); void on_action_StartThis_triggered(); void on_action_RCM_SetAutoConnection_triggered(); void on_action_RCM_EditThis_triggered(); void on_action_RCM_EditAsJson_triggered(); void on_action_RCM_EditAsComplex_triggered(); + void on_action_RCM_UpdateSubscription_triggered(); void on_action_RCM_LatencyTest_triggered(); void on_action_RCM_RenameThis_triggered(); void on_action_RCM_DeleteThese_triggered(); @@ -80,25 +78,27 @@ class MainWindow // void OnConnectionWidgetFocusRequested(const ConnectionItemWidget *widget); // + private: void ToggleVisibility(); void OnEditRequested(const ConnectionId &id); void OnEditJsonRequested(const ConnectionId &id); - void OnConnected(const ConnectionId &id); - void OnDisconnected(const ConnectionId &id); + void OnConnected(const ConnectionGroupPair &id); + void OnDisconnected(const ConnectionGroupPair &id); // - void OnStatsAvailable(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); - void OnVCoreLogAvailable(const ConnectionId &id, const QString &log); + void OnStatsAvailable(const ConnectionGroupPair &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); + void OnVCoreLogAvailable(const ConnectionGroupPair &id, const QString &log); // - void OnConnectionCreated(const ConnectionId &id, const QString &displayName); - void OnConnectionDeleted(const ConnectionId &id, const GroupId &groupId); - void OnConnectionGroupChanged(const ConnectionId &id, const GroupId &originalGroup, const GroupId &newGroup); + void OnConnectionCreated(const ConnectionGroupPair &Id, const QString &displayName); + void OnConnectionDeleted(const ConnectionGroupPair &Id); + void OnConnectionLinkedWithGroup(const ConnectionGroupPair &id); // void OnGroupCreated(const GroupId &id, const QString &displayName); void OnGroupDeleted(const GroupId &id, const QList &connections); // void SortConnectionList(MW_ITEM_COL byCol, bool asending); // - void ReloadRecentConnectionList(const QList &items); + void ReloadRecentConnectionList(); + void OnRecentConnectionsMenuReadyToShow(); protected: void timerEvent(QTimerEvent *event) override; @@ -107,11 +107,10 @@ class MainWindow void closeEvent(QCloseEvent *) override; private: - QHash> groupNodes; - QHash> connectionNodes; + QHash> groupNodes; + QHash> connectionNodes; // Charts SpeedWidget *speedChartWidget; - QSystemTrayIcon hTray; SyntaxHighlighter *vCoreLogHighlighter; ConnectionInfoWidget *infoWidget; // @@ -119,8 +118,7 @@ class MainWindow QMenu *tray_RootMenu = new QMenu(this); QMenu *tray_SystemProxyMenu = new QMenu(tr("System Proxy"), this); QMenu *tray_RecentConnectionsMenu = new QMenu(tr("Recent Connections"), this); - // - QList recentConnections; + QAction *tray_ClearRecentConnectionsAction = new QAction(tr("Clear Recent Connections"), this); // QAction *tray_action_ShowHide = new QAction(tr("Hide"), this); QAction *tray_action_ShowPreferencesWindow = new QAction(tr("Preferences"), this); @@ -134,6 +132,7 @@ class MainWindow QMenu *connectionListRCM_Menu = new QMenu(this); QAction *action_RCM_Start = new QAction(tr("Connect to this"), this); QAction *action_RCM_SetAutoConnection = new QAction(tr("Set as automatically connected"), this); + QAction *action_RCM_UpdateSubscription = new QAction(tr("Update Subscription"), this); QAction *action_RCM_Edit = new QAction(tr("Edit"), this); QAction *action_RCM_EditJson = new QAction(tr("Edit as JSON"), this); QAction *action_RCM_EditComplex = new QAction(tr("Edit as Complex Config"), this); @@ -159,13 +158,14 @@ class MainWindow QAction *action_RCM_tovCoreLog = new QAction(tr("Switch to vCore log"), this); QAction *action_RCM_toQvLog = new QAction(tr("Switch to Qv2ray log"), this); // - ConnectionId lastConnectedId; + ConnectionGroupPair lastConnectedIdentifier; void MWSetSystemProxy(); void MWClearSystemProxy(); void CheckSubscriptionsUpdate(); + bool StartAutoConnectionEntry(); // - void UpdateColorScheme(); + void updateColorScheme(); // - void MWAddConnectionItem_p(const ConnectionId &connection, const GroupId &groupId); + void MWAddConnectionItem_p(const ConnectionGroupPair &id); void MWAddGroupItem_p(const GroupId &groupId); }; diff --git a/src/ui/w_MainWindow.ui b/src/ui/windows/w_MainWindow.ui similarity index 94% rename from src/ui/w_MainWindow.ui rename to src/ui/windows/w_MainWindow.ui index b308458b..0913321d 100644 --- a/src/ui/w_MainWindow.ui +++ b/src/ui/windows/w_MainWindow.ui @@ -6,8 +6,8 @@ 0 0 - 860 - 650 + 882 + 687 @@ -22,17 +22,10 @@ - + 5 - - - - Subscriptions - - - @@ -93,7 +86,7 @@ - + :/assets/icons/ui_light/locate.png:/assets/icons/ui_light/locate.png @@ -117,7 +110,7 @@ - + :/assets/icons/ui_light/sort.png:/assets/icons/ui_light/sort.png @@ -184,23 +177,18 @@ - - - 0 - 0 - - Import Connection - Add + New - - - 22 - 22 - + + + + + + Groups / Subscriptions @@ -303,7 +291,7 @@ - + :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png @@ -366,7 +354,7 @@ - + :/assets/icons/ui_light/delete.png:/assets/icons/ui_light/delete.png @@ -546,12 +534,11 @@ - subsButton connectionListWidget importConfigButton - + diff --git a/src/ui/windows/w_MainWindow_extra.cpp b/src/ui/windows/w_MainWindow_extra.cpp new file mode 100644 index 00000000..547172d9 --- /dev/null +++ b/src/ui/windows/w_MainWindow_extra.cpp @@ -0,0 +1,115 @@ +#include "common/QvHelpers.hpp" +#include "components/proxy/QvProxyConfigurator.hpp" +#include "src/Qv2rayApplication.hpp" +#include "w_MainWindow.hpp" + +void MainWindow::MWSetSystemProxy() +{ + const auto inboundPorts = KernelInstance->InboundPorts(); + const auto inboundHosts = KernelInstance->InboundHosts(); + + const bool httpEnabled = inboundPorts.contains("http_IN"); + const auto httpPort = inboundPorts["http_IN"]; + + const bool socksEnabled = inboundPorts.contains("socks_IN"); + const auto socksPort = inboundPorts["socks_IN"]; + + QString proxyAddress; + + if (httpEnabled || socksEnabled) + { + proxyAddress = inboundHosts[httpEnabled ? "http_IN" : "socks_IN"]; + + if (proxyAddress == "0.0.0.0") + proxyAddress = "127.0.0.1"; + + if (proxyAddress == "::") + proxyAddress = "::1"; + + LOG(MODULE_UI, "ProxyAddress: " + proxyAddress); + SetSystemProxy(proxyAddress, httpPort, socksPort); + qvAppTrayIcon->setIcon(Q_TRAYICON("tray-systemproxy.png")); + if (!GlobalConfig.uiConfig.quietMode) + { + qvApp->showMessage(tr("System proxy configured.")); + } + } + else + { + LOG(MODULE_PROXY, "Neither of HTTP nor SOCKS is enabled, cannot set system proxy.") + QvMessageBoxWarn(this, tr("Cannot set system proxy"), tr("Both HTTP and SOCKS inbounds are not enabled")); + } +} + +void MainWindow::MWClearSystemProxy() +{ + ClearSystemProxy(); + qvAppTrayIcon->setIcon(KernelInstance->CurrentConnection().isEmpty() ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png")); + if (!GlobalConfig.uiConfig.quietMode) + { + qvApp->showMessage(tr("System proxy removed.")); + } +} + +bool MainWindow::StartAutoConnectionEntry() +{ + switch (GlobalConfig.autoStartBehavior) + { + case AUTO_CONNECTION_NONE: return false; + case AUTO_CONNECTION_FIXED: return ConnectionManager->StartConnection(GlobalConfig.autoStartId); + case AUTO_CONNECTION_LAST_CONNECTED: return ConnectionManager->StartConnection(GlobalConfig.lastConnectedId); + } + Q_UNREACHABLE(); +} + +void MainWindow::CheckSubscriptionsUpdate() +{ + QList> updateList; + QStringList updateNamesList; + + for (const auto &entry : ConnectionManager->Subscriptions()) + { + const auto info = ConnectionManager->GetGroupMetaObject(entry); + // + // The update is ignored. + if (info.subscriptionOption.updateInterval == 0) + continue; + // + const auto lastRenewDate = QDateTime::fromTime_t(info.lastUpdatedDate); + const auto renewTime = lastRenewDate.addSecs(info.subscriptionOption.updateInterval * 86400); + + if (renewTime <= QDateTime::currentDateTime()) + { + updateList << QPair{ info.displayName, entry }; + updateNamesList << info.displayName; + LOG(MODULE_SUBSCRIPTION, QString("Subscription update \"%1\": L=%2 R=%3 I=%4") + .arg(info.displayName) + .arg(lastRenewDate.toString()) + .arg(QSTRN(info.subscriptionOption.updateInterval)) + .arg(renewTime.toString())) + } + } + + if (!updateList.isEmpty()) + { + const auto result = GlobalConfig.uiConfig.quietMode ? QMessageBox::Yes : + QvMessageBoxAsk(this, tr("Update Subscriptions"), // + tr("Do you want to update these subscriptions?") + NEWLINE + // + updateNamesList.join(NEWLINE), // + QMessageBox::Ignore); + + for (const auto &[name, id] : updateList) + { + if (result == QMessageBox::Yes) + { + LOG(MODULE_UI, "Updating subscription: " + name) + ConnectionManager->UpdateSubscriptionAsync(id); + } + else if (result == QMessageBox::Ignore) + { + LOG(MODULE_UI, "Ignored subscription update: " + name) + ConnectionManager->IgnoreSubscriptionUpdate(id); + } + } + } +} diff --git a/src/ui/w_PluginManager.cpp b/src/ui/windows/w_PluginManager.cpp similarity index 96% rename from src/ui/w_PluginManager.cpp rename to src/ui/windows/w_PluginManager.cpp index 19e46094..c26acb2c 100644 --- a/src/ui/w_PluginManager.cpp +++ b/src/ui/windows/w_PluginManager.cpp @@ -3,9 +3,10 @@ #include "common/QvHelpers.hpp" #include "components/plugins/QvPluginHost.hpp" #include "core/settings/SettingsBackend.hpp" +#include "ui/common/UIBase.hpp" #include "ui/editors/w_JsonEditor.hpp" -PluginManageWindow::PluginManageWindow(QWidget *parent) : QDialog(parent) +PluginManageWindow::PluginManageWindow(QWidget *parent) : QvDialog(parent) { setupUi(this); for (auto &plugin : PluginHost->AvailablePlugins()) @@ -22,6 +23,7 @@ PluginManageWindow::PluginManageWindow(QWidget *parent) : QDialog(parent) PluginManageWindow::~PluginManageWindow() { + DEBUG(MODULE_UI, "Plugin window destructor.") } void PluginManageWindow::on_pluginListWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) @@ -122,7 +124,7 @@ void PluginManageWindow::on_openPluginFolder_clicked() { pluginPath.mkpath(QV2RAY_CONFIG_DIR + "plugins/"); } -#ifdef Q_OS_LINUX +#ifdef FALL_BACK_TO_XDG_OPEN QProcess::execute("xdg-open", { pluginPath.absolutePath() }); #else QDesktopServices::openUrl(QUrl::fromLocalFile(pluginPath.absolutePath())); diff --git a/src/ui/w_PluginManager.hpp b/src/ui/windows/w_PluginManager.hpp similarity index 55% rename from src/ui/w_PluginManager.hpp rename to src/ui/windows/w_PluginManager.hpp index fa40d450..f18b49b8 100644 --- a/src/ui/w_PluginManager.hpp +++ b/src/ui/windows/w_PluginManager.hpp @@ -1,12 +1,12 @@ #pragma once +#include "ui/common/QvDialog.hpp" #include "ui_w_PluginManager.h" -#include #include class PluginManageWindow - : public QDialog + : public QvDialog , private Ui::w_PluginManager { Q_OBJECT @@ -14,20 +14,32 @@ class PluginManageWindow public: explicit PluginManageWindow(QWidget *parent = nullptr); ~PluginManageWindow(); + void processCommands(QString command, QStringList commands, QMap) override + { + if (commands.isEmpty()) + return; + if (command == "open") + { + const auto c = commands.takeFirst(); + if (c == "plugindir") + on_openPluginFolder_clicked(); + if (c == "metadata") + tabWidget->setCurrentIndex(0); + if (c == "settings") + tabWidget->setCurrentIndex(1); + } + } private slots: void on_pluginListWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); void on_pluginListWidget_itemClicked(QListWidgetItem *item); void on_pluginListWidget_itemChanged(QListWidgetItem *item); - void on_pluginEditSettingsJsonBtn_clicked(); - void on_pluginListWidget_itemSelectionChanged(); - void on_openPluginFolder_clicked(); - void on_toolButton_clicked(); private: + void updateColorScheme() override{}; std::unique_ptr settingsWidget; bool isLoading = true; }; diff --git a/src/ui/w_PluginManager.ui b/src/ui/windows/w_PluginManager.ui similarity index 100% rename from src/ui/w_PluginManager.ui rename to src/ui/windows/w_PluginManager.ui diff --git a/src/ui/w_PreferencesWindow.cpp b/src/ui/windows/w_PreferencesWindow.cpp similarity index 55% rename from src/ui/w_PreferencesWindow.cpp rename to src/ui/windows/w_PreferencesWindow.cpp index 0a88d2b1..8a7cc608 100644 --- a/src/ui/w_PreferencesWindow.cpp +++ b/src/ui/windows/w_PreferencesWindow.cpp @@ -1,24 +1,28 @@ -#include "w_PreferencesWindow.hpp" +#include "w_PreferencesWindow.hpp" #include "common/HTTPRequestHelper.hpp" #include "common/QvHelpers.hpp" #include "common/QvTranslator.hpp" #include "components/autolaunch/QvAutoLaunch.hpp" -#include "components/plugins/interface/QvPluginInterface.hpp" -#include "components/plugins/toolbar/QvToolbar.hpp" +#include "components/ntp/QvNTPClient.hpp" #include "core/connection/ConnectionIO.hpp" #include "core/handler/ConfigHandler.hpp" #include "core/kernel/V2rayKernelInteractions.hpp" #include "core/settings/SettingsBackend.hpp" +#include "src/plugin-interface/QvPluginInterface.hpp" +#include "ui/styles/StyleManager.hpp" +#include "ui/widgets/DnsSettingsWidget.hpp" #include "ui/widgets/RouteSettingsMatrix.hpp" #include #include #include #include -#include -#include +#include +using Qv2ray::common::validation::IsIPv4Address; +using Qv2ray::common::validation::IsIPv6Address; +using Qv2ray::common::validation::IsValidDNSServer; using Qv2ray::common::validation::IsValidIPAddress; #define LOADINGCHECK \ @@ -29,26 +33,25 @@ using Qv2ray::common::validation::IsValidIPAddress; if (finishedLoading) \ NeedRestart = true; -PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), CurrentConfig() +#define SET_PROXY_UI_ENABLE(_enabled) \ + qvProxyTypeCombo->setEnabled(_enabled); \ + qvProxyAddressTxt->setEnabled(_enabled); \ + qvProxyPortCB->setEnabled(_enabled); + +#define SET_AUTOSTART_UI_ENABLED(_enabled) \ + autoStartConnCombo->setEnabled(_enabled); \ + autoStartSubsCombo->setEnabled(_enabled); + +PreferencesWindow::PreferencesWindow(QWidget *parent) : QvDialog(parent), CurrentConfig() { setupUi(this); // - // We currently don't support this feature. - // tProxyGroupBox->setVisible(false); tProxyCheckBox->setVisible(false); label_7->setVisible(false); // QvMessageBusConnect(PreferencesWindow); textBrowser->setHtml(StringFromFile(":/assets/credit.html")); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - // - // Set network Toolbar page state. - networkToolbarPage->setEnabled(StartupOption.enableToolbarPlguin); - - if (!StartupOption.enableToolbarPlguin) - { - networkToolbarInfoLabel->setText(tr("Qv2ray Network Toolbar is disabled and still under test. Add --withToolbarPlugin to enable.")); - } // We add locales auto langs = Qv2rayTranslator->GetAvailableLanguages(); @@ -64,11 +67,9 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current // Set auto start button state SetAutoStartButtonsState(GetLaunchAtLoginStatus()); + themeCombo->addItems(StyleManager->AllStyles()); // - nsBarContentCombo->addItems(NetSpeedPluginMessages.values()); - themeCombo->addItems(QStyleFactory::keys()); - // - qvVersion->setText(QV2RAY_VERSION_STRING); + qvVersion->setText(QV2RAY_VERSION_STRING ":" + QSTRN(QV2RAY_VERSION_BUILD)); qvBuildInfo->setText(QV2RAY_BUILD_INFO); qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO); qvBuildTime->setText(__DATE__ " " __TIME__); @@ -80,11 +81,6 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current themeCombo->setCurrentText(CurrentConfig.uiConfig.theme); darkThemeCB->setChecked(CurrentConfig.uiConfig.useDarkTheme); darkTrayCB->setChecked(CurrentConfig.uiConfig.useDarkTrayIcon); -#if (QV2RAY_USE_BUILTIN_DARKTHEME) - // If we use built in theme, it should always be fusion. - themeCombo->setEnabled(!CurrentConfig.uiConfig.useDarkTheme); - darkThemeLabel->setText(tr("Use Darkmode Theme")); -#endif languageComboBox->setCurrentText(CurrentConfig.uiConfig.language); logLevelComboBox->setCurrentIndex(CurrentConfig.logLevel); tProxyCheckBox->setChecked(CurrentConfig.tProxySupport); @@ -94,98 +90,87 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current // bool have_http = CurrentConfig.inboundConfig.useHTTP; httpGroupBox->setChecked(have_http); - httpPortLE->setValue(CurrentConfig.inboundConfig.http_port); - httpAuthCB->setChecked(CurrentConfig.inboundConfig.http_useAuth); + httpPortLE->setValue(CurrentConfig.inboundConfig.httpSettings.port); + httpAuthCB->setChecked(CurrentConfig.inboundConfig.httpSettings.useAuth); // - httpAuthCB->setChecked(CurrentConfig.inboundConfig.http_useAuth); - httpAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.http_useAuth); - httpAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.http_useAuth); - httpAuthUsernameTxt->setText(CurrentConfig.inboundConfig.httpAccount.user); - httpAuthPasswordTxt->setText(CurrentConfig.inboundConfig.httpAccount.pass); + httpAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.httpSettings.useAuth); + httpAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.httpSettings.useAuth); + httpAuthUsernameTxt->setText(CurrentConfig.inboundConfig.httpSettings.account.user); + httpAuthPasswordTxt->setText(CurrentConfig.inboundConfig.httpSettings.account.pass); + httpSniffingCB->setChecked(CurrentConfig.inboundConfig.httpSettings.sniffing); // // bool have_socks = CurrentConfig.inboundConfig.useSocks; socksGroupBox->setChecked(have_socks); - socksPortLE->setValue(CurrentConfig.inboundConfig.socks_port); + socksPortLE->setValue(CurrentConfig.inboundConfig.socksSettings.port); // - socksAuthCB->setChecked(CurrentConfig.inboundConfig.socks_useAuth); - socksAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.socks_useAuth); - socksAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.socks_useAuth); - socksAuthUsernameTxt->setText(CurrentConfig.inboundConfig.socksAccount.user); - socksAuthPasswordTxt->setText(CurrentConfig.inboundConfig.socksAccount.pass); + socksAuthCB->setChecked(CurrentConfig.inboundConfig.socksSettings.useAuth); + socksAuthUsernameTxt->setEnabled(CurrentConfig.inboundConfig.socksSettings.useAuth); + socksAuthPasswordTxt->setEnabled(CurrentConfig.inboundConfig.socksSettings.useAuth); + socksAuthUsernameTxt->setText(CurrentConfig.inboundConfig.socksSettings.account.user); + socksAuthPasswordTxt->setText(CurrentConfig.inboundConfig.socksSettings.account.pass); // Socks UDP Options - socksUDPCB->setChecked(CurrentConfig.inboundConfig.socksUDP); - socksUDPIP->setEnabled(CurrentConfig.inboundConfig.socksUDP); - socksUDPIP->setText(CurrentConfig.inboundConfig.socksLocalIP); + socksUDPCB->setChecked(CurrentConfig.inboundConfig.socksSettings.enableUDP); + socksUDPIP->setEnabled(CurrentConfig.inboundConfig.socksSettings.enableUDP); + socksUDPIP->setText(CurrentConfig.inboundConfig.socksSettings.localIP); + socksSniffingCB->setChecked(CurrentConfig.inboundConfig.socksSettings.sniffing); // // bool have_tproxy = CurrentConfig.inboundConfig.useTPROXY; tproxGroupBox->setChecked(have_tproxy); - tproxyListenAddr->setText(CurrentConfig.inboundConfig.tproxy_ip); - tProxyPort->setValue(CurrentConfig.inboundConfig.tproxy_port); - tproxyEnableTCP->setChecked(CurrentConfig.inboundConfig.tproxy_use_tcp); - tproxyEnableUDP->setChecked(CurrentConfig.inboundConfig.tproxy_use_udp); - tproxyFollowRedirect->setChecked(CurrentConfig.inboundConfig.tproxy_followRedirect); - tproxyMode->setCurrentText(CurrentConfig.inboundConfig.tproxy_mode); + tproxyListenAddr->setText(CurrentConfig.inboundConfig.tProxySettings.tProxyIP); + tproxyListenV6Addr->setText(CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP); + tProxyPort->setValue(CurrentConfig.inboundConfig.tProxySettings.port); + tproxyEnableTCP->setChecked(CurrentConfig.inboundConfig.tProxySettings.hasTCP); + tproxyEnableUDP->setChecked(CurrentConfig.inboundConfig.tProxySettings.hasUDP); + tproxyMode->setCurrentText(CurrentConfig.inboundConfig.tProxySettings.mode); outboundMark->setValue(CurrentConfig.outboundConfig.mark); - dnsIntercept->setChecked(CurrentConfig.inboundConfig.dnsIntercept); - DnsFreedomCb->setChecked(CurrentConfig.connectionConfig.v2rayFreedomDNS); + dnsIntercept->setChecked(CurrentConfig.inboundConfig.tProxySettings.dnsIntercept); + DnsFreedomCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.v2rayFreedomDNS); // // vCorePathTxt->setText(CurrentConfig.kernelConfig.KernelPath()); vCoreAssetsPathTxt->setText(CurrentConfig.kernelConfig.AssetsPath()); - enableAPI->setChecked(CurrentConfig.apiConfig.enableAPI); - statsPortBox->setValue(CurrentConfig.apiConfig.statsPort); + enableAPI->setChecked(CurrentConfig.kernelConfig.enableAPI); + statsPortBox->setValue(CurrentConfig.kernelConfig.statsPort); // // - bypassCNCb->setChecked(CurrentConfig.connectionConfig.bypassCN); - proxyDefaultCb->setChecked(CurrentConfig.connectionConfig.enableProxy); + bypassCNCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.bypassCN); + bypassBTCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.bypassBT); + proxyDefaultCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.enableProxy); // - localDNSCb->setChecked(CurrentConfig.connectionConfig.withLocalDNS); + localDNSCb->setChecked(CurrentConfig.defaultRouteConfig.connectionConfig.withLocalDNS); // pluginKernelV2rayIntegrationCB->setChecked(CurrentConfig.pluginConfig.v2rayIntegration); pluginKernelPortAllocateCB->setValue(CurrentConfig.pluginConfig.portAllocationStart); + pluginKernelPortAllocateCB->setEnabled(CurrentConfig.pluginConfig.v2rayIntegration); + // + // + latencyTCPingRB->setChecked(CurrentConfig.networkConfig.latencyTestingMethod == TCPING); + latencyICMPingRB->setChecked(CurrentConfig.networkConfig.latencyTestingMethod == ICMPING); // qvProxyPortCB->setValue(CurrentConfig.networkConfig.port); qvProxyAddressTxt->setText(CurrentConfig.networkConfig.address); qvProxyTypeCombo->setCurrentText(CurrentConfig.networkConfig.type); - qvNetworkUATxt->setText(CurrentConfig.networkConfig.userAgent); - switch (CurrentConfig.networkConfig.proxyType) - { - case Qv2rayNetworkConfig::QVPROXY_NONE: - { - qvProxyNoProxy->setChecked(true); - break; - } - case Qv2rayNetworkConfig::QVPROXY_SYSTEM: - { - qvProxySystemProxy->setChecked(true); - break; - } - case Qv2rayNetworkConfig::QVPROXY_CUSTOM: - { - qvProxyCustomProxy->setChecked(true); - break; - } - } + qvNetworkUATxt->setEditText(CurrentConfig.networkConfig.userAgent); + // + qvProxyNoProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_NONE); + qvProxySystemProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_SYSTEM); + qvProxyCustomProxy->setChecked(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_CUSTOM); + // + SET_PROXY_UI_ENABLE(CurrentConfig.networkConfig.proxyType == Qv2rayConfig_Network::QVPROXY_CUSTOM) // quietModeCB->setChecked(CurrentConfig.uiConfig.quietMode); // // Advanced config. setAllowInsecureCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecure); - setAllowInsecureCiphersCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecureCiphers); + setSessionResumptionCB->setChecked(CurrentConfig.advancedConfig.setSessionResumption); setTestLatenctCB->setChecked(CurrentConfig.advancedConfig.testLatencyPeriodcally); // - DNSListTxt->clear(); - for (auto dnsStr : CurrentConfig.connectionConfig.dnsList) - { - auto str = dnsStr.trimmed(); - if (!str.isEmpty()) - { - DNSListTxt->appendPlainText(str); - } - } - + dnsSettingsWidget = new DnsSettingsWidget(this); + dnsSettingsWidget->SetDNSObject(CurrentConfig.defaultRouteConfig.dnsConfig); + dnsSettingsLayout->addWidget(dnsSettingsWidget); + // #ifdef DISABLE_AUTO_UPDATE updateSettingsGroupBox->setEnabled(false); updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor.")); @@ -195,69 +180,50 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current cancelIgnoreVersionBtn->setEnabled(!CurrentConfig.updateConfig.ignoredVersion.isEmpty()); ignoredNextVersion->setText(CurrentConfig.updateConfig.ignoredVersion); // - for (auto i = 0; i < CurrentConfig.toolBarConfig.Pages.size(); i++) - { - nsBarPagesList->addItem(tr("Page") + QSTRN(i + 1) + ": " + QSTRN(CurrentConfig.toolBarConfig.Pages[i].Lines.size()) + " " + - tr("Item(s)")); - } - - if (CurrentConfig.toolBarConfig.Pages.size() > 0) - { - nsBarPagesList->setCurrentRow(0); - on_nsBarPagesList_currentRowChanged(0); - } - else - { - networkToolbarSettingsFrame->setEnabled(false); - nsBarLinesList->setEnabled(false); - nsBarLineDelBTN->setEnabled(false); - nsBarLineAddBTN->setEnabled(false); - nsBarPageYOffset->setEnabled(false); - } - - CurrentBarPageId = 0; // - // Empty for global config. - auto autoStartConnId = ConnectionId(CurrentConfig.autoStartId); - auto autoStartGroupId = GetConnectionGroupId(autoStartConnId); - - for (auto group : ConnectionManager->AllGroups()) { - autoStartSubsCombo->addItem(GetDisplayName(group)); + noAutoConnectRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_NONE); + lastConnectedRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_LAST_CONNECTED); + fixedAutoConnectRB->setChecked(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_FIXED); + // + SET_AUTOSTART_UI_ENABLED(CurrentConfig.autoStartBehavior == AUTO_CONNECTION_FIXED); + // + auto autoStartConnId = CurrentConfig.autoStartId.connectionId; + auto autoStartGroupId = CurrentConfig.autoStartId.groupId; + // + for (const auto &group : ConnectionManager->AllGroups()) // + autoStartSubsCombo->addItem(GetDisplayName(group), group.toString()); + + autoStartSubsCombo->setCurrentText(GetDisplayName(autoStartGroupId)); + + for (const auto &conn : ConnectionManager->Connections(autoStartGroupId)) + autoStartConnCombo->addItem(GetDisplayName(conn), conn.toString()); + + autoStartConnCombo->setCurrentText(GetDisplayName(autoStartConnId)); } - - autoStartSubsCombo->setCurrentText(GetDisplayName(autoStartGroupId)); - - for (auto conn : ConnectionManager->Connections(autoStartGroupId)) - { - autoStartConnCombo->addItem(GetDisplayName(conn)); - } - - autoStartConnCombo->setCurrentText(GetDisplayName(autoStartConnId)); - // FP Settings - if (CurrentConfig.connectionConfig.forwardProxyConfig.type.trimmed().isEmpty()) + if (CurrentConfig.defaultRouteConfig.forwardProxyConfig.type.trimmed().isEmpty()) { - CurrentConfig.connectionConfig.forwardProxyConfig.type = "http"; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.type = "http"; } - fpGroupBox->setChecked(CurrentConfig.connectionConfig.forwardProxyConfig.enableForwardProxy); - fpUsernameTx->setText(CurrentConfig.connectionConfig.forwardProxyConfig.username); - fpPasswordTx->setText(CurrentConfig.connectionConfig.forwardProxyConfig.password); - fpAddressTx->setText(CurrentConfig.connectionConfig.forwardProxyConfig.serverAddress); - fpTypeCombo->setCurrentText(CurrentConfig.connectionConfig.forwardProxyConfig.type); - fpPortSB->setValue(CurrentConfig.connectionConfig.forwardProxyConfig.port); - fpUseAuthCB->setChecked(CurrentConfig.connectionConfig.forwardProxyConfig.useAuth); + fpGroupBox->setChecked(CurrentConfig.defaultRouteConfig.forwardProxyConfig.enableForwardProxy); + fpUsernameTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.username); + fpPasswordTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.password); + fpAddressTx->setText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.serverAddress); + fpTypeCombo->setCurrentText(CurrentConfig.defaultRouteConfig.forwardProxyConfig.type); + fpPortSB->setValue(CurrentConfig.defaultRouteConfig.forwardProxyConfig.port); + fpUseAuthCB->setChecked(CurrentConfig.defaultRouteConfig.forwardProxyConfig.useAuth); fpUsernameTx->setEnabled(fpUseAuthCB->isChecked()); fpPasswordTx->setEnabled(fpUseAuthCB->isChecked()); // maxLogLinesSB->setValue(CurrentConfig.uiConfig.maximumLogLines); // - setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy); + setSysProxyCB->setChecked(CurrentConfig.inboundConfig.systemProxySettings.setSystemProxy); // finishedLoading = true; routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this); - routeSettingsWidget->SetRouteConfig(CurrentConfig.connectionConfig.routeConfig); + routeSettingsWidget->SetRouteConfig(CurrentConfig.defaultRouteConfig.routeConfig); advRouteSettingsLayout->addWidget(routeSettingsWidget); } @@ -265,14 +231,16 @@ QvMessageBusSlotImpl(PreferencesWindow) { switch (msg) { - case UPDATE_COLORSCHEME: - break; // - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + case UPDATE_COLORSCHEME: break; } } PreferencesWindow::~PreferencesWindow() { + DEBUG(MODULE_UI, "Preference window destructor.") } void PreferencesWindow::on_buttonBox_accepted() @@ -286,25 +254,25 @@ void PreferencesWindow::on_buttonBox_accepted() if (CurrentConfig.inboundConfig.useHTTP) { size++; - ports << CurrentConfig.inboundConfig.http_port; + ports << CurrentConfig.inboundConfig.httpSettings.port; } if (CurrentConfig.inboundConfig.useSocks) { size++; - ports << CurrentConfig.inboundConfig.socks_port; + ports << CurrentConfig.inboundConfig.socksSettings.port; } if (CurrentConfig.inboundConfig.useTPROXY) { size++; - ports << CurrentConfig.inboundConfig.tproxy_port; + ports << CurrentConfig.inboundConfig.tProxySettings.port; } if (!StartupOption.noAPI) { size++; - ports << CurrentConfig.apiConfig.statsPort; + ports << CurrentConfig.kernelConfig.statsPort; } if (ports.size() != size) @@ -312,10 +280,23 @@ void PreferencesWindow::on_buttonBox_accepted() // Duplicates detected. QvMessageBoxWarn(this, tr("Preferences"), tr("Duplicated port numbers detected, please check the port number settings.")); } - else if (CurrentConfig.inboundConfig.listenip.toLower() != "localhost" && !IsValidIPAddress(CurrentConfig.inboundConfig.listenip)) + else if (!IsValidIPAddress(CurrentConfig.inboundConfig.listenip)) { QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid inbound listening address.")); } + else if (!IsIPv4Address(CurrentConfig.inboundConfig.tProxySettings.tProxyIP)) + { + QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid tproxy listening ivp4 address.")); + } + else if (CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP != "" && + !IsIPv6Address(CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP)) + { + QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid tproxy listening ipv6 address.")); + } + else if (!dnsSettingsWidget->CheckIsValidDNS()) + { + QvMessageBoxWarn(this, tr("Preferences"), tr("Invalid DNS settings.")); + } else { if (CurrentConfig.uiConfig.language != GlobalConfig.uiConfig.language) @@ -331,15 +312,25 @@ void PreferencesWindow::on_buttonBox_accepted() LOG(MODULE_UI, "Failed to translate UI to: " + CurrentConfig.uiConfig.language) } } - CurrentConfig.connectionConfig.routeConfig = routeSettingsWidget->GetRouteConfig(); - if (!(CurrentConfig.connectionConfig.routeConfig == GlobalConfig.connectionConfig.routeConfig)) + CurrentConfig.defaultRouteConfig.routeConfig = routeSettingsWidget->GetRouteConfig(); + if (!(CurrentConfig.defaultRouteConfig.routeConfig == GlobalConfig.defaultRouteConfig.routeConfig)) { NEEDRESTART } - qApp->setStyle(QStyleFactory::create(CurrentConfig.uiConfig.theme)); + CurrentConfig.defaultRouteConfig.dnsConfig = dnsSettingsWidget->GetDNSObject(); + if (!(CurrentConfig.defaultRouteConfig.dnsConfig == GlobalConfig.defaultRouteConfig.dnsConfig)) + { + NEEDRESTART + } + // + // + if (CurrentConfig.uiConfig.theme != GlobalConfig.uiConfig.theme) + { + StyleManager->ApplyStyle(CurrentConfig.uiConfig.theme); + } SaveGlobalSettings(CurrentConfig); UIMessageBus.EmitGlobalSignal(QvMBMessage::UPDATE_COLORSCHEME); - if (NeedRestart) + if (NeedRestart && !KernelInstance->CurrentConnection().isEmpty()) { this->setEnabled(false); qApp->processEvents(); @@ -355,7 +346,7 @@ void PreferencesWindow::on_httpAuthCB_stateChanged(int checked) bool enabled = checked == Qt::Checked; httpAuthUsernameTxt->setEnabled(enabled); httpAuthPasswordTxt->setEnabled(enabled); - CurrentConfig.inboundConfig.http_useAuth = enabled; + CurrentConfig.inboundConfig.httpSettings.useAuth = enabled; } void PreferencesWindow::on_socksAuthCB_stateChanged(int checked) @@ -364,7 +355,7 @@ void PreferencesWindow::on_socksAuthCB_stateChanged(int checked) bool enabled = checked == Qt::Checked; socksAuthUsernameTxt->setEnabled(enabled); socksAuthPasswordTxt->setEnabled(enabled); - CurrentConfig.inboundConfig.socks_useAuth = enabled; + CurrentConfig.inboundConfig.socksSettings.useAuth = enabled; } void PreferencesWindow::on_languageComboBox_currentTextChanged(const QString &arg1) @@ -390,7 +381,7 @@ void PreferencesWindow::on_listenIPTxt_textEdited(const QString &arg1) NEEDRESTART CurrentConfig.inboundConfig.listenip = arg1; - if (IsValidIPAddress(arg1)) + if (arg1 == "" || IsValidIPAddress(arg1)) { BLACK(listenIPTxt) } @@ -406,37 +397,37 @@ void PreferencesWindow::on_listenIPTxt_textEdited(const QString &arg1) void PreferencesWindow::on_httpAuthUsernameTxt_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.httpAccount.user = arg1; + CurrentConfig.inboundConfig.httpSettings.account.user = arg1; } void PreferencesWindow::on_httpAuthPasswordTxt_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.httpAccount.pass = arg1; + CurrentConfig.inboundConfig.httpSettings.account.pass = arg1; } void PreferencesWindow::on_socksAuthUsernameTxt_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.socksAccount.user = arg1; + CurrentConfig.inboundConfig.socksSettings.account.user = arg1; } void PreferencesWindow::on_socksAuthPasswordTxt_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.socksAccount.pass = arg1; + CurrentConfig.inboundConfig.socksSettings.account.pass = arg1; } void PreferencesWindow::on_proxyDefaultCb_stateChanged(int arg1) { NEEDRESTART - CurrentConfig.connectionConfig.enableProxy = arg1 == Qt::Checked; + CurrentConfig.defaultRouteConfig.connectionConfig.enableProxy = arg1 == Qt::Checked; } void PreferencesWindow::on_localDNSCb_stateChanged(int arg1) { NEEDRESTART - CurrentConfig.connectionConfig.withLocalDNS = arg1 == Qt::Checked; + CurrentConfig.defaultRouteConfig.connectionConfig.withLocalDNS = arg1 == Qt::Checked; } void PreferencesWindow::on_selectVAssetBtn_clicked() @@ -468,32 +459,6 @@ void PreferencesWindow::on_vCorePathTxt_textEdited(const QString &arg1) CurrentConfig.kernelConfig.KernelPath(arg1); } -void PreferencesWindow::on_DNSListTxt_textChanged() -{ - LOADINGCHECK - try - { - QStringList hosts = DNSListTxt->toPlainText().replace("\r", "").split("\n"); - CurrentConfig.connectionConfig.dnsList.clear(); - - for (auto host : hosts) - { - if (host != "" && host != "\r") - { - // Not empty, so we save. - CurrentConfig.connectionConfig.dnsList.push_back(host); - NEEDRESTART - } - } - - BLACK(DNSListTxt) - } - catch (...) - { - RED(DNSListTxt) - } -} - void PreferencesWindow::on_aboutQt_clicked() { QApplication::aboutQt(); @@ -509,11 +474,9 @@ void PreferencesWindow::on_tProxyCheckBox_stateChanged(int arg1) { LOADINGCHECK #ifdef Q_OS_LINUX - // Setting up tProxy for linux // Steps: - // --> 1. Copy V2ray core files to the QV2RAY_TPROXY_VCORE_PATH and - // QV2RAY_TPROXY_VCTL_PATH dir. + // --> 1. Copy V2ray core files to the QV2RAY_TPROXY_VCORE_PATH and QV2RAY_TPROXY_VCTL_PATH dir. // --> 2. Change GlobalConfig.v2CorePath. // --> 3. Call `pkexec setcap // CAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE=eip` on the V2ray core. @@ -594,8 +557,7 @@ void PreferencesWindow::on_tProxyCheckBox_stateChanged(int arg1) } LOG(MODULE_UI, "Calling pkexec and setcap...") - int ret = QProcess::execute("pkexec /usr/sbin/setcap CAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE=eip " + kernelPath); - + int ret = QProcess::execute("pkexec", { "/usr/sbin/setcap CAP_NET_ADMIN,CAP_NET_RAW,CAP_NET_BIND_SERVICE=eip " + kernelPath }); if (ret != 0) { LOG(MODULE_UI, "WARN: setcap exits with code: " + QSTRN(ret)) @@ -608,7 +570,7 @@ void PreferencesWindow::on_tProxyCheckBox_stateChanged(int arg1) } else { - int ret = QProcess::execute("pkexec /usr/sbin/setcap -r " + kernelPath); + int ret = QProcess::execute("pkexec", { "/usr/sbin/setcap -r " + kernelPath }); if (ret != 0) { @@ -632,38 +594,50 @@ void PreferencesWindow::on_tProxyCheckBox_stateChanged(int arg1) void PreferencesWindow::on_bypassCNCb_stateChanged(int arg1) { NEEDRESTART - CurrentConfig.connectionConfig.bypassCN = arg1 == Qt::Checked; + CurrentConfig.defaultRouteConfig.connectionConfig.bypassCN = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_bypassBTCb_stateChanged(int arg1) +{ + NEEDRESTART + if (arg1 == Qt::Checked) + { + QvMessageBoxInfo(this, tr("Note"), + tr("To recognize the protocol of a connection, one must enable sniffing option in inbound proxy.") + NEWLINE + + tr("tproxy inbound's sniffing is enabled by default.")); + } + CurrentConfig.defaultRouteConfig.connectionConfig.bypassBT = arg1 == Qt::Checked; } void PreferencesWindow::on_statsPortBox_valueChanged(int arg1) { NEEDRESTART - CurrentConfig.apiConfig.statsPort = arg1; + CurrentConfig.kernelConfig.statsPort = arg1; } void PreferencesWindow::on_socksPortLE_valueChanged(int arg1) { NEEDRESTART - CurrentConfig.inboundConfig.socks_port = arg1; + CurrentConfig.inboundConfig.socksSettings.port = arg1; } void PreferencesWindow::on_httpPortLE_valueChanged(int arg1) { NEEDRESTART - CurrentConfig.inboundConfig.http_port = arg1; + CurrentConfig.inboundConfig.httpSettings.port = arg1; } void PreferencesWindow::on_socksUDPCB_stateChanged(int arg1) { NEEDRESTART - CurrentConfig.inboundConfig.socksUDP = arg1 == Qt::Checked; + CurrentConfig.inboundConfig.socksSettings.enableUDP = arg1 == Qt::Checked; socksUDPIP->setEnabled(arg1 == Qt::Checked); } void PreferencesWindow::on_socksUDPIP_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.socksLocalIP = arg1; + CurrentConfig.inboundConfig.socksSettings.localIP = arg1; if (IsValidIPAddress(arg1)) { @@ -675,274 +649,6 @@ void PreferencesWindow::on_socksUDPIP_textEdited(const QString &arg1) } } -// ------------------- NET SPEED PLUGIN OPERATIONS -// ----------------------------------------------------------------- - -#define CurrentBarPage CurrentConfig.toolBarConfig.Pages[this->CurrentBarPageId] -#define CurrentBarLine CurrentBarPage.Lines[this->CurrentBarLineId] -#define SET_LINE_LIST_TEXT nsBarLinesList->currentItem()->setText(GetBarLineDescription(CurrentBarLine)); - -void PreferencesWindow::on_nsBarPageAddBTN_clicked() -{ - QvBarPage page; - CurrentConfig.toolBarConfig.Pages.push_back(page); - CurrentBarPageId = CurrentConfig.toolBarConfig.Pages.size() - 1; - // Add default line. - QvBarLine line; - CurrentBarPage.Lines.push_back(line); - CurrentBarLineId = 0; - nsBarPagesList->addItem(QSTRN(CurrentBarPageId)); - ShowLineParameters(CurrentBarLine); - LOG(MODULE_UI, "Adding new page Id: " + QSTRN(CurrentBarPageId)) - nsBarPageDelBTN->setEnabled(true); - nsBarLineAddBTN->setEnabled(true); - nsBarLineDelBTN->setEnabled(true); - nsBarLinesList->setEnabled(true); - nsBarPageYOffset->setEnabled(true); - on_nsBarPagesList_currentRowChanged(static_cast(CurrentBarPageId)); - nsBarPagesList->setCurrentRow(static_cast(CurrentBarPageId)); -} - -void PreferencesWindow::on_nsBarPageDelBTN_clicked() -{ - if (nsBarPagesList->currentRow() >= 0) - { - CurrentConfig.toolBarConfig.Pages.removeAt(nsBarPagesList->currentRow()); - nsBarPagesList->takeItem(nsBarPagesList->currentRow()); - - if (nsBarPagesList->count() <= 0) - { - nsBarPageDelBTN->setEnabled(false); - nsBarLineAddBTN->setEnabled(false); - nsBarLineDelBTN->setEnabled(false); - nsBarLinesList->setEnabled(false); - networkToolbarSettingsFrame->setEnabled(false); - nsBarPageYOffset->setEnabled(false); - nsBarLinesList->clear(); - } - } -} - -void PreferencesWindow::on_nsBarPageYOffset_valueChanged(int arg1) -{ - LOADINGCHECK - CurrentBarPage.OffsetYpx = arg1; -} - -void PreferencesWindow::on_nsBarLineAddBTN_clicked() -{ - // WARNING Is it really just this simple? - QvBarLine line; - CurrentBarPage.Lines.push_back(line); - CurrentBarLineId = CurrentBarPage.Lines.size() - 1; - nsBarLinesList->addItem(QSTRN(CurrentBarLineId)); - ShowLineParameters(CurrentBarLine); - nsBarLineDelBTN->setEnabled(true); - LOG(MODULE_UI, "Adding new line Id: " + QSTRN(CurrentBarLineId)) - nsBarLinesList->setCurrentRow(static_cast(CurrentBarPage.Lines.size() - 1)); -} - -void PreferencesWindow::on_nsBarLineDelBTN_clicked() -{ - if (nsBarLinesList->currentRow() >= 0) - { - CurrentBarPage.Lines.removeAt(nsBarLinesList->currentRow()); - nsBarLinesList->takeItem(nsBarLinesList->currentRow()); - CurrentBarLineId = 0; - - if (nsBarLinesList->count() <= 0) - { - networkToolbarSettingsFrame->setEnabled(false); - nsBarLineDelBTN->setEnabled(false); - } - - // TODO Disabling some UI; - } -} - -void PreferencesWindow::on_nsBarPagesList_currentRowChanged(int currentRow) -{ - if (currentRow < 0) - return; - - // Change page. - // We reload the lines - // Set all parameters item to the property of the first line. - CurrentBarPageId = currentRow; - CurrentBarLineId = 0; - nsBarPageYOffset->setValue(CurrentBarPage.OffsetYpx); - nsBarLinesList->clear(); - - if (!CurrentBarPage.Lines.empty()) - { - for (auto line : CurrentBarPage.Lines) - { - auto description = GetBarLineDescription(line); - nsBarLinesList->addItem(description); - } - - nsBarLinesList->setCurrentRow(0); - ShowLineParameters(CurrentBarLine); - } - else - { - networkToolbarSettingsFrame->setEnabled(false); - } -} - -void PreferencesWindow::on_nsBarLinesList_currentRowChanged(int currentRow) -{ - if (currentRow < 0) - return; - - CurrentBarLineId = currentRow; - ShowLineParameters(CurrentBarLine); -} - -void PreferencesWindow::on_fontComboBox_currentFontChanged(const QFont &f) -{ - LOADINGCHECK - CurrentBarLine.Family = f.family(); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontBoldCB_stateChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.Bold = arg1 == Qt::Checked; - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontItalicCB_stateChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.Italic = arg1 == Qt::Checked; - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontASB_valueChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.ColorA = arg1; - ShowLineParameters(CurrentBarLine); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontRSB_valueChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.ColorR = arg1; - ShowLineParameters(CurrentBarLine); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontGSB_valueChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.ColorG = arg1; - ShowLineParameters(CurrentBarLine); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontBSB_valueChanged(int arg1) -{ - LOADINGCHECK - CurrentBarLine.ColorB = arg1; - ShowLineParameters(CurrentBarLine); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarFontSizeSB_valueChanged(double arg1) -{ - LOADINGCHECK - CurrentBarLine.Size = arg1; - SET_LINE_LIST_TEXT -} - -QString PreferencesWindow::GetBarLineDescription(const QvBarLine &barLine) -{ - QString result = "Empty"; - result = NetSpeedPluginMessages[barLine.ContentType]; - - if (barLine.ContentType == 0) - { - result += " (" + barLine.Message + ")"; - } - - result = result.append(barLine.Bold ? ", " + tr("Bold") : ""); - result = result.append(barLine.Italic ? ", " + tr("Italic") : ""); - return result; -} - -void PreferencesWindow::ShowLineParameters(QvBarLine &barLine) -{ - finishedLoading = false; - - if (!barLine.Family.isEmpty()) - { - fontComboBox->setCurrentFont(QFont(barLine.Family)); - } - - // Colors - nsBarFontASB->setValue(barLine.ColorA); - nsBarFontBSB->setValue(barLine.ColorB); - nsBarFontGSB->setValue(barLine.ColorG); - nsBarFontRSB->setValue(barLine.ColorR); - // - QColor color = QColor::fromRgb(barLine.ColorR, barLine.ColorG, barLine.ColorB, barLine.ColorA); - QString s(QStringLiteral("background: #") + ((color.red() < 16) ? "0" : "") + QString::number(color.red(), 16) + - ((color.green() < 16) ? "0" : "") + QString::number(color.green(), 16) + ((color.blue() < 16) ? "0" : "") + - QString::number(color.blue(), 16) + ";"); - chooseColorBtn->setStyleSheet(s); - nsBarFontSizeSB->setValue(barLine.Size); - nsBarFontBoldCB->setChecked(barLine.Bold); - nsBarFontItalicCB->setChecked(barLine.Italic); - nsBarContentCombo->setCurrentText(NetSpeedPluginMessages[barLine.ContentType]); - nsBarTagTxt->setText(barLine.Message); - finishedLoading = true; - networkToolbarSettingsFrame->setEnabled(true); -} - -void PreferencesWindow::on_chooseColorBtn_clicked() -{ - LOADINGCHECK - QColorDialog d(QColor::fromRgb(CurrentBarLine.ColorR, CurrentBarLine.ColorG, CurrentBarLine.ColorB, CurrentBarLine.ColorA), this); - d.exec(); - - if (d.result() == QDialog::DialogCode::Accepted) - { - d.selectedColor().getRgb(&CurrentBarLine.ColorR, &CurrentBarLine.ColorG, &CurrentBarLine.ColorB, &CurrentBarLine.ColorA); - ShowLineParameters(CurrentBarLine); - SET_LINE_LIST_TEXT - } -} - -void PreferencesWindow::on_nsBarTagTxt_textEdited(const QString &arg1) -{ - LOADINGCHECK - CurrentBarLine.Message = arg1; - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_nsBarContentCombo_currentIndexChanged(const QString &arg1) -{ - LOADINGCHECK - CurrentBarLine.ContentType = NetSpeedPluginMessages.key(arg1); - SET_LINE_LIST_TEXT -} - -void PreferencesWindow::on_applyNSBarSettingsBtn_clicked() -{ - if (QvMessageBoxAsk(this, tr("Apply network toolbar settings"), - tr("All other modified settings will be applied as well after this object.") + NEWLINE + - tr("Do you want to continue?")) == QMessageBox::Yes) - { - auto conf = GlobalConfig; - conf.toolBarConfig = CurrentConfig.toolBarConfig; - SaveGlobalSettings(conf); - } -} - void PreferencesWindow::on_themeCombo_currentTextChanged(const QString &arg1) { LOADINGCHECK @@ -953,16 +659,6 @@ void PreferencesWindow::on_darkThemeCB_stateChanged(int arg1) { LOADINGCHECK CurrentConfig.uiConfig.useDarkTheme = arg1 == Qt::Checked; -#if (QV2RAY_USE_BUILTIN_DARKTHEME) - themeCombo->setEnabled(arg1 != Qt::Checked); - - if (arg1 == Qt::Checked) - { - themeCombo->setCurrentIndex(QStyleFactory::keys().indexOf("Fusion")); - CurrentConfig.uiConfig.theme = "Fusion"; - } - -#endif } void PreferencesWindow::on_darkTrayCB_stateChanged(int arg1) @@ -975,13 +671,7 @@ void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1) { LOADINGCHECK NEEDRESTART - CurrentConfig.inboundConfig.setSystemProxy = arg1 == Qt::Checked; -} - -void PreferencesWindow::on_pushButton_clicked() -{ - LOADINGCHECK - QDesktopServices::openUrl(QUrl::fromUserInput(QV2RAY_RULES_DIR)); + CurrentConfig.inboundConfig.systemProxySettings.setSystemProxy = arg1 == Qt::Checked; } void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString &arg1) @@ -993,13 +683,12 @@ void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString } else { - auto groupId = ConnectionManager->GetGroupIdByDisplayName(arg1); - auto list = ConnectionManager->Connections(groupId); + auto list = ConnectionManager->Connections(GroupId(autoStartSubsCombo->currentData().toString())); autoStartConnCombo->clear(); - for (auto id : list) + for (const auto &id : list) { - autoStartConnCombo->addItem(GetDisplayName(id)); + autoStartConnCombo->addItem(GetDisplayName(id), id.toString()); } } } @@ -1015,9 +704,12 @@ void PreferencesWindow::on_autoStartConnCombo_currentIndexChanged(const QString { // Fully qualify the connection item. // Will not work when duplicated names are in the same group. - CurrentConfig.autoStartId = - ConnectionManager->GetConnectionIdByDisplayName(arg1, ConnectionManager->GetGroupIdByDisplayName(autoStartSubsCombo->currentText())) - .toString(); + CurrentConfig.autoStartId.groupId = GroupId(autoStartSubsCombo->currentData().toString()); + CurrentConfig.autoStartId.connectionId = ConnectionId(autoStartConnCombo->currentData().toString()); + //= autoStartConnCombo->currentData().toString(); + // ConnectionManager->GetConnectionIdByDisplayName(arg1, + // ConnectionManager->GetGroupIdByDisplayName(autoStartSubsCombo->currentText())) + // .toString(); } } @@ -1042,13 +734,13 @@ void PreferencesWindow::SetAutoStartButtonsState(bool isAutoStart) void PreferencesWindow::on_fpTypeCombo_currentIndexChanged(const QString &arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.type = arg1.toLower(); + CurrentConfig.defaultRouteConfig.forwardProxyConfig.type = arg1.toLower(); } void PreferencesWindow::on_fpAddressTx_textEdited(const QString &arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.serverAddress = arg1; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.serverAddress = arg1; if (IsValidIPAddress(arg1)) { @@ -1063,13 +755,13 @@ void PreferencesWindow::on_fpAddressTx_textEdited(const QString &arg1) void PreferencesWindow::on_spPortSB_valueChanged(int arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.port = arg1; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.port = arg1; } void PreferencesWindow::on_fpUseAuthCB_stateChanged(int arg1) { bool authEnabled = arg1 == Qt::Checked; - CurrentConfig.connectionConfig.forwardProxyConfig.useAuth = authEnabled; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.useAuth = authEnabled; fpUsernameTx->setEnabled(authEnabled); fpPasswordTx->setEnabled(authEnabled); } @@ -1077,19 +769,19 @@ void PreferencesWindow::on_fpUseAuthCB_stateChanged(int arg1) void PreferencesWindow::on_fpUsernameTx_textEdited(const QString &arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.username = arg1; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.username = arg1; } void PreferencesWindow::on_fpPasswordTx_textEdited(const QString &arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.password = arg1; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.password = arg1; } void PreferencesWindow::on_fpPortSB_valueChanged(int arg1) { LOADINGCHECK - CurrentConfig.connectionConfig.forwardProxyConfig.port = arg1; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.port = arg1; } void PreferencesWindow::on_checkVCoreSettings_clicked() @@ -1098,6 +790,35 @@ void PreferencesWindow::on_checkVCoreSettings_clicked() auto vAssetsPath = vCoreAssetsPathTxt->text(); QString result; + // prevent some bullshit situations. + if (const auto vCorePathSmallCased = vcorePath.toLower(); + vCorePathSmallCased.endsWith("qv2ray") || vCorePathSmallCased.endsWith("qv2ray.exe")) + { + const auto strWarnTitle = tr("Watch Out!"); + const auto strWarnContent = // + tr("You may be about to set V2Ray core incorrectly to Qv2ray itself, which is absolutely not correct.\r\n" + "This won't trigger a fork bomb, however, since Qv2ray works in singleton mode.\r\n" + "If your V2Ray core filename happened to be 'qv2ray'-something, you are totally free to ignore this warning."); + const auto answer = QMessageBox::warning(this, strWarnTitle, strWarnContent, // + QMessageBox::StandardButton::Abort | QMessageBox::StandardButton::Ignore, // + QMessageBox::StandardButton::Abort); + if (answer == QMessageBox::StandardButton::Abort) + return; + } + else if (vCorePathSmallCased.endsWith("v2ctl") || vCorePathSmallCased.endsWith("v2ctl.exe")) + { + const auto strWarnTitle = tr("Watch Out!"); + const auto strWarnContent = // + tr("You may be about to set V2Ray core incorrectly to V2Ray Control executable, which is absolutely not correct.\r\n" + "The filename of V2Ray core is usually 'v2ray' or 'v2ray.exe'. Make sure to choose it wisely.\r\n" + "If you insist to proceed, we're not providing with any support."); + const auto answer = QMessageBox::warning(this, strWarnTitle, strWarnContent, // + QMessageBox::StandardButton::Abort | QMessageBox::StandardButton::Ignore, // + QMessageBox::StandardButton::Abort); + if (answer == QMessageBox::StandardButton::Abort) + return; + } + if (!V2rayKernelInstance::ValidateKernel(vcorePath, vAssetsPath, &result)) { QvMessageBoxWarn(this, tr("V2ray Core Settings"), result); @@ -1128,7 +849,7 @@ void PreferencesWindow::on_fpGroupBox_clicked(bool checked) { LOADINGCHECK NEEDRESTART - CurrentConfig.connectionConfig.forwardProxyConfig.enableForwardProxy = checked; + CurrentConfig.defaultRouteConfig.forwardProxyConfig.enableForwardProxy = checked; } void PreferencesWindow::on_maxLogLinesSB_valueChanged(int arg1) @@ -1142,7 +863,15 @@ void PreferencesWindow::on_enableAPI_stateChanged(int arg1) { LOADINGCHECK NEEDRESTART - CurrentConfig.apiConfig.enableAPI = arg1 == Qt::Checked; + + CurrentConfig.kernelConfig.enableAPI = arg1 == Qt::Checked; + if (arg1 == Qt::Unchecked) + { + const auto msgAPIDisableTitle = tr("Disabling API Subsystem"); + const auto msgAPIDisableMsg = tr("Disabling API subsystem will also disable the statistics function of Qv2ray.") + NEWLINE + // + tr("Speed chart and traffic statistics will be disabled."); + QvMessageBoxWarn(this, msgAPIDisableTitle, msgAPIDisableMsg); + } } void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index) @@ -1155,12 +884,17 @@ void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index) void PreferencesWindow::on_pluginKernelV2rayIntegrationCB_stateChanged(int arg1) { LOADINGCHECK + if (KernelInstance->ActivePluginKernelsCount() > 0) + NEEDRESTART; CurrentConfig.pluginConfig.v2rayIntegration = arg1 == Qt::Checked; + pluginKernelPortAllocateCB->setEnabled(arg1 == Qt::Checked); } void PreferencesWindow::on_pluginKernelPortAllocateCB_valueChanged(int arg1) { LOADINGCHECK + if (KernelInstance->ActivePluginKernelsCount() > 0) + NEEDRESTART; CurrentConfig.pluginConfig.portAllocationStart = arg1; } @@ -1182,12 +916,6 @@ void PreferencesWindow::on_qvProxyPortCB_valueChanged(int arg1) CurrentConfig.networkConfig.port = arg1; } -void PreferencesWindow::on_qvNetworkUATxt_textEdited(const QString &arg1) -{ - LOADINGCHECK - CurrentConfig.networkConfig.userAgent = arg1; -} - void PreferencesWindow::on_setAllowInsecureCB_stateChanged(int arg1) { LOADINGCHECK @@ -1208,14 +936,14 @@ void PreferencesWindow::on_setTestLatenctCB_stateChanged(int arg1) CurrentConfig.advancedConfig.testLatencyPeriodcally = arg1 == Qt::Checked; } -void PreferencesWindow::on_setAllowInsecureCiphersCB_stateChanged(int arg1) +void PreferencesWindow::on_setSessionResumptionCB_stateChanged(int arg1) { LOADINGCHECK if (arg1 == Qt::Checked) { - QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("You will lose the advantage of TLS and make your connection under MITM attack.")); + QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("This will make your TLS fingerpring different from common golang programs.")); } - CurrentConfig.advancedConfig.setAllowInsecureCiphers = arg1 == Qt::Checked; + CurrentConfig.advancedConfig.setSessionResumption = arg1 == Qt::Checked; } void PreferencesWindow::on_quietModeCB_stateChanged(int arg1) @@ -1226,44 +954,70 @@ void PreferencesWindow::on_quietModeCB_stateChanged(int arg1) void PreferencesWindow::on_tproxGroupBox_toggled(bool arg1) { +#ifndef Q_OS_LINUX + Q_UNUSED(arg1) + // No such tProxy thing on Windows and macOS + QvMessageBoxWarn(this, tr("Preferences"), tr("tProxy is not supported on macOS and Windows")); + CurrentConfig.inboundConfig.useTPROXY = false; + tproxGroupBox->setChecked(false); +#else NEEDRESTART CurrentConfig.inboundConfig.useTPROXY = arg1; +#endif } void PreferencesWindow::on_tProxyPort_valueChanged(int arg1) { NEEDRESTART - CurrentConfig.inboundConfig.tproxy_port = arg1; + CurrentConfig.inboundConfig.tProxySettings.port = arg1; } void PreferencesWindow::on_tproxyEnableTCP_toggled(bool checked) { NEEDRESTART - CurrentConfig.inboundConfig.tproxy_use_tcp = checked; + CurrentConfig.inboundConfig.tProxySettings.hasTCP = checked; } void PreferencesWindow::on_tproxyEnableUDP_toggled(bool checked) { NEEDRESTART - CurrentConfig.inboundConfig.tproxy_use_udp = checked; -} - -void PreferencesWindow::on_tproxyFollowRedirect_toggled(bool checked) -{ - NEEDRESTART - CurrentConfig.inboundConfig.tproxy_followRedirect = checked; + CurrentConfig.inboundConfig.tProxySettings.hasUDP = checked; } void PreferencesWindow::on_tproxyMode_currentTextChanged(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.tproxy_mode = arg1; + CurrentConfig.inboundConfig.tProxySettings.mode = arg1; } void PreferencesWindow::on_tproxyListenAddr_textEdited(const QString &arg1) { NEEDRESTART - CurrentConfig.inboundConfig.tproxy_ip = arg1; + CurrentConfig.inboundConfig.tProxySettings.tProxyIP = arg1; + + if (arg1 == "" || IsIPv4Address(arg1)) + { + BLACK(tproxyListenAddr) + } + else + { + RED(tproxyListenAddr) + } +} + +void PreferencesWindow::on_tproxyListenV6Addr_textEdited(const QString &arg1) +{ + NEEDRESTART + CurrentConfig.inboundConfig.tProxySettings.tProxyV6IP = arg1; + + if (arg1 == "" || IsIPv6Address(arg1)) + { + BLACK(tproxyListenV6Addr) + } + else + { + RED(tproxyListenV6Addr) + } } void PreferencesWindow::on_jumpListCountSB_valueChanged(int arg1) @@ -1280,26 +1034,143 @@ void PreferencesWindow::on_outboundMark_valueChanged(int arg1) void PreferencesWindow::on_dnsIntercept_toggled(bool checked) { NEEDRESTART - CurrentConfig.inboundConfig.dnsIntercept = checked; + CurrentConfig.inboundConfig.tProxySettings.dnsIntercept = checked; } void PreferencesWindow::on_qvProxyCustomProxy_clicked() { - CurrentConfig.networkConfig.proxyType = Qv2rayNetworkConfig::QVPROXY_CUSTOM; + CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_CUSTOM; + SET_PROXY_UI_ENABLE(true); + qvProxyNoProxy->setChecked(false); + qvProxySystemProxy->setChecked(false); + qvProxyCustomProxy->setChecked(true); } void PreferencesWindow::on_qvProxySystemProxy_clicked() { - CurrentConfig.networkConfig.proxyType = Qv2rayNetworkConfig::QVPROXY_SYSTEM; + CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_SYSTEM; + SET_PROXY_UI_ENABLE(false); + qvProxyNoProxy->setChecked(false); + qvProxyCustomProxy->setChecked(false); + qvProxySystemProxy->setChecked(true); } void PreferencesWindow::on_qvProxyNoProxy_clicked() { - CurrentConfig.networkConfig.proxyType = Qv2rayNetworkConfig::QVPROXY_NONE; + CurrentConfig.networkConfig.proxyType = Qv2rayConfig_Network::QVPROXY_NONE; + SET_PROXY_UI_ENABLE(false); + qvProxySystemProxy->setChecked(false); + qvProxyCustomProxy->setChecked(false); + qvProxyNoProxy->setChecked(true); } void PreferencesWindow::on_DnsFreedomCb_stateChanged(int arg1) { NEEDRESTART - CurrentConfig.connectionConfig.v2rayFreedomDNS = arg1 == Qt::Checked; + CurrentConfig.defaultRouteConfig.connectionConfig.v2rayFreedomDNS = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_httpSniffingCB_stateChanged(int arg1) +{ + NEEDRESTART + CurrentConfig.inboundConfig.httpSettings.sniffing = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_socksSniffingCB_stateChanged(int arg1) +{ + NEEDRESTART + CurrentConfig.inboundConfig.socksSettings.sniffing = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_pushButton_clicked() +{ + const auto ntpTitle = tr("NTP Checker"); + const auto ntpHint = tr("Check date and time from server:"); + bool ok = false; + QString ntpServer = QInputDialog::getText(this, ntpTitle, ntpHint, QLineEdit::Normal, "202.118.1.46", &ok).trimmed(); + if (!ok) + return; + + auto client = new ntp::NtpClient(this); + connect(client, &ntp::NtpClient::replyReceived, [&](const QHostAddress &, quint16, const ntp::NtpReply &reply) { + const int offsetSecTotal = reply.localClockOffset() / 1000; + if (offsetSecTotal >= 90 || offsetSecTotal <= -90) + { + const auto inaccurateWarning = tr("Your time offset is %1 seconds, which is too high.") + NEWLINE + // + tr("Please synchronize your system to use V2Ray."); + QvMessageBoxWarn(this, tr("Time Inaccurate"), inaccurateWarning.arg(offsetSecTotal)); + } + else if (offsetSecTotal > 15 || offsetSecTotal < -15) + { + const auto smallErrorWarning = tr("Your time offset is %1 seconds, which is a little high.") + NEWLINE + // + tr("V2Ray may still work, but we suggest you synchronize your clock."); + QvMessageBoxInfo(this, tr("Time Somewhat Inaccurate"), smallErrorWarning.arg(offsetSecTotal)); + } + else + { + const auto accurateInfo = tr("Your time offset is %1 seconds, which looks good.") + NEWLINE + // + tr("V2Ray may not suffer from time inaccuracy."); + QvMessageBoxInfo(this, tr("Time Accurate"), accurateInfo.arg(offsetSecTotal)); + } + }); + + const auto hostInfo = QHostInfo::fromName(ntpServer); + if (hostInfo.error() == QHostInfo::NoError) + { + client->sendRequest(hostInfo.addresses().first(), 123); + } + else + { + QvMessageBoxWarn(this, ntpTitle, tr("Failed to lookup server: %1").arg(hostInfo.errorString())); + } +} + +void PreferencesWindow::on_noAutoConnectRB_clicked() +{ + LOADINGCHECK + CurrentConfig.autoStartBehavior = AUTO_CONNECTION_NONE; + SET_AUTOSTART_UI_ENABLED(false); +} + +void PreferencesWindow::on_lastConnectedRB_clicked() +{ + LOADINGCHECK + CurrentConfig.autoStartBehavior = AUTO_CONNECTION_LAST_CONNECTED; + SET_AUTOSTART_UI_ENABLED(false); +} + +void PreferencesWindow::on_fixedAutoConnectRB_clicked() +{ + LOADINGCHECK + CurrentConfig.autoStartBehavior = AUTO_CONNECTION_FIXED; + SET_AUTOSTART_UI_ENABLED(true); +} + +void PreferencesWindow::on_latencyTCPingRB_clicked() +{ + LOADINGCHECK + CurrentConfig.networkConfig.latencyTestingMethod = TCPING; + latencyICMPingRB->setChecked(false); + latencyTCPingRB->setChecked(true); +} + +void PreferencesWindow::on_latencyICMPingRB_clicked() +{ + LOADINGCHECK +#ifdef Q_OS_MAC + #warning No ICMPing support on macOS + CurrentConfig.networkConfig.latencyTestingMethod = TCPING; + latencyICMPingRB->setChecked(false); + latencyTCPingRB->setChecked(true); +#else + CurrentConfig.networkConfig.latencyTestingMethod = ICMPING; + latencyICMPingRB->setChecked(true); + latencyTCPingRB->setChecked(false); +#endif +} + +void PreferencesWindow::on_qvNetworkUATxt_editTextChanged(const QString &arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.userAgent = arg1; } diff --git a/src/ui/w_PreferencesWindow.hpp b/src/ui/windows/w_PreferencesWindow.hpp similarity index 72% rename from src/ui/w_PreferencesWindow.hpp rename to src/ui/windows/w_PreferencesWindow.hpp index 7f57a2bc..e6eea400 100644 --- a/src/ui/w_PreferencesWindow.hpp +++ b/src/ui/windows/w_PreferencesWindow.hpp @@ -1,15 +1,15 @@ -#pragma once +#pragma once #include "base/Qv2rayBase.hpp" +#include "ui/common/QvDialog.hpp" #include "ui/messaging/QvMessageBus.hpp" - -#include -#include +#include "ui_w_PreferencesWindow.h" class RouteSettingsMatrixWidget; +class DnsSettingsWidget; class PreferencesWindow - : public QDialog + : public QvDialog , private Ui::PreferencesWindow { Q_OBJECT @@ -17,205 +17,119 @@ class PreferencesWindow public: explicit PreferencesWindow(QWidget *parent = nullptr); ~PreferencesWindow(); + void processCommands(QString command, QStringList commands, QMap) override + { + const static QMap indexMap{ + { "general", 0 }, // + { "kernel", 1 }, // + { "inbound", 2 }, // + { "connection", 3 }, // + { "dns", 4 }, // + { "route", 5 }, // + { "about", 6 } // + }; + + if (commands.isEmpty()) + return; + if (command == "open") + { + const auto c = commands.takeFirst(); + tabWidget->setCurrentIndex(indexMap[c]); + } + } private: + void updateColorScheme() override{}; QvMessageBusSlotDecl; private slots: void on_buttonBox_accepted(); - void on_httpAuthCB_stateChanged(int arg1); - void on_socksAuthCB_stateChanged(int arg1); - void on_languageComboBox_currentTextChanged(const QString &arg1); - void on_logLevelComboBox_currentIndexChanged(int index); - void on_vCoreAssetsPathTxt_textEdited(const QString &arg1); - void on_listenIPTxt_textEdited(const QString &arg1); - void on_socksPortLE_valueChanged(int arg1); - void on_httpPortLE_valueChanged(int arg1); - void on_httpAuthUsernameTxt_textEdited(const QString &arg1); - void on_httpAuthPasswordTxt_textEdited(const QString &arg1); - void on_socksAuthUsernameTxt_textEdited(const QString &arg1); - void on_socksAuthPasswordTxt_textEdited(const QString &arg1); - void on_proxyDefaultCb_stateChanged(int arg1); - void on_localDNSCb_stateChanged(int arg1); - void on_selectVAssetBtn_clicked(); - - void on_DNSListTxt_textChanged(); - void on_aboutQt_clicked(); - void on_cancelIgnoreVersionBtn_clicked(); - void on_tProxyCheckBox_stateChanged(int arg1); - void on_bypassCNCb_stateChanged(int arg1); - + void on_bypassBTCb_stateChanged(int arg1); void on_statsPortBox_valueChanged(int arg1); - void on_socksUDPCB_stateChanged(int arg1); - void on_socksUDPIP_textEdited(const QString &arg1); - - void on_nsBarPageAddBTN_clicked(); - - void on_nsBarPageDelBTN_clicked(); - - void on_nsBarPageYOffset_valueChanged(int arg1); - - void on_nsBarLineAddBTN_clicked(); - - void on_nsBarLineDelBTN_clicked(); - - void on_nsBarPagesList_currentRowChanged(int currentRow); - - void on_nsBarLinesList_currentRowChanged(int currentRow); - - void on_fontComboBox_currentFontChanged(const QFont &f); - - void on_nsBarFontBoldCB_stateChanged(int arg1); - - void on_nsBarFontItalicCB_stateChanged(int arg1); - - void on_nsBarFontASB_valueChanged(int arg1); - - void on_nsBarFontRSB_valueChanged(int arg1); - - void on_nsBarFontGSB_valueChanged(int arg1); - - void on_nsBarFontBSB_valueChanged(int arg1); - - void on_nsBarFontSizeSB_valueChanged(double arg1); - - void on_chooseColorBtn_clicked(); - - void on_nsBarTagTxt_textEdited(const QString &arg1); - - void on_nsBarContentCombo_currentIndexChanged(const QString &arg1); - - void on_applyNSBarSettingsBtn_clicked(); - void on_selectVCoreBtn_clicked(); - void on_vCorePathTxt_textEdited(const QString &arg1); - void on_themeCombo_currentTextChanged(const QString &arg1); - void on_darkThemeCB_stateChanged(int arg1); - void on_darkTrayCB_stateChanged(int arg1); - void on_setSysProxyCB_stateChanged(int arg1); - - void on_pushButton_clicked(); - void on_autoStartSubsCombo_currentIndexChanged(const QString &arg1); - void on_autoStartConnCombo_currentIndexChanged(const QString &arg1); - void on_fpTypeCombo_currentIndexChanged(const QString &arg1); - void on_fpAddressTx_textEdited(const QString &arg1); - void on_spPortSB_valueChanged(int arg1); - void on_fpUseAuthCB_stateChanged(int arg1); - void on_fpUsernameTx_textEdited(const QString &arg1); - void on_fpPasswordTx_textEdited(const QString &arg1); - void on_fpPortSB_valueChanged(int arg1); - void on_checkVCoreSettings_clicked(); - void on_httpGroupBox_clicked(bool checked); - void on_socksGroupBox_clicked(bool checked); - void on_fpGroupBox_clicked(bool checked); - void on_maxLogLinesSB_valueChanged(int arg1); - void on_enableAPI_stateChanged(int arg1); - void on_startWithLoginCB_stateChanged(int arg1); - void on_updateChannelCombo_currentIndexChanged(int index); - void on_pluginKernelV2rayIntegrationCB_stateChanged(int arg1); - void on_pluginKernelPortAllocateCB_valueChanged(int arg1); - void on_qvProxyAddressTxt_textEdited(const QString &arg1); - void on_qvProxyTypeCombo_currentTextChanged(const QString &arg1); - void on_qvProxyPortCB_valueChanged(int arg1); - - void on_qvNetworkUATxt_textEdited(const QString &arg1); - void on_setAllowInsecureCB_stateChanged(int arg1); - void on_setTestLatenctCB_stateChanged(int arg1); - - void on_setAllowInsecureCiphersCB_stateChanged(int arg1); - + void on_setSessionResumptionCB_stateChanged(int arg1); void on_quietModeCB_stateChanged(int arg1); - void on_tproxGroupBox_toggled(bool arg1); - void on_tProxyPort_valueChanged(int arg1); - void on_tproxyEnableTCP_toggled(bool checked); - void on_tproxyEnableUDP_toggled(bool checked); - - void on_tproxyFollowRedirect_toggled(bool checked); - void on_tproxyMode_currentTextChanged(const QString &arg1); - void on_tproxyListenAddr_textEdited(const QString &arg1); - + void on_tproxyListenV6Addr_textEdited(const QString &arg1); void on_jumpListCountSB_valueChanged(int arg1); - void on_outboundMark_valueChanged(int arg1); - void on_dnsIntercept_toggled(bool checked); - void on_qvProxyCustomProxy_clicked(); - void on_qvProxySystemProxy_clicked(); - void on_qvProxyNoProxy_clicked(); - void on_DnsFreedomCb_stateChanged(int arg1); + void on_httpSniffingCB_stateChanged(int arg1); + void on_socksSniffingCB_stateChanged(int arg1); + void on_pushButton_clicked(); + void on_noAutoConnectRB_clicked(); + void on_lastConnectedRB_clicked(); + void on_fixedAutoConnectRB_clicked(); + void on_latencyTCPingRB_clicked(); + void on_latencyICMPingRB_clicked(); + void on_qvNetworkUATxt_editTextChanged(const QString &arg1); private: // + DnsSettingsWidget *dnsSettingsWidget; RouteSettingsMatrixWidget *routeSettingsWidget; void SetAutoStartButtonsState(bool isAutoStart); - // Set ui parameters for a line; - void ShowLineParameters(QvBarLine &line); - QString GetBarLineDescription(const QvBarLine &barLine); - // - int CurrentBarLineId; - int CurrentBarPageId; // bool NeedRestart = false; bool finishedLoading = false; - Qv2rayConfig CurrentConfig; + Qv2rayConfigObject CurrentConfig; }; diff --git a/src/ui/w_PreferencesWindow.ui b/src/ui/windows/w_PreferencesWindow.ui similarity index 70% rename from src/ui/w_PreferencesWindow.ui rename to src/ui/windows/w_PreferencesWindow.ui index 0e202e08..d68ba2ec 100644 --- a/src/ui/w_PreferencesWindow.ui +++ b/src/ui/windows/w_PreferencesWindow.ui @@ -9,8 +9,8 @@ 0 0 - 864 - 614 + 867 + 624 @@ -35,7 +35,7 @@ 0 - + General Settings @@ -53,6 +53,9 @@ Darkmode UI Icons + + Qt::PlainText + @@ -67,6 +70,9 @@ Darkmode Tray Icon + + Qt::PlainText + @@ -81,6 +87,9 @@ UI Theme + + Qt::PlainText + @@ -98,6 +107,9 @@ Language + + Qt::PlainText + @@ -121,6 +133,9 @@ Maximum log lines + + Qt::PlainText + @@ -153,6 +168,9 @@ Recent Jumplist + + Qt::PlainText + @@ -179,12 +197,15 @@ Behavior - + Launch at Login + + Qt::PlainText + @@ -199,6 +220,9 @@ Transparent Proxy + + Qt::PlainText + @@ -208,6 +232,16 @@ + + + + Quiet Mode + + + Qt::PlainText + + + @@ -220,21 +254,27 @@ Auto Connect + + Qt::PlainText + - + - + Config + + Qt::PlainText + - + @@ -242,29 +282,45 @@ 0 - - - - - - + Group + + Qt::PlainText + + + + + + + None + + + + + + + Last Connected + + + + + + + Fixed + + + + + - - - - Quiet Mode - - - @@ -274,108 +330,207 @@ Network Settings - + + + + + true + + + + These settings are used by Qv2ray itself. +For example, for updating subscriptions. + + + Qt::PlainText + + + + User-Agent - - - - - - + + Qt::PlainText + + + + + 0 + 0 + + + + true + + + Qv2ray/$VERSION WebRequestHelper + + + + Qv2ray/$VERSION WebRequestHelper + + + + + Mozilla/5.0 (X11; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0 + + + + + + + + + - + - Qv2ray Proxy + Latency Test Method + + + Qt::PlainText - + - + - No Proxy + TCPing + + + false - + - System Proxy + ICMPing - - - - - - Custom Proxy + + false - - - - Curtom Proxy - - - - - - - - http - - - - - socks - - - - - + - Curtom Proxy + Qv2ray Proxy + + + Qt::PlainText - - - + + + + + + + None + + + false + + + + + + + System Proxy + + + false + + + + + + + Custom Proxy + + + false + + + + - - + + - : + Type - - Qt::AlignCenter + + Qt::PlainText - - - - QAbstractSpinBox::NoButtons + + + + + http + + + + + socks + + + + + + + + Server - - 0 - - - 65535 + + Qt::PlainText + + + + + + + + + : + + + Qt::AlignCenter + + + + + + + QAbstractSpinBox::NoButtons + + + 0 + + + 65535 + + + + + @@ -390,7 +545,10 @@ - AllowInsecure By Default + Set AllowInsecure By Default + + + Qt::PlainText @@ -410,6 +568,9 @@ This could resolve the certificate issues, but also could let one performing TLS Test Latency Periodcally + + Qt::PlainText + @@ -439,17 +600,23 @@ Qv2ray will give a more accurate latency value if Enabled, but makes it easy to These settings may be useful. But could damage your server if improperly used. + + Qt::PlainText + - AllowInsecureCiphers By Default + Enable SessionResumption By Default + + + Qt::PlainText - + Enabled @@ -475,7 +642,7 @@ But could damage your server if improperly used. - + Kernel Settings @@ -491,6 +658,9 @@ But could damage your server if improperly used. Log Level + + Qt::PlainText + @@ -539,6 +709,9 @@ But could damage your server if improperly used. Core Executable Path + + Qt::PlainText + @@ -564,6 +737,9 @@ But could damage your server if improperly used. V2ray Assets Directory + + Qt::PlainText + @@ -585,6 +761,9 @@ But could damage your server if improperly used. API SubSystem + + Qt::PlainText + @@ -599,6 +778,9 @@ But could damage your server if improperly used. API Port + + Qt::PlainText + @@ -627,6 +809,23 @@ But could damage your server if improperly used. + + + + Check Date and Time from Network + + + + + + + NTP + + + Qt::PlainText + + + @@ -641,6 +840,9 @@ But could damage your server if improperly used. Enabling V2ray Integration will allow the kernel benefit from the V2ray routing engine. + + Qt::PlainText + @@ -648,6 +850,9 @@ But could damage your server if improperly used. V2ray Integration + + Qt::PlainText + @@ -670,6 +875,9 @@ Custom DNS Settings Qv2ray will allocate ports, for HTTP and SOCKS respectively, if enabled, for each kernel plugin. + + Qt::PlainText + @@ -677,6 +885,9 @@ Custom DNS Settings Port Allocation Start + + Qt::PlainText + @@ -710,7 +921,7 @@ Custom DNS Settings - + Inbound Settings @@ -728,8 +939,8 @@ Custom DNS Settings 0 0 - 818 - 520 + 821 + 594 @@ -740,12 +951,15 @@ Custom DNS Settings Listening Address + + Qt::PlainText + - 'localhost', or IPv4 address, e.g. '127.0.0.1' or IPv6 address with brackets, e.g. [::1] + IPv4 address, e.g. '127.0.0.1' or IPv6 address, e.g. ::1 @@ -754,6 +968,9 @@ Custom DNS Settings Set System Proxy + + Qt::PlainText + @@ -784,6 +1001,9 @@ Custom DNS Settings Port + + Qt::PlainText + @@ -804,6 +1024,9 @@ Custom DNS Settings UDP Support + + Qt::PlainText + @@ -818,6 +1041,9 @@ Custom DNS Settings UDP Local IP + + Qt::PlainText + @@ -832,6 +1058,9 @@ Custom DNS Settings Authentication + + Qt::PlainText + @@ -841,34 +1070,57 @@ Custom DNS Settings - + Username + + Qt::PlainText + - + user - + Password + + Qt::PlainText + - + pass + + + + Enabled + + + + + + + Sniffing + + + Qt::PlainText + + + @@ -889,6 +1141,9 @@ Custom DNS Settings Port + + Qt::PlainText + @@ -909,6 +1164,9 @@ Custom DNS Settings Authentication + + Qt::PlainText + @@ -923,6 +1181,9 @@ Custom DNS Settings Username + + Qt::PlainText + @@ -937,6 +1198,9 @@ Custom DNS Settings Password + + Qt::PlainText + @@ -946,6 +1210,23 @@ Custom DNS Settings + + + + Enabled + + + + + + + Sniffing + + + Qt::PlainText + + + @@ -961,19 +1242,12 @@ Custom DNS Settings false - - - - Listening Address - - - - 127.0.0.1 + IPv4 address, e.g. '127.0.0.1' @@ -999,14 +1273,17 @@ Custom DNS Settings - + Network Options + + Qt::PlainText + - + @@ -1029,13 +1306,6 @@ Custom DNS Settings - - - - Follow Redirect - - - @@ -1058,14 +1328,17 @@ Custom DNS Settings - + Mode + + Qt::PlainText + - + @@ -1075,24 +1348,27 @@ Custom DNS Settings - redirect + redirect - tproxy + tproxy - + Outbound Mark + + Qt::PlainText + - + 0 @@ -1105,6 +1381,39 @@ Custom DNS Settings + + + + Listening IPv4 Address + + + Qt::PlainText + + + + + + + Listening IPv6 Address + + + Qt::PlainText + + + + + + + + + + + + + IPv6 address, e.g. ::1. Leave blank will disable ipv6. + + + @@ -1129,204 +1438,253 @@ Custom DNS Settings - + Connection Settings - - - - - - - General Connection Settings - - - - - - Enable Proxy - - - - - - - Enabled - - - - - - - Bypass CN Mainland - - - - - - - Enabled - - - - - - - Use V2ray Dns for Freedom Outbound - - - - - - - Enabled - - - - - - - Use Local DNS - - - - - - - Custom DNS List - - - - - - - - - - Enabled - - - - - - - + + + + + General Connection Settings + + + + + + Enable Proxy + + + Qt::PlainText + + + + + + + Enabled + + + + + + + Bypass CN Mainland + + + Qt::PlainText + + + + + + + Enabled + + + + + + + Bypass Bittorrent Protocol + + + Qt::PlainText + + + + + + + Enabled + + + + + + + Use V2ray DNS for Direct Connection + + + Qt::PlainText + + + + + + + Enabled + + + + + + + Use Local DNS + + + Qt::PlainText + + + + + + + Enabled + + + + + - - - - - - Forward Proxy - - - true - - - false - - - - - - Only simple config is supported. - - + + + + Forward Proxy + + + true + + + false + + + + + + Only simple config is supported. + + + Qt::PlainText + + + + + + + Type + + + Qt::PlainText + + + + + + + + HTTP + - - - - Type - - + + + Socks + - - - - - HTTP - - - - - Socks - - - - - - - - Host Address - - - - - - - - - - Port - - - - - - - 1 - - - 65535 - - - 8000 - - - - - - - Authentication - - - - - - - Enabled - - - - - - - Username - - - - - - - - - - Password - - - - - - - - - - + + + + + + Host Address + + + Qt::PlainText + + + + + + + + + + Port + + + Qt::PlainText + + + + + + + 1 + + + 65535 + + + 8000 + + + + + + + Authentication + + + Qt::PlainText + + + + + + + Enabled + + + + + + + Username + + + Qt::PlainText + + + + + + + + + + Password + + + Qt::PlainText + + + + + + + + + + DNS Settings + + + + + + You can configure default DNS settings for all simple connection config here. + + + + + + + + + Advanced Route Settings @@ -1337,396 +1695,16 @@ Custom DNS Settings - You can configure route rules for all simple connection config here. + You can configure default routing rules for all simple connection config here. + + + Qt::PlainText - - - true - - - Network Toolbar Settings - - - - - - You can config how the network speed toolbar looks like in this panel - - - - - - - Items - - - - - - - 0 - 0 - - - - - 26 - 26 - - - - - - - - - - - - - 0 - 0 - - - - - 26 - 26 - - - - + - - - - - - - Page Y Axis Offset - - - - - - - Pages - - - - - - - Lines - - - - - - - 1024 - - - - - - - Qt::Vertical - - - - 20 - 38 - - - - - - - - - 0 - 0 - - - - - 26 - 26 - - - - - - - - - - - - - 0 - 0 - - - - - 26 - 26 - - - - + - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - - - - - - - - - - true - - - - 75 - true - true - - - - color: red - - - This feature is not stable and no documentation is provided, please use it at your own risk! - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Content - - - - - - Content Type - - - - - - - - - - Text/Tag - - - - - - - - - - - - - - 0 - 0 - - - - Text Style - - - - - - Font - - - - - - - - - - - - Bold - - - - - - - Italic - - - - - - - - - Size - - - - - - - - - - Color - - - - - - - - - A: - - - - - - - G: - - - - - - - 255 - - - - - - - 255 - - - - - - - 255 - - - - - - - 255 - - - - - - - R: - - - - - - - Select Color - - - - - - - B: - - - - - - - - - Style - - - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - Apply Network Speed Bar UI Settings - - - - - - - - - + About @@ -1773,6 +1751,9 @@ Custom DNS Settings Ignored Version + + Qt::PlainText + @@ -1794,6 +1775,9 @@ Custom DNS Settings Update Channel + + Qt::PlainText + @@ -1827,6 +1811,9 @@ Custom DNS Settings Qv2ray + + Qt::PlainText + @@ -1841,6 +1828,9 @@ Custom DNS Settings Version: + + Qt::PlainText + @@ -1853,6 +1843,9 @@ Custom DNS Settings IBeamCursor + + Qt::PlainText + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse @@ -1880,6 +1873,9 @@ Custom DNS Settings Plugin Interface + + Qt::PlainText + @@ -1891,10 +1887,10 @@ Custom DNS Settings - <html><head/><body><p><a href="https://github.com/Qv2ray/Qv2ray"><span style="text-decoration: underline; color:#2980b9;">https://github.com/Qv2ray/Qv2ray</span></a></p></body></html> + https://github.com/Qv2ray/Qv2ray - Qt::RichText + Qt::PlainText false @@ -1902,6 +1898,9 @@ Custom DNS Settings true + + Qt::LinksAccessibleByMouse + @@ -1909,6 +1908,9 @@ Custom DNS Settings Extra Build Info + + Qt::PlainText + @@ -1916,6 +1918,9 @@ Custom DNS Settings + + Qt::PlainText + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse @@ -1926,6 +1931,9 @@ Custom DNS Settings + + Qt::PlainText + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse @@ -1936,6 +1944,9 @@ Custom DNS Settings + + Qt::PlainText + @@ -1948,14 +1959,14 @@ Custom DNS Settings - 10 + 9 - <html><head/><body><p><a href="https://www.gnu.org/licenses/gpl-3.0.txt"><span style="text-decoration: underline; color:#2980b9;">GPLv3 (https://www.gnu.org/licenses/gpl-3.0.txt)</span></a></p></body></html> + GPLv3 (https://www.gnu.org/licenses/gpl-3.0.txt) - Qt::RichText + Qt::PlainText false @@ -1970,6 +1981,9 @@ Custom DNS Settings Built Time + + Qt::PlainText + @@ -1977,6 +1991,9 @@ Custom DNS Settings Build Info + + Qt::PlainText + @@ -1984,6 +2001,9 @@ Custom DNS Settings Official Repo + + Qt::PlainText + @@ -1991,6 +2011,9 @@ Custom DNS Settings + + Qt::PlainText + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse @@ -2001,6 +2024,9 @@ Custom DNS Settings License + + Qt::PlainText + @@ -2066,29 +2092,12 @@ Custom DNS Settings httpAuthPasswordTxt proxyDefaultCb bypassCNCb - DNSListTxt - fpGroupBox fpTypeCombo fpAddressTx fpPortSB fpUseAuthCB fpUsernameTx fpPasswordTx - nsBarPagesList - nsBarPageAddBTN - nsBarPageDelBTN - nsBarPageYOffset - nsBarLinesList - nsBarLineAddBTN - nsBarLineDelBTN - nsBarContentCombo - nsBarTagTxt - fontComboBox - nsBarFontBoldCB - nsBarFontItalicCB - nsBarFontSizeSB - nsBarFontASB - applyNSBarSettingsBtn diff --git a/src/ui/w_ScreenShot_Core.cpp b/src/ui/windows/w_ScreenShot_Core.cpp similarity index 79% rename from src/ui/w_ScreenShot_Core.cpp rename to src/ui/windows/w_ScreenShot_Core.cpp index 4f1ecb41..0df5e7d1 100644 --- a/src/ui/w_ScreenShot_Core.cpp +++ b/src/ui/windows/w_ScreenShot_Core.cpp @@ -1,5 +1,6 @@ #include "w_ScreenShot_Core.hpp" +#include "base/Qv2rayBase.hpp" #include "common/QvHelpers.hpp" #include @@ -8,11 +9,10 @@ #define QV2RAY_SCREENSHOT_DIM_RATIO 0.6f -ScreenShotWindow::ScreenShotWindow() : QDialog(), rubber(new QRubberBand(QRubberBand::Rectangle, this)) +ScreenShotWindow::ScreenShotWindow() : QDialog(), rubber(QRubberBand::Rectangle, this) { setupUi(this); - // Fusion prevents the KDE Plasma Breeze's "Move window when dragging in the - // empty area" issue + // Fusion prevents the KDE Plasma Breeze's "Move window when dragging in the empty area" issue this->setStyle(QStyleFactory::create("Fusion")); // label->setAttribute(Qt::WA_TranslucentBackground); @@ -33,22 +33,21 @@ ScreenShotWindow::ScreenShotWindow() : QDialog(), rubber(new QRubberBand(QRubber QImage ScreenShotWindow::DoScreenShot() { LOG(MODULE_IMPORT, "We currently only support the current screen.") - // The msleep is the only solution which prevent capturing our windows - // again. It works on KDE, + // The msleep is the only solution which prevent capturing our windows again. It works on KDE, // https://www.qtcentre.org/threads/55708-Get-Desktop-Screenshot-Without-Application-Window-Being-Shown?p=248993#post248993 QThread::msleep(100); QApplication::processEvents(); // - auto pos = QCursor::pos(); - desktopImage = QGuiApplication::screenAt(pos)->grabWindow(0); + desktopImage = qApp->screenAt(QCursor::pos())->grabWindow(0); + scale = qApp->screenAt(QCursor::pos())->devicePixelRatio(); // int w = desktopImage.width(); int h = desktopImage.height(); - QImage bg_grey(w, h, QImage::Format_RGB32); // int r, g, b; - auto _xdesktopImg = desktopImage.toImage(); + const auto _xdesktopImg = desktopImage.toImage(); + QImage bg_grey(w, h, QImage::Format_RGB32); for (int i = 0; i < w; i++) { for (int j = 0; j < h; j++) @@ -59,8 +58,8 @@ QImage ScreenShotWindow::DoScreenShot() bg_grey.setPixel(i, j, qRgb(r, g, b)); } } - - bg_grey = bg_grey.scaled(bg_grey.size() / devicePixelRatio(), Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation); + setStyleSheet("QDialog { background-color: transparent; }"); + bg_grey = bg_grey.scaled(bg_grey.size() / scale, Qt::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation); auto p = this->palette(); p.setBrush(QPalette::Window, bg_grey); setPalette(p); @@ -78,12 +77,9 @@ void ScreenShotWindow::pSize() imgH = abs(end.y() - origin.y()); imgX = origin.x() < end.x() ? origin.x() : end.x(); imgY = origin.y() < end.y() ? origin.y() : end.y(); - DEBUG("Capture Mouse Position", QSTRN(imgW) + " " + QSTRN(imgH) + " " + QSTRN(imgX) + " " + QSTRN(imgY)) - rubber->setGeometry(imgX, imgY, imgW, imgH); - fg->setGeometry(rubber->geometry()); - auto copied = desktopImage.copy(fg->x() * devicePixelRatio(), fg->y() * devicePixelRatio(), fg->width() * devicePixelRatio(), - fg->height() * devicePixelRatio()); - fg->setPixmap(copied); + rubber.setGeometry(imgX, imgY, imgW, imgH); + fg->setGeometry(rubber.geometry()); + fg->setPixmap(desktopImage.copy(imgX * scale, imgY * scale, imgW * scale, imgH * scale)); } bool ScreenShotWindow::event(QEvent *e) @@ -111,9 +107,9 @@ void ScreenShotWindow::keyPressEvent(QKeyEvent *e) void ScreenShotWindow::mousePressEvent(QMouseEvent *e) { origin = e->pos(); - rubber->setGeometry(origin.x(), origin.y(), 0, 0); - rubber->show(); - rubber->raise(); + rubber.setGeometry(origin.x(), origin.y(), 0, 0); + rubber.show(); + rubber.raise(); // label->hide(); // startBtn->hide(); } @@ -128,7 +124,6 @@ void ScreenShotWindow::mouseMoveEvent(QMouseEvent *e) label->setText(QString("%1x%2").arg(imgW).arg(imgH)); label->adjustSize(); // - // QRect labelRect(label->contentsRect()); QRect btnRect(startBtn->contentsRect()); @@ -161,6 +156,10 @@ void ScreenShotWindow::mouseReleaseEvent(QMouseEvent *e) { reject(); } + else if (e->button() == Qt::LeftButton) + { + rubber.hide(); + } } ScreenShotWindow::~ScreenShotWindow() @@ -170,6 +169,6 @@ ScreenShotWindow::~ScreenShotWindow() void ScreenShotWindow::on_startBtn_clicked() { resultImage = desktopImage.copy(imgX, imgY, imgW, imgH).toImage(); - rubber->hide(); + rubber.hide(); accept(); } diff --git a/src/ui/w_ScreenShot_Core.hpp b/src/ui/windows/w_ScreenShot_Core.hpp similarity index 95% rename from src/ui/w_ScreenShot_Core.hpp rename to src/ui/windows/w_ScreenShot_Core.hpp index b74da91b..366e51c6 100644 --- a/src/ui/w_ScreenShot_Core.hpp +++ b/src/ui/windows/w_ScreenShot_Core.hpp @@ -34,7 +34,8 @@ class ScreenShotWindow void on_startBtn_clicked(); private: - QRubberBand *rubber; + double scale; + QRubberBand rubber; // Desktop Image QPixmap desktopImage; QImage windowBg; diff --git a/src/ui/w_ScreenShot_Core.ui b/src/ui/windows/w_ScreenShot_Core.ui similarity index 100% rename from src/ui/w_ScreenShot_Core.ui rename to src/ui/windows/w_ScreenShot_Core.ui diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 00000000..691a6808 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,19 @@ +function(ADD_QV2RAY_TEST TEST_NAME TEST_SOURCE) + add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp) + target_include_directories(${TEST_NAME} + PRIVATE + $ + ) + target_link_libraries( + ${TEST_NAME} + PRIVATE + $<$:qv2ray-baselib> + ) + +add_test(NAME QV2RAY_TEST_${TEST_NAME} COMMAND $) +endfunction() + +ADD_QV2RAY_TEST(parse_ss_url src/core/connection/TestParseSS.cpp) +ADD_QV2RAY_TEST(parse_vmess_url src/core/connection/TestParseVmess.cpp) +ADD_QV2RAY_TEST(generation src/core/connection/TestGeneration.cpp) +ADD_QV2RAY_TEST(qjsonio libs/QJsonStruct/QJsonIO.cpp) diff --git a/test/catch.hpp b/test/catch.hpp new file mode 100644 index 00000000..6beb0ead --- /dev/null +++ b/test/catch.hpp @@ -0,0 +1,17698 @@ +/* + * Catch v2.12.1 + * Generated: 2020-04-21 19:29:20.964532 + * ---------------------------------------------------------- + * This file has been merged from multiple headers. Please don't edit it directly + * Copyright (c) 2020 Two Blue Cubes Ltd. All rights reserved. + * + * Distributed under the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + */ +#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED +// start catch.hpp + + +#define CATCH_VERSION_MAJOR 2 +#define CATCH_VERSION_MINOR 12 +#define CATCH_VERSION_PATCH 1 + +#ifdef __clang__ +# pragma clang system_header +#elif defined __GNUC__ +# pragma GCC system_header +#endif + +// start catch_suppress_warnings.h + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(push) +# pragma warning(disable: 161 1682) +# else // __ICC +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wpadded" +# pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +# endif +#elif defined __GNUC__ + // Because REQUIREs trigger GCC's -Wparentheses, and because still + // supported version of g++ have only buggy support for _Pragmas, + // Wparentheses have to be suppressed globally. +# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details + +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-variable" +# pragma GCC diagnostic ignored "-Wpadded" +#endif +// end catch_suppress_warnings.h +#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) +# define CATCH_IMPL +# define CATCH_CONFIG_ALL_PARTS +#endif + +// In the impl file, we want to have access to all parts of the headers +// Can also be used to sanely support PCHs +#if defined(CATCH_CONFIG_ALL_PARTS) +# define CATCH_CONFIG_EXTERNAL_INTERFACES +# if defined(CATCH_CONFIG_DISABLE_MATCHERS) +# undef CATCH_CONFIG_DISABLE_MATCHERS +# endif +# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) +# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER +# endif +#endif + +#if !defined(CATCH_CONFIG_IMPL_ONLY) +// start catch_platform.h + +#ifdef __APPLE__ +# include +# if TARGET_OS_OSX == 1 +# define CATCH_PLATFORM_MAC +# elif TARGET_OS_IPHONE == 1 +# define CATCH_PLATFORM_IPHONE +# endif + +#elif defined(linux) || defined(__linux) || defined(__linux__) +# define CATCH_PLATFORM_LINUX + +#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) +# define CATCH_PLATFORM_WINDOWS +#endif + +// end catch_platform.h + +#ifdef CATCH_IMPL +# ifndef CLARA_CONFIG_MAIN +# define CLARA_CONFIG_MAIN_NOT_DEFINED +# define CLARA_CONFIG_MAIN +# endif +#endif + +// start catch_user_interfaces.h + +namespace Catch { + unsigned int rngSeed(); +} + +// end catch_user_interfaces.h +// start catch_tag_alias_autoregistrar.h + +// start catch_common.h + +// start catch_compiler_capabilities.h + +// Detect a number of compiler features - by compiler +// The following features are defined: +// +// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? +// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? +// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? +// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + +// In general each macro has a _NO_ form +// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. +// Many features, at point of detection, define an _INTERNAL_ macro, so they +// can be combined, en-mass, with the _NO_ forms later. + +#ifdef __cplusplus + +# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) +# define CATCH_CPP14_OR_GREATER +# endif + +# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define CATCH_CPP17_OR_GREATER +# endif + +#endif + +#if defined(__cpp_lib_uncaught_exceptions) +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +// We have to avoid both ICC and Clang, because they try to mask themselves +// as gcc, and we want only GCC in this block +#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) + +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) + +#endif + +#if defined(__clang__) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) + +// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug +// which results in calls to destructors being emitted for each temporary, +// without a matching initialization. In practice, this can result in something +// like `std::string::~string` being called on an uninitialized value. +// +// For example, this code will likely segfault under IBM XL: +// ``` +// REQUIRE(std::string("12") + "34" == "1234") +// ``` +// +// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. +# if !defined(__ibmxl__) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg) */ +# endif + +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ + _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") + +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) + +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) + +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ + _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) + +#endif // __clang__ + +//////////////////////////////////////////////////////////////////////////////// +// Assume that non-Windows platforms support posix signals by default +#if !defined(CATCH_PLATFORM_WINDOWS) + #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS +#endif + +//////////////////////////////////////////////////////////////////////////////// +// We know some environments not to support full POSIX signals +#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) + #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +#endif + +#ifdef __OS400__ +# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS +# define CATCH_CONFIG_COLOUR_NONE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Android somehow still does not support std::to_string +#if defined(__ANDROID__) +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING +# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Not all Windows environments support SEH properly +#if defined(__MINGW32__) +# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH +#endif + +//////////////////////////////////////////////////////////////////////////////// +// PS4 +#if defined(__ORBIS__) +# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE +#endif + +//////////////////////////////////////////////////////////////////////////////// +// Cygwin +#ifdef __CYGWIN__ + +// Required for some versions of Cygwin to declare gettimeofday +// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin +# define _BSD_SOURCE +// some versions of cygwin (most) do not support std::to_string. Use the libstd check. +// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 +# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ + && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) + +# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING + +# endif +#endif // __CYGWIN__ + +//////////////////////////////////////////////////////////////////////////////// +// Visual C++ +#if defined(_MSC_VER) + +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) + +# if _MSC_VER >= 1900 // Visual Studio 2015 or newer +# define CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +# endif + +// Universal Windows platform does not support SEH +// Or console colours (or console at all...) +# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +# define CATCH_CONFIG_COLOUR_NONE +# else +# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH +# endif + +// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ +// _MSVC_TRADITIONAL == 0 means new conformant preprocessor +// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor +# if !defined(__clang__) // Handle Clang masquerading for msvc +# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) +# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +# endif // MSVC_TRADITIONAL +# endif // __clang__ + +#endif // _MSC_VER + +#if defined(_REENTRANT) || defined(_MSC_VER) +// Enable async processing, as -pthread is specified or no additional linking is required +# define CATCH_INTERNAL_CONFIG_USE_ASYNC +#endif // _MSC_VER + +//////////////////////////////////////////////////////////////////////////////// +// Check if we are compiled with -fno-exceptions or equivalent +#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) +# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED +#endif + +//////////////////////////////////////////////////////////////////////////////// +// DJGPP +#ifdef __DJGPP__ +# define CATCH_INTERNAL_CONFIG_NO_WCHAR +#endif // __DJGPP__ + +//////////////////////////////////////////////////////////////////////////////// +// Embarcadero C++Build +#if defined(__BORLANDC__) + #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// Use of __COUNTER__ is suppressed during code analysis in +// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly +// handled by it. +// Otherwise all supported compilers support COUNTER macro, +// but user still might want to turn it off +#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) + #define CATCH_INTERNAL_CONFIG_COUNTER +#endif + +//////////////////////////////////////////////////////////////////////////////// + +// RTX is a special version of Windows that is real time. +// This means that it is detected as Windows, but does not provide +// the same set of capabilities as real Windows does. +#if defined(UNDER_RTSS) || defined(RTX64_BUILD) + #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH + #define CATCH_INTERNAL_CONFIG_NO_ASYNC + #define CATCH_CONFIG_COLOUR_NONE +#endif + +#if !defined(_GLIBCXX_USE_C99_MATH_TR1) +#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Various stdlib support checks that require __has_include +#if defined(__has_include) + // Check if string_view is available and usable + #if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW + #endif + + // Check if optional is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if byte is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # define CATCH_INTERNAL_CONFIG_CPP17_BYTE + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) + + // Check if variant is available and usable + # if __has_include() && defined(CATCH_CPP17_OR_GREATER) + # if defined(__clang__) && (__clang_major__ < 8) + // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 + // fix should be in clang 8, workaround in libstdc++ 8.2 + # include + # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # define CATCH_CONFIG_NO_CPP17_VARIANT + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) + # else + # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT + # endif // defined(__clang__) && (__clang_major__ < 8) + # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) +#endif // defined(__has_include) + +#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) +# define CATCH_CONFIG_COUNTER +#endif +#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) +# define CATCH_CONFIG_WINDOWS_SEH +#endif +// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. +#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) +# define CATCH_CONFIG_POSIX_SIGNALS +#endif +// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. +#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) +# define CATCH_CONFIG_WCHAR +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) +# define CATCH_CONFIG_CPP11_TO_STRING +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) +# define CATCH_CONFIG_CPP17_OPTIONAL +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_NO_CPP17_UNCAUGHT_EXCEPTIONS) && !defined(CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS) +# define CATCH_CONFIG_CPP17_UNCAUGHT_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) +# define CATCH_CONFIG_CPP17_STRING_VIEW +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) +# define CATCH_CONFIG_CPP17_VARIANT +#endif + +#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) +# define CATCH_CONFIG_CPP17_BYTE +#endif + +#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) +# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) +# define CATCH_CONFIG_NEW_CAPTURE +#endif + +#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +# define CATCH_CONFIG_DISABLE_EXCEPTIONS +#endif + +#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) +# define CATCH_CONFIG_POLYFILL_ISNAN +#endif + +#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) +# define CATCH_CONFIG_USE_ASYNC +#endif + +#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) +# define CATCH_CONFIG_ANDROID_LOGWRITE +#endif + +#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) +# define CATCH_CONFIG_GLOBAL_NEXTAFTER +#endif + +// Even if we do not think the compiler has that warning, we still have +// to provide a macro that can be used by the code. +#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) +# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS +#endif +#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS +#endif + +// The goal of this macro is to avoid evaluation of the arguments, but +// still have the compiler warn on problems inside... +#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) +# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) +#endif + +#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#elif defined(__clang__) && (__clang_major__ < 5) +# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) +# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS +#endif + +#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) +#define CATCH_TRY if ((true)) +#define CATCH_CATCH_ALL if ((false)) +#define CATCH_CATCH_ANON(type) if ((false)) +#else +#define CATCH_TRY try +#define CATCH_CATCH_ALL catch (...) +#define CATCH_CATCH_ANON(type) catch (type) +#endif + +#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) +#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#endif + +// end catch_compiler_capabilities.h +#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line +#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) +#ifdef CATCH_CONFIG_COUNTER +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) +#else +# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) +#endif + +#include +#include +#include + +// We need a dummy global operator<< so we can bring it into Catch namespace later +struct Catch_global_namespace_dummy {}; +std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); + +namespace Catch { + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + + class NonCopyable { + NonCopyable( NonCopyable const& ) = delete; + NonCopyable( NonCopyable && ) = delete; + NonCopyable& operator = ( NonCopyable const& ) = delete; + NonCopyable& operator = ( NonCopyable && ) = delete; + + protected: + NonCopyable(); + virtual ~NonCopyable(); + }; + + struct SourceLineInfo { + + SourceLineInfo() = delete; + SourceLineInfo( char const* _file, std::size_t _line ) noexcept + : file( _file ), + line( _line ) + {} + + SourceLineInfo( SourceLineInfo const& other ) = default; + SourceLineInfo& operator = ( SourceLineInfo const& ) = default; + SourceLineInfo( SourceLineInfo&& ) noexcept = default; + SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; + + bool empty() const noexcept { return file[0] == '\0'; } + bool operator == ( SourceLineInfo const& other ) const noexcept; + bool operator < ( SourceLineInfo const& other ) const noexcept; + + char const* file; + std::size_t line; + }; + + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); + + // Bring in operator<< from global namespace into Catch namespace + // This is necessary because the overload of operator<< above makes + // lookup stop at namespace Catch + using ::operator<<; + + // Use this in variadic streaming macros to allow + // >> +StreamEndStop + // as well as + // >> stuff +StreamEndStop + struct StreamEndStop { + std::string operator+() const; + }; + template + T const& operator + ( T const& value, StreamEndStop ) { + return value; + } +} + +#define CATCH_INTERNAL_LINEINFO \ + ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) + +// end catch_common.h +namespace Catch { + + struct RegistrarForTagAliases { + RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); + }; + +} // end namespace Catch + +#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ + CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ + CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ + namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ + CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION + +// end catch_tag_alias_autoregistrar.h +// start catch_test_registry.h + +// start catch_interfaces_testcase.h + +#include + +namespace Catch { + + class TestSpec; + + struct ITestInvoker { + virtual void invoke () const = 0; + virtual ~ITestInvoker(); + }; + + class TestCase; + struct IConfig; + + struct ITestCaseRegistry { + virtual ~ITestCaseRegistry(); + virtual std::vector const& getAllTests() const = 0; + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; + }; + + bool isThrowSafe( TestCase const& testCase, IConfig const& config ); + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + +} + +// end catch_interfaces_testcase.h +// start catch_stringref.h + +#include +#include +#include +#include + +namespace Catch { + + /// A non-owning string class (similar to the forthcoming std::string_view) + /// Note that, because a StringRef may be a substring of another string, + /// it may not be null terminated. + class StringRef { + public: + using size_type = std::size_t; + using const_iterator = const char*; + + private: + static constexpr char const* const s_empty = ""; + + char const* m_start = s_empty; + size_type m_size = 0; + + public: // construction + constexpr StringRef() noexcept = default; + + StringRef( char const* rawChars ) noexcept; + + constexpr StringRef( char const* rawChars, size_type size ) noexcept + : m_start( rawChars ), + m_size( size ) + {} + + StringRef( std::string const& stdString ) noexcept + : m_start( stdString.c_str() ), + m_size( stdString.size() ) + {} + + explicit operator std::string() const { + return std::string(m_start, m_size); + } + + public: // operators + auto operator == ( StringRef const& other ) const noexcept -> bool; + auto operator != (StringRef const& other) const noexcept -> bool { + return !(*this == other); + } + + auto operator[] ( size_type index ) const noexcept -> char { + assert(index < m_size); + return m_start[index]; + } + + public: // named queries + constexpr auto empty() const noexcept -> bool { + return m_size == 0; + } + constexpr auto size() const noexcept -> size_type { + return m_size; + } + + // Returns the current start pointer. If the StringRef is not + // null-terminated, throws std::domain_exception + auto c_str() const -> char const*; + + public: // substrings and searches + // Returns a substring of [start, start + length). + // If start + length > size(), then the substring is [start, size()). + // If start > size(), then the substring is empty. + auto substr( size_type start, size_type length ) const noexcept -> StringRef; + + // Returns the current start pointer. May not be null-terminated. + auto data() const noexcept -> char const*; + + constexpr auto isNullTerminated() const noexcept -> bool { + return m_start[m_size] == '\0'; + } + + public: // iterators + constexpr const_iterator begin() const { return m_start; } + constexpr const_iterator end() const { return m_start + m_size; } + }; + + auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; + auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; + + constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { + return StringRef( rawChars, size ); + } +} // namespace Catch + +constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { + return Catch::StringRef( rawChars, size ); +} + +// end catch_stringref.h +// start catch_preprocessor.hpp + + +#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ +#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) +#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) + +#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ +// MSVC needs more evaluations +#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) +#else +#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) +#endif + +#define CATCH_REC_END(...) +#define CATCH_REC_OUT + +#define CATCH_EMPTY() +#define CATCH_DEFER(id) id CATCH_EMPTY() + +#define CATCH_REC_GET_END2() 0, CATCH_REC_END +#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 +#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 +#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT +#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) +#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) + +#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) + +#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) +#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) + +// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, +// and passes userdata as the first parameter to each invocation, +// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) +#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) + +#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) +#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ +#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ +#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) +#else +// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF +#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) +#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ +#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) +#endif + +#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ +#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) + +#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) + +#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) +#else +#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) +#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) +#endif + +#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ + CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) + +#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) +#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) +#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) +#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) +#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) +#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) +#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _4, _5, _6) +#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) +#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) +#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) +#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) + +#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N + +#define INTERNAL_CATCH_TYPE_GEN\ + template struct TypeList {};\ + template\ + constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ + template class...> struct TemplateTypeList{};\ + template class...Cs>\ + constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ + template\ + struct append;\ + template\ + struct rewrap;\ + template class, typename...>\ + struct create;\ + template class, typename>\ + struct convert;\ + \ + template \ + struct append { using type = T; };\ + template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ + struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ + template< template class L1, typename...E1, typename...Rest>\ + struct append, TypeList, Rest...> { using type = L1; };\ + \ + template< template class Container, template class List, typename...elems>\ + struct rewrap, List> { using type = TypeList>; };\ + template< template class Container, template class List, class...Elems, typename...Elements>\ + struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ + \ + template