diff --git a/.github/workflows/build-qv2ray-cmake.yml b/.github/workflows/build-qv2ray-cmake.yml index 623b8996..020deb6d 100644 --- a/.github/workflows/build-qv2ray-cmake.yml +++ b/.github/workflows/build-qv2ray-cmake.yml @@ -81,10 +81,7 @@ jobs: - name: macOS - ${{ matrix.qt_version }} - Build preparation - Install Packages if: matrix.platform == 'macos-latest' run: | - brew install protobuf grpc ninja wget - wget https://github.com/phracker/MacOSX-SDKs/releases/download/10.15/MacOSX10.14.sdk.tar.xz - tar -xf MacOSX10.14.sdk.tar.xz - sudo mv -v ./MacOSX10.14.sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk + brew install protobuf grpc ninja # -------------------------------------------------------- - name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Build preparation - Download Dependencies shell: bash @@ -99,18 +96,23 @@ jobs: pathSource: ./libs/Qv2ray-deps-grpc-${{ matrix.arch }}-windows.7z pathTarget: ./libs # ========================================================================================================= Generate MakeFile and Build + + - uses: actions/setup-node@v1 + if: matrix.platform == 'macos-latest' + with: + node-version: '10.x' + - run: npm install -g appdmg + if: matrix.platform == 'macos-latest' - name: macOS - ${{ matrix.qt_version }} - Generate Dependencies and Build shell: bash if: matrix.platform == 'macos-latest' run: | mkdir build cd build - cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_SYSROOT=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.14.sdk -DCMAKE_OSX_DEPLOYMENT_TARGET=10.14 - sudo cmake --build . --target package --parallel $(sysctl -n hw.logicalcpu) - cp qv2ray-*.dmg ../ - - name: macOS - Get package name - id: get_package - run: echo ::set-output name=NAME::$(basename qv2ray-*.dmg) + cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDS_STORE_SCRIPT=ON + sudo cmake --build . --parallel $(sysctl -n hw.logicalcpu) + sudo cmake --install . + sudo appdmg ../assets/package_dmg.json ../Qv2ray.dmg # -------------------------------------------------------- - name: Windows - ${{ matrix.qt_version }} - Generate Dependencies and Build shell: bash @@ -177,15 +179,15 @@ jobs: if: matrix.platform == 'macos-latest' uses: actions/upload-artifact@master with: - name: ${{ steps.get_package.outputs.NAME }} - path: ${{ steps.get_package.outputs.NAME }} + name: Qv2ray-${{ github.sha }}.macOS-${{ matrix.arch }}.qt${{ matrix.qt_version }}.dmg + path: Qv2ray.dmg - name: macOS - ${{ matrix.qt_version }} - Upload binaries to release uses: svenstaro/upload-release-action@v1-release if: github.event_name == 'release' && matrix.platform == 'macos-latest' && matrix.qt_version == '5.14.2' with: repo_token: ${{ secrets.GITHUB_TOKEN }} - file: ${{ steps.get_package.outputs.NAME }} - asset_name: ${{ steps.get_package.outputs.NAME }} + file: Qv2ray.dmg + asset_name: Qv2ray-${{ steps.get_version.outputs.VERSION }}.macOS-${{ matrix.arch }}.dmg tag: ${{ github.ref }} overwrite: true # -------------------------------------------------------- diff --git a/.gitmodules b/.gitmodules index d175445d..acd86d16 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,12 +10,12 @@ [submodule "libs/libqvb"] path = libs/libqvb url = https://github.com/Qv2ray/QvRPCBridge -[submodule "3rdparty/cpp-httplib"] - path = 3rdparty/cpp-httplib - url = https://github.com/yhirose/cpp-httplib [submodule "libs/puresource"] path = libs/puresource url = https://github.com/Qv2ray/PureSource/ [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 + url = https://github.com/Qv2ray/QvPlugin-Interface/ diff --git a/.travis.yml b/.travis.yml index 9e6b4b19..59026963 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,6 @@ language: shell os: linux dist: bionic -arch: - - amd64 - - arm64 - -git: - depth: false - -branches: - only: - - dev - - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ env: global: @@ -23,30 +12,22 @@ addons: - name: snapcraft channel: stable confinement: classic - -before_install: - - echo "deb http://ppa.launchpad.net/ymshenyu/qv2ray-deps/ubuntu bionic main" | sudo tee -a /etc/apt/sources.list - - echo "deb http://archive.neon.kde.org/unstable bionic main" | sudo tee -a /etc/apt/sources.list - - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 281F24E574404629AA3BDA1A4F10C386C55CDB04 - - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E6D4736255751E5D - - sudo apt-get update -qq + - name: lxd + channel: stable script: - - snapcraft --destructive-mode + - 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 deploy: - - provider: snap - snap: qv2ray_*.snap - channel: edge - skip_cleanup: true + - provider: launchpad + slug: "~ymshenyu/qv2ray/+git/trunk" + oauth_token: $LAUNCHPAD_OAUTH_TOKEN + oauth_token_secret: $LAUNCHPAD_OAUTH_TOKEN_SECRET on: - branch: dev - - provider: snap - snap: qv2ray_*.snap - channel: beta - skip_cleanup: true - on: - branch: /^v\d+\.\d+(\.\d+)?(-\S*)?$/ \ No newline at end of file + all_branches: true \ No newline at end of file diff --git a/3rdparty/QNodeEditor b/3rdparty/QNodeEditor index f3f17e9a..db07dd4f 160000 --- a/3rdparty/QNodeEditor +++ b/3rdparty/QNodeEditor @@ -1 +1 @@ -Subproject commit f3f17e9a04e3db67e4a717fd2984754fd4555c24 +Subproject commit db07dd4ffcbfdd62431584d499928e45b5864f40 diff --git a/3rdparty/SingleApplication b/3rdparty/SingleApplication index 4abe20af..4baf2e74 160000 --- a/3rdparty/SingleApplication +++ b/3rdparty/SingleApplication @@ -1 +1 @@ -Subproject commit 4abe20afbfa5695ac7a9bce1298943b645aeffe9 +Subproject commit 4baf2e74f64c9a6ce36d456491bb41d0f2ae999e diff --git a/3rdparty/cpp-httplib b/3rdparty/cpp-httplib deleted file mode 160000 index e1acb949..00000000 --- a/3rdparty/cpp-httplib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e1acb949e74c663dc9dedc04e41d8bb0dfafb7c7 diff --git a/CMakeLists.txt b/CMakeLists.txt index 905d830a..882b120b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,7 @@ set(CMAKE_AUTOUIC ON) cmake_policy(SET CMP0071 NEW) if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.17.0") - cmake_policy(SET CMP0100 NEW) + cmake_policy(SET CMP0100 NEW) endif() message(" ") @@ -63,21 +63,21 @@ message("|-------------------------------------------------|") 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) - set(GUI_TYPE WIN32) - if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) - if(CMAKE_CL_64) - include(${CMAKE_SOURCE_DIR}/libs/x64-windows/scripts/buildsystems/vcpkg.cmake) - else() - include(${CMAKE_SOURCE_DIR}/libs/x86-windows/scripts/buildsystems/vcpkg.cmake) - endif() - endif() + 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) + set(GUI_TYPE WIN32) + if(NOT DEFINED CMAKE_TOOLCHAIN_FILE) + if(CMAKE_CL_64) + include(${CMAKE_SOURCE_DIR}/libs/x64-windows/scripts/buildsystems/vcpkg.cmake) + else() + include(${CMAKE_SOURCE_DIR}/libs/x86-windows/scripts/buildsystems/vcpkg.cmake) + endif() + endif() endif() if(UNIX AND NOT APPLE) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pthread") endif() set(QV2RAY_QNODEEDITOR_PROVIDER "module" CACHE STRING "qnodeeditor provider") @@ -120,176 +120,185 @@ 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") + 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") + 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}") + 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}") + 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}") + add_definitions(-DQV2RAY_TRANSLATION_PATH="${QV2RAY_TRANSLATION_PATH}") endif() if(QV2RAY_USE_BUILTIN_DARKTHEME) - add_definitions(-DQV2RAY_USE_BUILTIN_DARKTHEME=true) + add_definitions(-DQV2RAY_USE_BUILTIN_DARKTHEME=true) endif() if(QV2RAY_DISABLE_AUTO_UPDATE) - add_definitions(-DDISABLE_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 - 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/pac/QvGFWPACConverter.cpp - src/components/pac/QvPACHandler.cpp - src/components/plugins/toolbar/QvToolbar.cpp - src/components/plugins/toolbar/QvToolbar_linux.cpp - src/components/plugins/toolbar/QvToolbar_win.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/V2rayInstanceHandler.cpp - src/core/kernel/APIBackend.cpp - src/core/kernel/KernelInteractions.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_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_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/pac/QvPACHandler.hpp - src/components/plugins/toolbar/QvToolbar.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/kernel/APIBackend.hpp - src/core/kernel/KernelInteractions.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_ScreenShot_Core.hpp - src/ui/w_SubscriptionManager.hpp - assets/qv2ray.rc -) + ${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) + 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 @@ -305,123 +314,125 @@ set(QT_LIBRARY Qt5::Gui Qt5::Widgets Qt5::Network -) + ) add_executable(${PROJECT_NAME} - ${GUI_TYPE} - ${QV2RAY_SOURCES} - ${QNODEEDITOR_SOURCES} - ${QNODEEDITOR_QRC_RESOURCES} - ${SINGLEAPPLICATION_SOURCES} - ${ZXING_SOURCES} - ${PROTO_SRCS} - ${PROTO_HDRS} - ${API_GRPC_SRCS} - ${API_PROTO_SRCS} - ${QRC_RESOURCES} - ${QM_FILES} -) + ${GUI_TYPE} + ${QV2RAY_SOURCES} + ${QNODEEDITOR_SOURCES} + ${QNODEEDITOR_QRC_RESOURCES} + ${SINGLEAPPLICATION_SOURCES} + ${ZXING_SOURCES} + ${PROTO_SRCS} + ${PROTO_HDRS} + ${API_GRPC_SRCS} + ${API_PROTO_SRCS} + ${QRC_RESOURCES} + ${QM_FILES} + ) target_link_libraries(${PROJECT_NAME} - ${QT_LIBRARY} - ${QV2RAY_PROTOBUF_LIBRARY} - ${QV2RAY_BACKEND_LIBRARIES} - ${QNODEEDITOR_LIBRARY} - ${ZXING_LIBRARY} -) + ${QT_LIBRARY} + ${QV2RAY_PROTOBUF_LIBRARY} + ${QV2RAY_BACKEND_LIBRARIES} + ${QNODEEDITOR_LIBRARY} + ${ZXING_LIBRARY} + ) target_include_directories(${PROJECT_NAME} PRIVATE - ${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} -) + ${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} + ) if(APPLE) find_package(Iconv REQUIRED) target_link_libraries(${PROJECT_NAME} - Iconv::Iconv - "-framework Carbon" - "-framework Cocoa" - "-framework Security" - ) + Iconv::Iconv + "-framework Carbon" + "-framework Cocoa" + "-framework Security" + ) target_include_directories(${PROJECT_NAME} PRIVATE - ${Iconv_INCLUDE_DIR} - ) + ${Iconv_INCLUDE_DIR} + ) set(MACOSX_ICON "${CMAKE_SOURCE_DIR}/assets/icons/qv2ray.icns") + set(MACOSX_PLIST "${CMAKE_SOURCE_DIR}/assets/MacOSXBundleInfo.plist.in") set_source_files_properties(${QM_FILES} - PROPERTIES - MACOSX_PACKAGE_LOCATION Resources/lang - ) + PROPERTIES + MACOSX_PACKAGE_LOCATION Resources/lang + ) target_sources(${PROJECT_NAME} PRIVATE - ${MACOSX_ICON} - ) + ${MACOSX_ICON} + ) set_target_properties(${PROJECT_NAME} - PROPERTIES - MACOSX_BUNDLE TRUE - MACOSX_BUNDLE_BUNDLE_NAME "Qv2ray" - MACOSX_BUNDLE_BUNDLE_VERSION ${QV2RAY_VERSION_STRING} - MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2019-2020 Qv2ray Development Group" - MACOSX_BUNDLE_GUI_IDENTIFIER "com.github.qv2ray" - MACOSX_BUNDLE_ICON_FILE "qv2ray.icns" - MACOSX_BUNDLE_INFO_STRING "Created by Qv2ray development team" - MACOSX_BUNDLE_LONG_VERSION_STRING ${QV2RAY_VERSION_STRING} - MACOSX_BUNDLE_SHORT_VERSION_STRING ${QV2RAY_VERSION_STRING} - RESOURCE - ${MACOSX_ICON} - ) + PROPERTIES + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_INFO_PLIST ${MACOSX_PLIST} + MACOSX_BUNDLE_BUNDLE_NAME "Qv2ray" + MACOSX_BUNDLE_BUNDLE_VERSION ${QV2RAY_VERSION_STRING} + MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2019-2020 Qv2ray Development Group" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.github.qv2ray" + MACOSX_BUNDLE_ICON_FILE "qv2ray.icns" + MACOSX_BUNDLE_INFO_STRING "Created by Qv2ray development team" + MACOSX_BUNDLE_LONG_VERSION_STRING ${QV2RAY_VERSION_STRING} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${QV2RAY_VERSION_STRING} + RESOURCE + ${MACOSX_ICON} + ) # Destination paths below are relative to ${CMAKE_INSTALL_PREFIX} install(TARGETS ${PROJECT_NAME} - BUNDLE DESTINATION . COMPONENT Runtime - RUNTIME DESTINATION bin COMPONENT Runtime - ) + 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 - ) + COMMAND macdeployqt ${CMAKE_BINARY_DIR}/${PROJECT_NAME}.app + ) set(APPS "\${CMAKE_INSTALL_PREFIX}/${PROJECT_NAME}.app") include(cmake/deployment.cmake) endif() if(UNIX AND NOT APPLE AND NOT WIN32) - install(TARGETS ${PROJECT_NAME} 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) - if(NOT EMBED_TRANSLATIONS) - install(FILES ${QM_FILES} DESTINATION share/qv2ray/lang) - endif() + install(TARGETS ${PROJECT_NAME} 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) + if(NOT EMBED_TRANSLATIONS) + install(FILES ${QM_FILES} DESTINATION share/qv2ray/lang) + endif() endif() if(WIN32) - target_link_libraries(${PROJECT_NAME} wininet wsock32 ws2_32 user32) - install(TARGETS ${PROJECT_NAME} 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 .) - endif() + target_link_libraries(${PROJECT_NAME} wininet wsock32 ws2_32 user32) + install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION .) + if(NOT EMBED_TRANSLATIONS) + install(FILES ${QM_FILES} DESTINATION lang) + 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") - include(cmake/deployment.cmake) + 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 .) + 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") + include(cmake/deployment.cmake) endif() diff --git a/assets/CMakeDMGBackground.png b/assets/CMakeDMGBackground.png new file mode 100644 index 00000000..28f3d3ed Binary files /dev/null and b/assets/CMakeDMGBackground.png differ diff --git a/assets/CMakeDMGBackground.tif b/assets/CMakeDMGBackground.tif new file mode 100644 index 00000000..6e3eb7dd Binary files /dev/null and b/assets/CMakeDMGBackground.tif differ diff --git a/assets/DS_Store b/assets/DS_Store new file mode 100644 index 00000000..b71b7d79 Binary files /dev/null and b/assets/DS_Store differ diff --git a/assets/MacOSXBundleInfo.plist.in b/assets/MacOSXBundleInfo.plist.in new file mode 100644 index 00000000..e06b17ec --- /dev/null +++ b/assets/MacOSXBundleInfo.plist.in @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSPrincipalClass + NSApplication + NSHighResolutionCapable + True + + diff --git a/assets/icons/designs/Applogo_Bird.svg b/assets/icons/designs/Applogo_Bird.svg new file mode 100644 index 00000000..b3578563 --- /dev/null +++ b/assets/icons/designs/Applogo_Bird.svg @@ -0,0 +1,238 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/assets/icons/ui_dark/design/tray-connected.svg b/assets/icons/ui_dark/design/tray-connected.svg new file mode 100644 index 00000000..ca95e7bd --- /dev/null +++ b/assets/icons/ui_dark/design/tray-connected.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/assets/icons/ui_dark/design/tray-systemproxy.svg b/assets/icons/ui_dark/design/tray-systemproxy.svg new file mode 100644 index 00000000..fd542395 --- /dev/null +++ b/assets/icons/ui_dark/design/tray-systemproxy.svg @@ -0,0 +1,309 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/assets/icons/ui_dark/tray-connected.png b/assets/icons/ui_dark/tray-connected.png new file mode 100644 index 00000000..10dc8cd0 Binary files /dev/null and b/assets/icons/ui_dark/tray-connected.png differ diff --git a/assets/icons/ui_dark/tray-systemproxy.png b/assets/icons/ui_dark/tray-systemproxy.png new file mode 100644 index 00000000..035ddabd Binary files /dev/null and b/assets/icons/ui_dark/tray-systemproxy.png differ diff --git a/assets/icons/ui_light/design/tray-connected.svg b/assets/icons/ui_light/design/tray-connected.svg new file mode 100644 index 00000000..ea355dd4 --- /dev/null +++ b/assets/icons/ui_light/design/tray-connected.svg @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/assets/icons/ui_light/design/tray-systemproxy.svg b/assets/icons/ui_light/design/tray-systemproxy.svg new file mode 100644 index 00000000..239b4758 --- /dev/null +++ b/assets/icons/ui_light/design/tray-systemproxy.svg @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/assets/icons/ui_light/tray-connected.png b/assets/icons/ui_light/tray-connected.png new file mode 100644 index 00000000..5760b021 Binary files /dev/null and b/assets/icons/ui_light/tray-connected.png differ diff --git a/assets/icons/ui_light/tray-systemproxy.png b/assets/icons/ui_light/tray-systemproxy.png new file mode 100644 index 00000000..452dd7be Binary files /dev/null and b/assets/icons/ui_light/tray-systemproxy.png differ diff --git a/assets/package_dmg.json.in b/assets/package_dmg.json.in new file mode 100644 index 00000000..fb1b9dc8 --- /dev/null +++ b/assets/package_dmg.json.in @@ -0,0 +1,17 @@ + +{ + "title": "Qv2ray @QV2RAY_VERSION_STRING@", + "icon": "@CMAKE_SOURCE_DIR@/assets/icons/qv2ray.icns", + "background": "@CMAKE_SOURCE_DIR@/assets/CMakeDMGBackground.png", + "icon-size": 128, + "window": { + "size": { + "width": 500, + "height": 365 + } + }, + "contents": [ + { "x": 130, "y": 205, "type": "file", "path": "@CMAKE_BINARY_DIR@/qv2ray.app" }, + { "x": 375, "y": 205, "type": "link", "path": "/Applications" } + ] +} diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..66ce01c5 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,46 @@ +# Starter pipeline + +trigger: + branches: + include: + - master + - dev + tags: + include: + - v* + +pool: + vmImage: 'macOS-10.14' + +steps: +- checkout: self + submodules: recursive +- script: | + brew install protobuf grpc ninja qt5 + 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) + 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' + ArtifactName: 'qv2ray-legacy.dmg' + publishLocation: 'Container' +- task: GitHubRelease@0 + condition: startsWith(variables['Build.SourceBranch'], 'refs/tags/v') + inputs: + gitHubConnection: 'GitHub - DuckSoft' + assets: 'qv2ray-legacy.dmg' + action: edit + tag: '$(Build.SourceBranchName)' + isPreRelease: true + addChangeLog: false \ No newline at end of file diff --git a/cmake/CMakeDMGSetup.scpt b/cmake/CMakeDMGSetup.scpt new file mode 100644 index 00000000..2ec34563 --- /dev/null +++ b/cmake/CMakeDMGSetup.scpt @@ -0,0 +1,57 @@ +on run argv + set image_name to item 1 of argv + + tell application "Finder" + tell disk image_name + + -- wait for the image to finish mounting + set open_attempts to 0 + repeat while open_attempts < 4 + try + open + delay 1 + set open_attempts to 5 + close + on error errStr number errorNumber + set open_attempts to open_attempts + 1 + delay 10 + end try + end repeat + delay 5 + + -- open the image the first time and save a DS_Store with just + -- background and icon setup + open + set current view of container window to icon view + set theViewOptions to the icon view options of container window + set background picture of theViewOptions to file ".background:background.png" + set arrangement of theViewOptions to not arranged + set icon size of theViewOptions to 128 + delay 5 + close + + -- next setup the position of the app and Applications symlink + -- plus hide all the window decoration + open + update without registering applications + tell container window + set sidebar width to 0 + set statusbar visible to false + set toolbar visible to false + set the bounds to { 400, 100, 900, 465 } + set position of item "qv2ray.app" to { 133, 200 } + set position of item "Applications" to { 378, 200 } + end tell + update without registering applications + delay 5 + close + + -- one last open and close so you can see everything looks correct + open + delay 5 + close + + end tell + delay 1 +end tell +end run diff --git a/cmake/deployment.cmake b/cmake/deployment.cmake index ec9b2ce5..16f011f4 100644 --- a/cmake/deployment.cmake +++ b/cmake/deployment.cmake @@ -61,6 +61,14 @@ endif() if(APPLE) set(CPACK_GENERATOR "DragNDrop") + if(DS_STORE_SCRIPT) + set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CMakeDMGSetup.scpt") + else() + set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/assets/DS_Store") + endif() + + set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/assets/CMakeDMGBackground.png") + configure_file("${CMAKE_SOURCE_DIR}/assets/package_dmg.json.in" "${CMAKE_SOURCE_DIR}/assets/package_dmg.json" @ONLY) endif() include(CPack) diff --git a/debian/changelog b/debian/changelog index f48e5fa8..31a21954 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,13 @@ +qv2ray (2.5.0~pre1-1) unstable; urgency=medium + + * fix: skip saving config when importing multiple connection configs + * add: ignore updating subscription by setting interval to 0 + * fix: fixed a missing signal emit + * update: updated label text, fixed a spelling misATke + * add: added kernel plugin support + + -- Guobang Bi Sat, 11 Apr 2020 23:12:20 +0800 + qv2ray (2.4.1-1) unstable; urgency=medium * add: add new semver checker diff --git a/makespec/BUILDVERSION b/makespec/BUILDVERSION index 03753f4e..2dc33761 100644 --- a/makespec/BUILDVERSION +++ b/makespec/BUILDVERSION @@ -1 +1 @@ -5150 +5264 \ No newline at end of file diff --git a/makespec/VERSION b/makespec/VERSION index 005119ba..437459cd 100644 --- a/makespec/VERSION +++ b/makespec/VERSION @@ -1 +1 @@ -2.4.1 +2.5.0 diff --git a/makespec/VERSIONSUFFIX b/makespec/VERSIONSUFFIX index 8b137891..39a5a5a1 100644 --- a/makespec/VERSIONSUFFIX +++ b/makespec/VERSIONSUFFIX @@ -1 +1 @@ - +-pre1 diff --git a/resources.qrc b/resources.qrc index d3598f3a..e76afe7b 100644 --- a/resources.qrc +++ b/resources.qrc @@ -32,5 +32,9 @@ assets/icons/ui_light/locate.png assets/icons/ui_dark/sort.png assets/icons/ui_light/sort.png + assets/icons/ui_dark/tray-connected.png + assets/icons/ui_dark/tray-systemproxy.png + assets/icons/ui_light/tray-connected.png + assets/icons/ui_light/tray-systemproxy.png diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 40127b21..8ebebe22 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -83,6 +83,7 @@ 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 @@ -108,8 +109,21 @@ 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 + 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 diff --git a/src/base/Qv2rayBase.hpp b/src/base/Qv2rayBase.hpp index 66814bbb..55d89ea8 100644 --- a/src/base/Qv2rayBase.hpp +++ b/src/base/Qv2rayBase.hpp @@ -46,6 +46,7 @@ using namespace Qv2ray::base::objects::transfer; #define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf") #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/") @@ -90,9 +91,17 @@ using namespace Qv2ray::base::objects::transfer; #define BLACK(obj) obj->setPalette(QWidget::palette()); -#define QV2RAY_UI_COLORSCHEME_ROOT \ +#ifdef Q_OS_MACOS + #define ACCESS_OPTIONAL_VALUE(obj) (*obj) +#else + #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 \ ((GlobalConfig.uiConfig.useDarkTheme) ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/")) -#define QICON_R(file) QIcon(QV2RAY_UI_COLORSCHEME_ROOT + file) +#define QICON_R(file) QIcon(QV2RAY_COLORSCHEME_ROOT + file) #define QSTRN(num) QString::number(num) @@ -127,4 +136,27 @@ namespace Qv2ray isExiting = true; QApplication::quit(); } + + inline QStringList Qv2rayAssetsPaths(const QString &dirName) + { + // Configuration Path + QStringList list; + list << QV2RAY_CONFIG_DIR + dirName; + // +#ifdef Q_OS_LINUX + // Linux platform directories. + 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); +#elif defined(Q_OS_MAC) + // macOS platform directories. + list << QDir(QApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath(); +#endif + // This is the default behavior on Windows + list << QApplication::applicationDirPath() + "/" + dirName; + list.removeDuplicates(); + return list; + }; + } // namespace Qv2ray diff --git a/src/base/Qv2rayLog.hpp b/src/base/Qv2rayLog.hpp index 28e2a1bf..c01499c0 100644 --- a/src/base/Qv2rayLog.hpp +++ b/src/base/Qv2rayLog.hpp @@ -41,6 +41,7 @@ const inline QString MODULE_FILEIO = "COMMON-FILEIO"; // const inline QString MODULE_PROXY = "COMPONENT-PROXY"; const inline QString MODULE_UPDATE = "COMPONENT-UPDATE"; -const inline QString MODULE_PLUGIN = "COMPONENT-PLUGIN"; +const inline QString MODULE_PLUGINHOST = "COMPONENT-PLUGINHOST"; +const inline QString MODULE_PLUGINCLIENT = "PLUGIN-CLIENT"; // ================================================================ const inline QString MODULE_CORE_HANDLER = "QV2RAY-CORE"; diff --git a/src/base/models/CoreObjectModels.hpp b/src/base/models/CoreObjectModels.hpp index 14d86f04..3c4e5a68 100644 --- a/src/base/models/CoreObjectModels.hpp +++ b/src/base/models/CoreObjectModels.hpp @@ -257,13 +257,14 @@ namespace Qv2ray::base::objects { QString serverName; bool allowInsecure; + bool allowInsecureCiphers; QList alpn; QList certificates; bool disableSystemRoot; - TLSObject() : serverName(), allowInsecure(), certificates(), disableSystemRoot() + TLSObject() : serverName(), allowInsecure(), allowInsecureCiphers(), certificates(), disableSystemRoot() { } - XTOSTRUCT(O(serverName, allowInsecure, alpn, certificates, disableSystemRoot)) + XTOSTRUCT(O(serverName, allowInsecure, allowInsecureCiphers, alpn, certificates, disableSystemRoot)) }; } // namespace transfer // diff --git a/src/base/models/QvSettingsObject.hpp b/src/base/models/QvSettingsObject.hpp index 75a92972..3e1561ad 100644 --- a/src/base/models/QvSettingsObject.hpp +++ b/src/base/models/QvSettingsObject.hpp @@ -41,18 +41,6 @@ namespace Qv2ray::base::config XTOSTRUCT(O(Pages)) }; - struct Qv2rayPACConfig - { - bool enablePAC; - int port; - QString localIP; - bool useSocksProxy; - Qv2rayPACConfig() : enablePAC(false), port(8989), useSocksProxy(false) - { - } - XTOSTRUCT(O(enablePAC, port, localIP, useSocksProxy)) - }; - struct Qv2rayForwardProxyConfig { bool enableForwardProxy; @@ -73,7 +61,6 @@ namespace Qv2ray::base::config { QString listenip; bool setSystemProxy; - Qv2rayPACConfig pacConfig; // SOCKS bool useSocks; @@ -89,26 +76,27 @@ namespace Qv2ray::base::config objects::AccountObject httpAccount; Qv2rayInboundsConfig() - : listenip("127.0.0.1"), setSystemProxy(true), pacConfig(), useSocks(true), socks_port(1088), socks_useAuth(false), socksUDP(true), + : 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() { } - XTOSTRUCT(O(setSystemProxy, pacConfig, listenip, useSocks, useHTTP, socks_port, socks_useAuth, socksAccount, socksUDP, socksLocalIP, - http_port, http_useAuth, httpAccount)) + XTOSTRUCT(O(setSystemProxy, listenip, useSocks, useHTTP, socks_port, socks_useAuth, socksAccount, socksUDP, socksLocalIP, http_port, + http_useAuth, httpAccount)) }; struct Qv2rayUIConfig { QString theme; QString language; + bool quietMode; bool useDarkTheme; bool useDarkTrayIcon; int maximumLogLines; Qv2rayUIConfig() : theme("Fusion"), language("en_US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500) { } - XTOSTRUCT(O(theme, language, useDarkTheme, useDarkTrayIcon, maximumLogLines)) + XTOSTRUCT(O(theme, language, quietMode, useDarkTheme, useDarkTrayIcon, maximumLogLines)) }; struct Qv2rayRouteConfig_Impl @@ -141,6 +129,15 @@ namespace Qv2ray::base::config XTOSTRUCT(O(domainStrategy, domains, ips)) }; + struct Qv2rayPluginConfig + { + QMap pluginStates; + bool v2rayIntegration; + int portAllocationStart; + Qv2rayPluginConfig() : pluginStates(), v2rayIntegration(true), portAllocationStart(15000){}; + XTOSTRUCT(O(pluginStates, v2rayIntegration)) + }; + struct Qv2rayConnectionConfig { bool bypassCN; @@ -218,6 +215,25 @@ namespace Qv2ray::base::config XTOSTRUCT(O(ignoredVersion, updateChannel)) }; + struct Qv2rayAdvancedConfig + { + bool setAllowInsecure; + bool setAllowInsecureCiphers; + bool testLatencyPeriodcally; + XTOSTRUCT(O(setAllowInsecure, setAllowInsecureCiphers, testLatencyPeriodcally)) + }; + + struct Qv2rayNetworkConfig + { + bool useCustomProxy; + QString address; + QString type; + int port; + QString userAgent; + Qv2rayNetworkConfig() : address(""), type("http"), port(8000), userAgent("Qv2ray/" QV2RAY_VERSION_STRING " WebRequestHelper"){}; + XTOSTRUCT(O(useCustomProxy, type, address, port, userAgent)) + }; + struct Qv2rayConfig { int config_version; @@ -234,19 +250,53 @@ namespace Qv2ray::base::config // Qv2rayUIConfig uiConfig; Qv2rayAPIConfig apiConfig; + Qv2rayPluginConfig pluginConfig; Qv2rayKernelConfig kernelConfig; Qv2rayUpdateConfig updateConfig; + Qv2rayNetworkConfig networkConfig; Qv2rayToolBarConfig toolBarConfig; Qv2rayInboundsConfig inboundConfig; + Qv2rayAdvancedConfig advancedConfig; Qv2rayConnectionConfig connectionConfig; Qv2rayConfig() - : config_version(QV2RAY_CONFIG_VERSION), tProxySupport(false), logLevel(), autoStartId("null"), groups(), subscriptions(), - connections(), uiConfig(), apiConfig(), kernelConfig(), updateConfig(), toolBarConfig(), inboundConfig(), connectionConfig() + : config_version(QV2RAY_CONFIG_VERSION), // + tProxySupport(false), // + logLevel(), // + autoStartId("null"), // + groups(), // + subscriptions(), // + connections(), // + uiConfig(), // + apiConfig(), // + pluginConfig(), // + kernelConfig(), // + updateConfig(), // + networkConfig(), // + toolBarConfig(), // + inboundConfig(), // + advancedConfig(), // + connectionConfig() { } - XTOSTRUCT(O(config_version, tProxySupport, logLevel, uiConfig, kernelConfig, updateConfig, groups, connections, subscriptions, - autoStartId, inboundConfig, connectionConfig, toolBarConfig, apiConfig)) + XTOSTRUCT(O(config_version, // + tProxySupport, // + logLevel, // + uiConfig, // + pluginConfig, // + updateConfig, // + kernelConfig, // + networkConfig, // + groups, // + connections, // + subscriptions, // + autoStartId, // + inboundConfig, // + connectionConfig, // + toolBarConfig, // + advancedConfig, // + apiConfig // + )) }; } // namespace Qv2ray::base::config diff --git a/src/base/models/QvStartupConfig.hpp b/src/base/models/QvStartupConfig.hpp index 258a1631..8f88f79f 100644 --- a/src/base/models/QvStartupConfig.hpp +++ b/src/base/models/QvStartupConfig.hpp @@ -16,6 +16,8 @@ namespace Qv2ray bool enableToolbarPlguin; /// Disable Qt scale factors support. bool noScaleFactors; + /// Disable all plugin features. + bool noPlugins; }; } // namespace base inline base::QvStartupOptions StartupOption = base::QvStartupOptions(); diff --git a/src/common/CommandArgs.cpp b/src/common/CommandArgs.cpp index 2a2c8077..51f24736 100644 --- a/src/common/CommandArgs.cpp +++ b/src/common/CommandArgs.cpp @@ -9,6 +9,7 @@ namespace Qv2ray::common 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") @@ -20,6 +21,7 @@ namespace Qv2ray::common parser.addOption(runAsRootOption); parser.addOption(debugOption); parser.addOption(noScaleFactorOption); + parser.addOption(noPluginsOption); parser.addOption(withToolbarOption); helpOption = parser.addHelpOption(); versionOption = parser.addVersionOption(); @@ -63,6 +65,12 @@ namespace Qv2ray::common 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.") diff --git a/src/common/CommandArgs.hpp b/src/common/CommandArgs.hpp index 61483122..dbab70cc 100644 --- a/src/common/CommandArgs.hpp +++ b/src/common/CommandArgs.hpp @@ -28,6 +28,7 @@ namespace Qv2ray::common QCommandLineOption runAsRootOption; QCommandLineOption debugOption; QCommandLineOption noScaleFactorOption; + QCommandLineOption noPluginsOption; QCommandLineOption withToolbarOption; QCommandLineOption helpOption; QCommandLineOption versionOption; diff --git a/src/common/HTTPRequestHelper.cpp b/src/common/HTTPRequestHelper.cpp index 240f3b6e..fee30437 100644 --- a/src/common/HTTPRequestHelper.cpp +++ b/src/common/HTTPRequestHelper.cpp @@ -16,112 +16,80 @@ namespace Qv2ray::common accessManager.disconnect(); } - bool QvHttpRequestHelper::setUrl(const QString &url) - { - QUrl qUrl = QUrl(url); - - if (!qUrl.isValid()) - { - LOG(MODULE_NETWORK, "Provided URL is invalid: " + url) - return false; - } - - request.setUrl(qUrl); - return true; - } - void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value) { DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value) request.setRawHeader(key, value); } - QByteArray QvHttpRequestHelper::syncget(const QString &url, bool useProxy) + QByteArray QvHttpRequestHelper::Get(const QString &url, bool useProxy) { - this->setUrl(url); - + request.setUrl({ url }); if (useProxy) { - auto proxy = QNetworkProxyFactory::systemProxyForQuery(); - accessManager.setProxy(proxy.first()); - LOG(MODULE_NETWORK, "Sync get is using system proxy settings") + auto p = GlobalConfig.networkConfig.useCustomProxy ? + QNetworkProxy{ + GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, // + GlobalConfig.networkConfig.address, // + quint16(GlobalConfig.networkConfig.port) // + } : + QNetworkProxyFactory::systemProxyForQuery().first(); + accessManager.setProxy(p); } else { + DEBUG(MODULE_NETWORK, "Get without proxy.") accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy)); } - + if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) + { + DEBUG(MODULE_NETWORK, "Adding HostNameLookupCapability to proxy.") + accessManager.proxy().setCapabilities(accessManager.proxy().capabilities() | QNetworkProxy::HostNameLookupCapability); + } request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); - request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, "Mozilla/5.0 (rv:71.0) Gecko/20100101 Firefox/71.0"); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, GlobalConfig.networkConfig.userAgent); reply = accessManager.get(request); - connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p); // QEventLoop loop; connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); loop.exec(); + // // Data or timeout? auto data = reply->readAll(); return data; } - void QvHttpRequestHelper::get(const QString &url) + void QvHttpRequestHelper::AsyncGet(const QString &url) { - this->setUrl(url); - // request.setRawHeader("Content-Type", - // "application/x-www-form-urlencoded"); + request.setUrl({ url }); + if (GlobalConfig.networkConfig.useCustomProxy) + { + QNetworkProxy p{ + GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, // + GlobalConfig.networkConfig.address, // + quint16(GlobalConfig.networkConfig.port) // + }; + accessManager.setProxy(p); + } + else + { + DEBUG(MODULE_NETWORK, "Get without proxy.") + accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy)); + } + if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy) + { + DEBUG(MODULE_NETWORK, "Adding HostNameLookupCapability to proxy.") + accessManager.proxy().setCapabilities(accessManager.proxy().capabilities() | QNetworkProxy::HostNameLookupCapability); + } + request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy); + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); + request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, GlobalConfig.networkConfig.userAgent); reply = accessManager.get(request); connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p); - connect(reply, &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead); + connect(reply, &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead_p); } - // void QvHttpRequestHelper::post(const QString &url, const QByteArray - // &data) - //{ - // this->setUrl(url); - // request.setRawHeader("Content-Type", "application/json"); - // reply = accessManager.post(request, data); - // connect(reply, &QNetworkReply::finished, this, - // &QvHttpRequestHelper::onRequestFinished); connect(reply, - // &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead); - //} - - // void QvHttpRequestHelper::put(const QString &url, const QByteArray - // &data) - // { - // this->setUrl(url); - // request.setRawHeader("Content-Type", "application/json"); - // reply = accessManager.put(request, data); - // connect(reply, &QNetworkReply::finished, this, - // &QvHttpRequestHelper::onRequestFinished); connect(reply, - // &QNetworkReply::readyRead, this, - // &QvHttpRequestHelper::onReadyRead); - // } - - // void QvHttpRequestHelper::del(const QString &url) - // { - // this->setUrl(url); - // request.setRawHeader("Content-Type", "application/json"); - // reply = accessManager.deleteResource(request); - // connect(reply, &QNetworkReply::finished, this, - // &QvHttpRequestHelper::onRequestFinished); connect(reply, - // &QNetworkReply::readyRead, this, - // &QvHttpRequestHelper::onReadyRead); - // } - - // void QvHttpRequestHelper::login(const QString &url, const QByteArray - // &data) - // { - // this->setUrl(url); - // request.setRawHeader("Content-Type", - // "application/x-www-form-urlencoded"); reply = - // accessManager.post(request, data); connect(reply, - // &QNetworkReply::finished, this, - // &QvHttpRequestHelper::onRequestFinished); connect(reply, - // &QNetworkReply::readyRead, this, - // &QvHttpRequestHelper::onReadyRead); - // } - void QvHttpRequestHelper::onRequestFinished_p() { if (reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool()) @@ -134,15 +102,15 @@ namespace Qv2ray::common QString error = QMetaEnum::fromType().key(reply->error()); LOG(MODULE_NETWORK, "Network request error string: " + error) QByteArray empty; - emit httpRequestFinished(empty); + emit OnRequestFinished(empty); } else { - emit httpRequestFinished(this->data); + emit OnRequestFinished(this->data); } } - void QvHttpRequestHelper::onReadyRead() + void QvHttpRequestHelper::onReadyRead_p() { DEBUG(MODULE_NETWORK, "A request is now ready read") this->data += reply->readAll(); diff --git a/src/common/HTTPRequestHelper.hpp b/src/common/HTTPRequestHelper.hpp index c2ebc159..a2f8f14b 100644 --- a/src/common/HTTPRequestHelper.hpp +++ b/src/common/HTTPRequestHelper.hpp @@ -31,26 +31,18 @@ namespace Qv2ray::common public: explicit QvHttpRequestHelper(QObject *parent = nullptr); ~QvHttpRequestHelper(); - bool setUrl(const QString &url); - void setHeader(const QByteArray &key, const QByteArray &value); // get - QByteArray syncget(const QString &url, bool useProxy); - void get(const QString &url); - //// insert - // void post(const QString &url, const QByteArray &data); - //// update - // void put(const QString &url, const QByteArray &data); - //// delete - // void del(const QString &url); - // void login(const QString &url, const QByteArray &data); + void AsyncGet(const QString &url); + QByteArray Get(const QString &url, bool useProxy); signals: - void httpRequestFinished(QByteArray &data); + void OnRequestFinished(QByteArray &data); private slots: void onRequestFinished_p(); - void onReadyRead(); + void onReadyRead_p(); private: + void setHeader(const QByteArray &key, const QByteArray &value); QByteArray data; QUrl url; QNetworkReply *reply; diff --git a/src/common/QRCodeHelper.cpp b/src/common/QRCodeHelper.cpp index 096d37e1..87cc2486 100644 --- a/src/common/QRCodeHelper.cpp +++ b/src/common/QRCodeHelper.cpp @@ -7,14 +7,16 @@ #include "TextUtfEncoding.h" #include "base/Qv2rayBase.hpp" -namespace Qv2ray::components +namespace Qv2ray::common { using namespace ZXing; QString DecodeQRCode(const QImage &source) { + if (source.isNull()) + return ""; QImage img = source.copy(); - auto result = - ReadBarcode(img.width(), img.height(), img.bits(), img.width() * 4, 4, 0, 1, 2, { BarcodeFormatFromString("") }, true, true); + const auto result = + ReadBarcode(img.width(), img.height(), img.bits(), img.width() * 4, 4, 0, 1, 2, { ZXing::BarcodeFormat::QR_CODE }, true, true); if (result.isValid()) { @@ -34,17 +36,15 @@ namespace Qv2ray::components int eccLevel = 1; try { - auto barcodeFormat = BarcodeFormatFromString("QR_CODE"); - - MultiFormatWriter writer(barcodeFormat); + MultiFormatWriter writer(ZXing::BarcodeFormat::QR_CODE); writer.setMargin(1); writer.setEccLevel(eccLevel); - auto bitmap = writer.encode(content.toStdWString(), size.width(), size.height()); - auto BM = bitmap.toByteMatrix(); + const auto bitmap = writer.encode(content.toStdWString(), size.width(), size.height()); + const auto BM = bitmap.toByteMatrix(); // - const QRgb black = qRgba(0, 0, 0, 255); - const QRgb white = qRgba(255, 255, 255, 255); + const auto black = qRgba(0, 0, 0, 255); + const auto white = qRgba(255, 255, 255, 255); // auto image = QImage(bitmap.width(), bitmap.width(), QImage::Format_ARGB32); image.fill(white); @@ -67,4 +67,4 @@ namespace Qv2ray::components return {}; } } -} // namespace Qv2ray::components +} // namespace Qv2ray::common diff --git a/src/common/QRCodeHelper.hpp b/src/common/QRCodeHelper.hpp index 49576918..b69ecacc 100644 --- a/src/common/QRCodeHelper.hpp +++ b/src/common/QRCodeHelper.hpp @@ -2,9 +2,9 @@ #include #include -namespace Qv2ray::components +namespace Qv2ray::common { QString DecodeQRCode(const QImage &img); QImage EncodeQRCode(const QString &content, const QSize &size); -} // namespace Qv2ray::components -using namespace Qv2ray::components; +} // namespace Qv2ray::common +using namespace Qv2ray::common; diff --git a/src/common/QvTranslator.cpp b/src/common/QvTranslator.cpp index 25057b92..2c5f5c63 100644 --- a/src/common/QvTranslator.cpp +++ b/src/common/QvTranslator.cpp @@ -17,33 +17,14 @@ using namespace Qv2ray::base; QStringList getLanguageSearchPaths() { // Configuration Path - QStringList list; - list << QV2RAY_CONFIG_DIR + "lang"; -// + QStringList list = Qv2rayAssetsPaths("lang"); #ifdef EMBED_TRANSLATIONS // If the translations have been embedded. list << QString(":/translations/"); #endif - // - // #ifdef QV2RAY_TRANSLATION_PATH // Platform-specific dir, if specified. list << QString(QV2RAY_TRANSLATION_PATH); -#endif - // - // -#ifdef Q_OS_LINUX - // Linux platform directories. - list << QString("/usr/share/qv2ray/lang/"); - list << QString("/usr/local/share/qv2ray/lang/"); - list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, "lang", QStandardPaths::LocateDirectory); - list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, "lang", QStandardPaths::LocateDirectory); -#elif defined(Q_OS_MAC) - // macOS platform directories. - list << QDir(QApplication::applicationDirPath() + "/../Resources/lang").absolutePath(); -#else - // This is the default behavior on Windows - list << QApplication::applicationDirPath() + "/lang"; #endif return list; }; diff --git a/src/components/pac/QvGFWPACConverter.cpp b/src/components/pac/QvGFWPACConverter.cpp deleted file mode 100644 index 30e0e9e8..00000000 --- a/src/components/pac/QvGFWPACConverter.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* ORIGINAL LICENSE: Do What The F*ck You Want To Public License - * AUTHOR: LBYPatrick - * - * MODIFIED BY Leroy.H.Y @lhy0403 re-licenced under GPLv3 - */ - -#include "common/QvHelpers.hpp" - -namespace Qv2ray::components::pac -{ - // Private function - string getRawDomain(string originLine) - { - size_t startPosition = 0; - size_t endPosition = originLine.size(); - string returnBuffer; - bool skipRule1 = originLine.find("[") != string::npos; // [Auto xxxx... - bool skipRule2 = originLine.find("!") != string::npos; // Comments - bool skipRule3 = originLine.find("@") != string::npos; // Non-proxy Lines - bool skipRule4 = originLine.find("*") != string::npos; - bool passRule1 = originLine.find("|") != string::npos; // Proxy Lines - bool passRule2 = originLine.find(".") != string::npos; // Link-Contained Lines - - if (originLine[endPosition] == '\n') - { - endPosition -= 1; - } - - if (originLine.find("http://") != string::npos) - { - startPosition += 8; - } - else if (originLine.find("https://") != string::npos) - { - startPosition += 9; - } - - // Skip unrelated lines - if (skipRule1 || skipRule2 || skipRule3 || skipRule4) - { - return ""; - } - else if (passRule2) - { - if (passRule1) - { - startPosition += originLine.find_last_of("|") + 1; - } - - if (originLine[startPosition] == '\n') - startPosition += 1; - - for (size_t i = startPosition; i < endPosition; ++i) - { - returnBuffer += originLine[i]; - } - } - - return returnBuffer; - } - - QString ConvertGFWToPAC(const QString &rawContent, const QString &customProxyString) - { - auto rawFileContent = Base64Decode(rawContent).toStdString(); - string readBuffer = ""; // cleanup - string writeBuffer; - string domainListCache = ""; - - for (size_t i = 0; i < rawFileContent.size(); ++i) - { - readBuffer += rawFileContent[i]; - - if (rawFileContent[i + 1] == '\n') - { - writeBuffer = getRawDomain(readBuffer); - - if (writeBuffer != "") - { - domainListCache += writeBuffer + "\n"; - } - - readBuffer = ""; - i += 1; - } - } - - size_t rotatorTwo = 0; - string readDomainBuffer = ""; - bool isFirstLine = true; - string outputContent = ""; - // Header - outputContent += "var domains = {\n"; - - // Read and process output content line by line - while (rotatorTwo < domainListCache.size()) - { - while (true) - { - // Get Domain - readDomainBuffer += domainListCache[rotatorTwo]; - - if (domainListCache[rotatorTwo + 1] == '\n') - { - rotatorTwo += 2; - break; - } - - rotatorTwo++; - } - - // Format - if (!isFirstLine) - outputContent += ",\n"; - else - isFirstLine = false; - - outputContent += "\t\""; - outputContent += readDomainBuffer; - outputContent += "\" : 1"; - readDomainBuffer = ""; - } - - // End Message - outputContent += NEWLINE "};" NEWLINE "" NEWLINE " var proxy = \"" + customProxyString.toStdString() + ";\";" + - NEWLINE " var direct = 'DIRECT;';" NEWLINE " function FindProxyForURL(url, host) {" NEWLINE - " var suffix;" NEWLINE " var pos = host.lastIndexOf('.');" NEWLINE - " pos = host.lastIndexOf('.', pos - 1);" NEWLINE " //" NEWLINE " while (1) {" NEWLINE - " if (domains[host] != undefined) {" NEWLINE " return proxy;" NEWLINE - " }" NEWLINE " else if (pos <= 0) {" NEWLINE - " return domains['.' + host] != undefined ? proxy : direct;" NEWLINE " }" NEWLINE - " suffix = host.substring(pos);" NEWLINE " if (domains[suffix] != undefined) {" NEWLINE - " return proxy;" NEWLINE " }" NEWLINE - " pos = host.lastIndexOf('.', pos - 1);" NEWLINE " }" NEWLINE " }"; - // - return QString::fromStdString(outputContent); - } -} // namespace Qv2ray::components::pac diff --git a/src/components/pac/QvPACHandler.cpp b/src/components/pac/QvPACHandler.cpp deleted file mode 100644 index 7b727fbf..00000000 --- a/src/components/pac/QvPACHandler.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include "QvPACHandler.hpp" - -#include "3rdparty/cpp-httplib/httplib.h" -#include "common/QvHelpers.hpp" -#include "core/CoreUtils.hpp" - -namespace Qv2ray::components::pac -{ - PACServer::PACServer(QObject *parent) : QThread(parent) - { - server = new httplib::Server(); - connect(this, &QThread::finished, this, &QThread::deleteLater); - } - PACServer::~PACServer() - { - wait(); - DEBUG(MODULE_PROXY, "~PACServer") - delete server; - } - void PACServer::stopServer() - { - if (server->is_running()) - server->stop(); - quit(); - LOG(MODULE_UI, "Stopping PAC server") - } - void PACServer::run() - { - LOG(MODULE_PROXY, "Starting PAC listener") - // - auto address = GlobalConfig.inboundConfig.listenip; - auto port = GlobalConfig.inboundConfig.pacConfig.port; - // - DEBUG(MODULE_PROXY, "PAC Listening local endpoint: " + address + ":" + QSTRN(port)) - // - QString gfwContent = StringFromFile(QV2RAY_RULES_GFWLIST_PATH); - pacContent = ConvertGFWToPAC(gfwContent, proxyString); - // - server->Get("/pac", pacRequestHandler); - auto result = server->listen(address.toStdString().c_str(), static_cast(port)); - if (result) - { - DEBUG(MODULE_PROXY, "PAC handler stopped.") - } - else - { - LOG(MODULE_PROXY, "Failed to listen on port " + QSTRN(port) + ", possible permission denied.") - QvMessageBoxWarn(nullptr, tr("PAC Handler"), tr("Failed to listen PAC request on this port, please verify the permissions")); - } - } - - void PACServer::pacRequestHandler(const httplib::Request &req, httplib::Response &rsp) - { - rsp.set_header("Server", "Qv2ray/" QV2RAY_VERSION_STRING " PAC_Handler"); - if (req.method == "GET") - { - if (req.path == "/pac") - { - DEBUG(MODULE_PROXY, "Serving PAC file request.") - // - rsp.status = 200; - rsp.set_content(pacContent.toStdString(), "application/javascript; charset=utf-8"); - DEBUG(MODULE_PROXY, "Serving a pac file...") - } - else - { - rsp.status = 404; - rsp.set_content("NOT FOUND", "text/plain; charset=utf-8"); - } - } - else - { - rsp.status = 405; - rsp.set_content("PAC ONLY SUPPORT GET", "text/plain; charset=utf-8"); - } - } -} // namespace Qv2ray::components::pac diff --git a/src/components/pac/QvPACHandler.hpp b/src/components/pac/QvPACHandler.hpp deleted file mode 100644 index cf4b238a..00000000 --- a/src/components/pac/QvPACHandler.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace httplib -{ - class Server; - struct Request; - struct Response; -} // namespace httplib - -namespace Qv2ray::components::pac -{ - QString ConvertGFWToPAC(const QString &rawContent, const QString &customProxyString); - class PACServer : public QThread - { - Q_OBJECT - public: - explicit PACServer(QObject *parent = nullptr); - ~PACServer(); - inline void setPACProxyString(const QString &proxyStr) - { - proxyString = proxyStr; - } - void stopServer(); - - private: - void run() override; - QString proxyString; - - private: - httplib::Server *server; - static void pacRequestHandler(const httplib::Request &req, httplib::Response &rsp); - static inline QString pacContent; - }; -} // namespace Qv2ray::components::pac - -using namespace Qv2ray::components; -using namespace Qv2ray::components::pac; diff --git a/src/components/plugins/QvPluginHost.cpp b/src/components/plugins/QvPluginHost.cpp new file mode 100644 index 00000000..194382ba --- /dev/null +++ b/src/components/plugins/QvPluginHost.cpp @@ -0,0 +1,370 @@ +#include "QvPluginHost.hpp" + +#include "base/Qv2rayBase.hpp" +#include "base/Qv2rayLog.hpp" +#include "common/QvHelpers.hpp" +#include "core/settings/SettingsBackend.hpp" + +#include + +namespace Qv2ray::components::plugins +{ + QvPluginHost::QvPluginHost(QObject *parent) : QObject(parent) + { + if (!StartupOption.noPlugins) + { + if (auto dir = QDir(QV2RAY_PLUGIN_SETTINGS_DIR); !dir.exists()) + { + dir.mkpath(QV2RAY_PLUGIN_SETTINGS_DIR); + } + InitializePluginHost(); + } + else + { + LOG(MODULE_PLUGINHOST, "PluginHost initilization skipped by command line option.") + } + } + + int QvPluginHost::RefreshPluginList() + { + ClearPlugins(); + LOG(MODULE_PLUGINHOST, "Reloading plugin list") + for (const auto &pluginDirPath : Qv2rayAssetsPaths("plugins")) + { + const QStringList entries = QDir(pluginDirPath).entryList(QDir::Files); + for (const auto &fileName : entries) + { + DEBUG(MODULE_PLUGINHOST, "Loading plugin: " + fileName + " from: " + pluginDirPath) + // + QvPluginInfo info; + auto pluginFullPath = QDir(pluginDirPath).absoluteFilePath(fileName); + info.libraryPath = pluginFullPath; + info.pluginLoader = new QPluginLoader(pluginFullPath, this); + // auto meta = pluginLoader.metaData(); + // You should not call "delete" on this object, it's handled by the QPluginLoader + QObject *plugin = info.pluginLoader->instance(); + if (plugin == nullptr) + { + LOG(MODULE_PLUGINHOST, info.pluginLoader->errorString()); + continue; + } + info.pluginInterface = qobject_cast(plugin); + if (info.pluginInterface == nullptr) + { + LOG(MODULE_PLUGINHOST, "Failed to cast from QObject to Qv2rayPluginInterface") + 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") + QvMessageBoxWarn(nullptr, tr("Cannot load plugin"), + info.metadata.Name + " " + tr("cannot be loaded.") + 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; + } + 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 + "\"") + plugins.insert(info.metadata.InternalName, info); + } + } + return plugins.count(); + } + + void QvPluginHost::QvPluginLog(const QString &log) + { + auto _sender = sender(); + if (auto _interface = qobject_cast(_sender); _interface) + { + LOG(MODULE_PLUGINCLIENT + "-" + _interface->GetMetadata().InternalName, log) + } + else + { + LOG(MODULE_PLUGINHOST, "UNKNOWN CLIENT: " + log) + } + } + + void QvPluginHost::QvPluginMessageBox(const QString &msg) + { + auto _sender = sender(); + if (auto _interface = qobject_cast(_sender); _interface) + { + QvMessageBoxWarn(nullptr, _interface->GetMetadata().Name, msg); + } + else + { + QvMessageBoxWarn(nullptr, "Unknown Plugin", msg); + } + } + + bool QvPluginHost::GetPluginEnableState(const QString &internalName) const + { + return GlobalConfig.pluginConfig.pluginStates[internalName]; + } + + void QvPluginHost::SetPluginEnableState(const QString &internalName, bool isEnabled) + { + LOG(MODULE_PLUGINHOST, "Set plugin: \"" + internalName + "\" enable state: " + (isEnabled ? "true" : "false")) + GlobalConfig.pluginConfig.pluginStates[internalName] = isEnabled; + if (isEnabled && !plugins[internalName].isLoaded) + { + // 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.")); + } + } + + void QvPluginHost::InitializePluginHost() + { + RefreshPluginList(); + for (auto &plugin : plugins.keys()) + { + InitializePlugin(plugin); + } + } + + void QvPluginHost::ClearPlugins() + { + for (auto &&plugin : plugins) + { + DEBUG(MODULE_PLUGINHOST, "Unloading: \"" + plugin.metadata.Name + "\"") + plugin.pluginLoader->unload(); + plugin.pluginLoader->deleteLater(); + } + plugins.clear(); + } + bool QvPluginHost::InitializePlugin(const QString &internalName) + { + auto &plugin = plugins[internalName]; + if (plugin.isLoaded) + { + LOG(MODULE_PLUGINHOST, "The plugin: \"" + internalName + "\" has already been loaded.") + return true; + } + if (!GlobalConfig.pluginConfig.pluginStates.contains(internalName)) + { + // If not contained, default to disable. + GlobalConfig.pluginConfig.pluginStates[internalName] = false; + } + // If the plugin is disabled + if (!GlobalConfig.pluginConfig.pluginStates[internalName]) + { + LOG(MODULE_PLUGINHOST, "Cannot load a plugin that's been disabled.") + return false; + } + + auto conf = JsonFromString(StringFromFile(QV2RAY_PLUGIN_SETTINGS_DIR + internalName + ".conf")); + plugins[internalName].pluginInterface->Initialize(QV2RAY_PLUGIN_SETTINGS_DIR + internalName + "/", conf); + plugins[internalName].isLoaded = true; + return true; + } + + QvPluginHost::~QvPluginHost() + { + for (auto name : plugins.keys()) + { + if (plugins[name].isLoaded) + { + LOG(MODULE_PLUGINHOST, "Saving plugin settings for: \"" + name + "\"") + auto &conf = plugins[name].pluginInterface->GetSettngs(); + StringToFile(JsonToString(conf), QV2RAY_PLUGIN_SETTINGS_DIR + name + ".conf"); + } + } + ClearPlugins(); + } + + // ================== BEGIN SEND EVENTS ================== + void QvPluginHost::Send_ConnectionStatsEvent(const Events::ConnectionStats::EventObject &object) + { + for (auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.Capabilities.contains(CAPABILITY_STATS)) + { + plugin.pluginInterface->GetEventHandler()->ProcessEvent_ConnectionStats(object); + } + } + } + void QvPluginHost::Send_ConnectivityEvent(const Events::Connectivity::EventObject &object) + { + for (auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.Capabilities.contains(CAPABILITY_CONNECTIVITY)) + { + plugin.pluginInterface->GetEventHandler()->ProcessEvent_Connectivity(object); + } + } + } + void QvPluginHost::Send_ConnectionEvent(const Events::ConnectionEntry::EventObject &object) + { + for (auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.Capabilities.contains(CAPABILITY_CONNECTION_ENTRY)) + { + plugin.pluginInterface->GetEventHandler()->ProcessEvent_ConnectionEntry(object); + } + } + } + void QvPluginHost::Send_SystemProxyEvent(const Events::SystemProxy::EventObject &object) + { + for (auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.Capabilities.contains(CAPABILITY_SYSTEM_PROXY)) + { + plugin.pluginInterface->GetEventHandler()->ProcessEvent_SystemProxy(object); + } + } + } + + const QList QvPluginHost::GetOutboundEditorWidgets() const + { + QList data; + for (const auto &plugin : plugins) + { + if (!plugin.isLoaded) + continue; + auto editor = plugin.pluginInterface->GetEditorWidget(UI_TYPE::UI_TYPE_OUTBOUND_EDITOR); + if (editor) + { + data.append(editor.release()); + } + } + return data; + } + + const QMultiHash> QvPluginHost::TryDeserializeShareLink(const QString &sharelink, // + QString *prefix, // + QString *errMessage, // + QString *newGroupName, // + bool *status) const + { + Q_UNUSED(newGroupName) + QMultiHash> data; + *status = true; + for (const auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR)) + { + auto serializer = plugin.pluginInterface->GetSerializer(); + bool thisPluginCanHandle = false; + for (const auto &prefix : serializer->ShareLinkPrefixes()) + { + thisPluginCanHandle = thisPluginCanHandle || sharelink.startsWith(prefix); + } + if (thisPluginCanHandle) + { + auto [protocol, outboundSettings] = serializer->DeserializeOutbound(sharelink, prefix, errMessage); + *status = *status && errMessage->isEmpty(); + data.insert(*prefix, { protocol, outboundSettings }); + } + } + } + return data; + } + const QvPluginOutboundInfoObject QvPluginHost::TryGetOutboundInfo(const QString &protocol, const QJsonObject &o, bool *status) const + { + *status = false; + for (const auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR)) + { + auto serializer = plugin.pluginInterface->GetSerializer(); + if (serializer->OutboundProtocols().contains(protocol)) + { + auto info = serializer->GetOutboundInfo(protocol, o); + *status = true; + return info; + } + } + } + return {}; + } + const QString QvPluginHost::TrySerializeShareLink(const QString &protocol, // + const QJsonObject &outboundSettings, // + const QString &alias, // + const QString &groupName, // + bool *status) const + { + *status = false; + for (const auto &plugin : plugins) + { + if (plugin.isLoaded && plugin.metadata.SpecialPluginType.contains(SPECIAL_TYPE_SERIALIZOR)) + { + auto serializer = plugin.pluginInterface->GetSerializer(); + if (serializer->OutboundProtocols().contains(protocol)) + { + auto link = serializer->SerializeOutbound(protocol, alias, groupName, outboundSettings); + *status = true; + return link; + } + } + } + return ""; + } + + 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()) + { + kernels.insert(cap.protocol, kern); + } + } + } + return kernels; + } + + const QString GetPluginTypeString(const SPECIAL_TYPE_FLAGS &types) + { + QStringList typesList; + if (types.isEmpty()) + { + typesList << QObject::tr("Normal Plugin"); + } + for (auto type : types) + { + switch (type) + { + case SPECIAL_TYPE_KERNEL: typesList << QObject::tr("Kernel"); break; + case SPECIAL_TYPE_SERIALIZOR: typesList << QObject::tr("Share Link Parser"); break; + default: typesList << QObject::tr("Unknown type."); break; + } + } + return typesList.join(NEWLINE); + } + + const QString GetPluginCapabilityString(const CAPABILITY_FLAGS &caps) + { + QStringList capsString; + if (caps.isEmpty()) + { + capsString << QObject::tr("No Capability"); + } + for (auto cap : caps) + { + switch (cap) + { + case CAPABILITY_CONNECTIVITY: capsString << QObject::tr("Connection State Change"); break; + case CAPABILITY_CONNECTION_ENTRY: capsString << QObject::tr("Connection Change"); break; + case CAPABILITY_STATS: capsString << QObject::tr("Statistics Event"); break; + case CAPABILITY_SYSTEM_PROXY: capsString << QObject::tr("System Proxy Event"); break; + default: capsString << QObject::tr("Unknown"); break; + } + } + return capsString.join(NEWLINE); + } +} // namespace Qv2ray::components::plugins diff --git a/src/components/plugins/QvPluginHost.hpp b/src/components/plugins/QvPluginHost.hpp new file mode 100644 index 00000000..a0c7a981 --- /dev/null +++ b/src/components/plugins/QvPluginHost.hpp @@ -0,0 +1,99 @@ +#pragma once +#include "components/plugins/interface/QvPluginInterface.hpp" + +#include +#include +#include + +class QPluginLoader; + +using namespace Qv2rayPlugin; +namespace Qv2ray::components::plugins +{ + struct QvPluginInfo + { + bool isLoaded = false; + QString libraryPath; + QvPluginMetadata metadata; + QPluginLoader *pluginLoader; + Qv2rayInterface *pluginInterface; + }; + + class QvPluginHost : public QObject + { + Q_OBJECT + public: + explicit QvPluginHost(QObject *parent = nullptr); + ~QvPluginHost(); + // + bool GetPluginEnableState(const QString &internalName) const; + void SetPluginEnableState(const QString &internalName, bool isEnabled); + // + bool inline GetPluginLoadState(const QString &internalName) const + { + return plugins.value(internalName).isLoaded; + } + const inline QString GetPluginLibraryPath(const QString &internalName) const + { + return plugins.value(internalName).libraryPath; + } + const inline QStringList AvailablePlugins() const + { + return plugins.keys(); + } + inline std::unique_ptr GetPluginSettingsWidget(const QString &internalName) const + { + return plugins.value(internalName).pluginInterface->GetSettingsWidget(); + } + const inline QJsonObject GetPluginSettings(const QString &internalName) const + { + return plugins.value(internalName).pluginInterface->GetSettngs(); + } + bool inline SetPluginSettings(const QString &internalName, const QJsonObject &settings) const + { + return plugins.value(internalName).pluginInterface->UpdateSettings(settings); + } + const inline QvPluginMetadata GetPluginMetadata(const QString &internalName) const + { + return plugins.value(internalName).metadata; + } + const QMap> GetPluginKernels() const; + // + const QMultiHash> TryDeserializeShareLink(const QString &sharelink, // + QString *prefix, // + QString *errMessage, // + QString *newGroupName, // + bool *status) const; + // + const QString TrySerializeShareLink(const QString &protocol, // + const QJsonObject &outboundSettings, // + const QString &alias, // + const QString &groupName, // + bool *status) const; + const QvPluginOutboundInfoObject TryGetOutboundInfo(const QString &protocol, const QJsonObject &o, bool *status) const; + const QList GetOutboundEditorWidgets() const; + // + void Send_ConnectionStatsEvent(const Events::ConnectionStats::EventObject &object); + void Send_ConnectivityEvent(const Events::Connectivity::EventObject &object); + void Send_ConnectionEvent(const Events::ConnectionEntry::EventObject &object); + void Send_SystemProxyEvent(const Events::SystemProxy::EventObject &object); + // + private slots: + void QvPluginLog(const QString &log); + void QvPluginMessageBox(const QString &message); + + private: + void InitializePluginHost(); + int RefreshPluginList(); + bool InitializePlugin(const QString &internalName); + void ClearPlugins(); + // Internal name, plugin info + QHash plugins; + }; + + const QString GetPluginTypeString(const SPECIAL_TYPE_FLAGS &types); + const QString GetPluginCapabilityString(const CAPABILITY_FLAGS &caps); + inline ::Qv2ray::components::plugins::QvPluginHost *PluginHost = nullptr; +} // namespace Qv2ray::components::plugins + +using namespace Qv2ray::components::plugins; diff --git a/src/components/plugins/interface b/src/components/plugins/interface new file mode 160000 index 00000000..d37c7ea9 --- /dev/null +++ b/src/components/plugins/interface @@ -0,0 +1 @@ +Subproject commit d37c7ea9459956dc459610e98b821d4a790cb6e8 diff --git a/src/components/plugins/toolbar/QvToolbar.cpp b/src/components/plugins/toolbar/QvToolbar.cpp index 6c6cde88..967bfd22 100644 --- a/src/components/plugins/toolbar/QvToolbar.cpp +++ b/src/components/plugins/toolbar/QvToolbar.cpp @@ -89,15 +89,15 @@ namespace Qv2ray::components::plugins case 104: { // Current Connection Name - CL.Message = GetDisplayName(ConnectionManager->CurrentConnection()); + CL.Message = GetDisplayName(KernelInstance->CurrentConnection()); break; } case 105: { // Current Connection Status - CL.Message = ConnectionManager->CurrentConnection() == NullConnectionId ? QObject::tr("Not connected") : - QObject::tr("Connected"); + CL.Message = KernelInstance->CurrentConnection() == NullConnectionId ? QObject::tr("Not connected") : + QObject::tr("Connected"); break; } @@ -132,14 +132,14 @@ namespace Qv2ray::components::plugins case 301: { // Total Upload - CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount(ConnectionManager->CurrentConnection()))); + CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount(KernelInstance->CurrentConnection()))); break; } case 302: { // Total download - CL.Message = FormatBytes(get<1>(GetConnectionUsageAmount(ConnectionManager->CurrentConnection()))); + CL.Message = FormatBytes(get<1>(GetConnectionUsageAmount(KernelInstance->CurrentConnection()))); break; } @@ -160,7 +160,7 @@ namespace Qv2ray::components::plugins case 305: { // Connection latency - CL.Message = QSTRN(GetConnectionLatency(ConnectionManager->CurrentConnection())) + " ms"; + CL.Message = QSTRN(GetConnectionLatency(KernelInstance->CurrentConnection())) + " ms"; break; } default: diff --git a/src/components/plugins/toolbar/QvToolbar_linux.cpp b/src/components/plugins/toolbar/QvToolbar_linux.cpp index 1fa19908..9fdd2a4b 100644 --- a/src/components/plugins/toolbar/QvToolbar_linux.cpp +++ b/src/components/plugins/toolbar/QvToolbar_linux.cpp @@ -43,7 +43,7 @@ namespace Qv2ray::components::plugins::Toolbar } catch (...) { - LOG(MODULE_PLUGIN, "Closing a broken socket.") + LOG(MODULE_PLUGINHOST, "Closing a broken socket.") } } void DataMessageQThread() @@ -66,8 +66,8 @@ namespace Qv2ray::components::plugins::Toolbar while (!isExiting) { bool result = server->waitForNewConnection(5000, &timeOut); - DEBUG(MODULE_PLUGIN, "Plugin thread listening failed: " + server->errorString()) - DEBUG(MODULE_PLUGIN, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false")) + DEBUG(MODULE_PLUGINHOST, "Plugin thread listening failed: " + server->errorString()) + DEBUG(MODULE_PLUGINHOST, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false")) } server->close(); @@ -85,7 +85,7 @@ namespace Qv2ray::components::plugins::Toolbar if (linuxWorkerThread->isRunning()) { - LOG(MODULE_PLUGIN, "Waiting for linuxWorkerThread to stop.") + LOG(MODULE_PLUGINHOST, "Waiting for linuxWorkerThread to stop.") linuxWorkerThread->wait(); } diff --git a/src/components/plugins/toolbar/QvToolbar_win.cpp b/src/components/plugins/toolbar/QvToolbar_win.cpp index 718c625e..495a8660 100644 --- a/src/components/plugins/toolbar/QvToolbar_win.cpp +++ b/src/components/plugins/toolbar/QvToolbar_win.cpp @@ -28,7 +28,7 @@ namespace Qv2ray::components::plugins::Toolbar if (hThread == nullptr) { - LOG(MODULE_PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError())) return; } else @@ -52,7 +52,7 @@ namespace Qv2ray::components::plugins::Toolbar if (hPipe == INVALID_HANDLE_VALUE) { - LOG(MODULE_PLUGIN, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError())) return static_cast(-1); } @@ -60,12 +60,12 @@ namespace Qv2ray::components::plugins::Toolbar if (fConnected) { - LOG(MODULE_PLUGIN, "Client connected, creating a processing thread") + LOG(MODULE_PLUGINHOST, "Client connected, creating a processing thread") ThreadHandle = CreateThread(nullptr, 0, InstanceThread, hPipe, 0, &dwThreadId); if (ThreadHandle == nullptr) { - LOG(MODULE_PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError())) return static_cast(-1); } else @@ -93,11 +93,11 @@ namespace Qv2ray::components::plugins::Toolbar { if (GetLastError() == ERROR_BROKEN_PIPE) { - LOG(MODULE_PLUGIN, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError())) } else { - LOG(MODULE_PLUGIN, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError())) } break; @@ -120,7 +120,7 @@ namespace Qv2ray::components::plugins::Toolbar if (!fSuccess || cbReplyBytes != cbWritten) { - LOG(MODULE_PLUGIN, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError())) + LOG(MODULE_PLUGINHOST, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError())) break; } } diff --git a/src/components/proxy/QvProxyConfigurator.cpp b/src/components/proxy/QvProxyConfigurator.cpp index b9edd8f9..59bfd8b5 100644 --- a/src/components/proxy/QvProxyConfigurator.cpp +++ b/src/components/proxy/QvProxyConfigurator.cpp @@ -1,6 +1,7 @@ #include "QvProxyConfigurator.hpp" #include "common/QvHelpers.hpp" +#include "components/plugins/QvPluginHost.hpp" #ifdef Q_OS_WIN #include #include @@ -188,46 +189,30 @@ namespace Qv2ray::components::proxy } #endif - void SetSystemProxy(const QString &address, int httpPort, int socksPort, bool usePAC) + void SetSystemProxy(const QString &address, int httpPort, int socksPort) { LOG(MODULE_PROXY, "Setting up System Proxy") bool hasHTTP = (httpPort != 0); bool hasSOCKS = (socksPort != 0); - if (!(hasHTTP || hasSOCKS || usePAC)) + if (!(hasHTTP || hasSOCKS)) { LOG(MODULE_PROXY, "Nothing?") return; } - if (usePAC) + if (hasHTTP) { - LOG(MODULE_PROXY, "Qv2ray will set system proxy to use PAC file") + LOG(MODULE_PROXY, "Qv2ray will set system proxy to use HTTP") } - else - { - if (hasHTTP) - { - LOG(MODULE_PROXY, "Qv2ray will set system proxy to use HTTP") - } - if (hasSOCKS) - { - LOG(MODULE_PROXY, "Qv2ray will set system proxy to use SOCKS") - } + if (hasSOCKS) + { + LOG(MODULE_PROXY, "Qv2ray will set system proxy to use SOCKS") } #ifdef Q_OS_WIN - QString __a; - - if (usePAC) - { - __a = address; - } - else - { - __a = (hasHTTP ? "http://" : "socks5://") + address + ":" + QSTRN(httpPort); - } + QString __a = (hasHTTP ? "http://" : "socks5://") + address + ":" + QSTRN(hasHTTP ? httpPort : socksPort); LOG(MODULE_PROXY, "Windows proxy string: " + __a) auto proxyStrW = new WCHAR[__a.length() + 1]; @@ -235,7 +220,7 @@ namespace Qv2ray::components::proxy // __QueryProxyOptions(); - if (!__SetProxyOptions(proxyStrW, usePAC)) + if (!__SetProxyOptions(proxyStrW, false)) { LOG(MODULE_PROXY, "Failed to set proxy.") } @@ -243,66 +228,50 @@ namespace Qv2ray::components::proxy __QueryProxyOptions(); #elif defined(Q_OS_LINUX) QStringList actions; - auto proxyMode = usePAC ? "auto" : "manual"; + auto proxyMode = "manual"; actions << QString("gsettings set org.gnome.system.proxy mode '%1'").arg(proxyMode); bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE"; if (isKDE) { LOG(MODULE_PROXY, "KDE detected") } - // - if (usePAC) + if (isKDE) { - actions << QString("gsettings set org.gnome.system.proxy autoconfig-url '%1'").arg(address); + actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + + "/kioslaverc --group \"Proxy Settings\" --key ProxyType 1"); + } + 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) { - actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key ProxyType 2"); - - actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key \"Proxy Config Script\" " + address); + // FTP here should be scheme: ftp:// + for (auto protocol : { "http", "ftp", "https" }) + { + auto str = QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + + "/kioslaverc --group \"Proxy Settings\" --key %1Proxy \"http://%2 %3\"") + .arg(protocol) + .arg(address) + .arg(QSTRN(httpPort)); + actions << str; + } } } - else + + 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) { - actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key ProxyType 1"); - } - 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) - { - // FTP here should be scheme: ftp:// - for (auto protocol : { "http", "ftp", "https" }) - { - auto str = QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key %1Proxy \"http://%2 %3\"") - .arg(protocol) - .arg(address) - .arg(QSTRN(httpPort)); - actions << str; - } - } - } - - 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) - { - actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + - "/kioslaverc --group \"Proxy Settings\" --key socksProxy \"socks://%1 %2\"") - .arg(address) - .arg(QSTRN(socksPort)); - } + actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + + "/kioslaverc --group \"Proxy Settings\" --key socksProxy \"socks://%1 %2\"") + .arg(address) + .arg(QSTRN(socksPort)); } } @@ -326,30 +295,31 @@ namespace Qv2ray::components::proxy { LOG(MODULE_PROXY, "Setting proxy for interface: " + service) - if (usePAC) + if (hasHTTP) { - QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " on"); - QProcess::execute("/usr/sbin/networksetup -setautoproxyurl " + service + " " + address); + 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)); } - else - { - 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)); - } - if (hasSOCKS) - { - QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " on"); - QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort)); - } + if (hasSOCKS) + { + QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxystate " + service + " on"); + QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort)); } } #endif + // + // Trigger plugin events + QMap portSettings; + if (hasHTTP) + 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 }); } void ClearSystemProxy() @@ -403,5 +373,9 @@ namespace Qv2ray::components::proxy } #endif + // + // Trigger plugin events + PluginHost->Send_SystemProxyEvent( + Events::SystemProxy::EventObject{ {}, Events::SystemProxy::SystemProxyStateType::SystemProxyState_ClearProxy }); } } // namespace Qv2ray::components::proxy diff --git a/src/components/proxy/QvProxyConfigurator.hpp b/src/components/proxy/QvProxyConfigurator.hpp index f8e071d8..67bc3df8 100644 --- a/src/components/proxy/QvProxyConfigurator.hpp +++ b/src/components/proxy/QvProxyConfigurator.hpp @@ -5,7 +5,7 @@ namespace Qv2ray::components::proxy { void ClearSystemProxy(); - void SetSystemProxy(const QString &address, int http_port, int socks_port, bool usePAC); + void SetSystemProxy(const QString &address, int http_port, int socks_port); } // namespace Qv2ray::components::proxy using namespace Qv2ray::components; diff --git a/src/components/route/presets/RouteScheme_V2rayN.hpp b/src/components/route/presets/RouteScheme_V2rayN.hpp index bb662e1f..447c4074 100644 --- a/src/components/route/presets/RouteScheme_V2rayN.hpp +++ b/src/components/route/presets/RouteScheme_V2rayN.hpp @@ -142,9 +142,8 @@ namespace Qv2ray::components::route::presets::v2rayN const inline QList DomainsBlock{ "geosite:category-ads-all" }; const inline QList DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam", - "geosite:telegram", "geosite:tumblr", "geosite:speedtest", "geosite:bbc", - "domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org", - "domain:naver.com" }; + "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", diff --git a/src/components/update/UpdateChecker.cpp b/src/components/update/UpdateChecker.cpp index 93bac127..0662fb93 100644 --- a/src/components/update/UpdateChecker.cpp +++ b/src/components/update/UpdateChecker.cpp @@ -19,7 +19,7 @@ namespace Qv2ray::components QvUpdateChecker::QvUpdateChecker(QObject *parent) : QObject(parent) { requestHelper = new QvHttpRequestHelper(this); - connect(requestHelper, &QvHttpRequestHelper::httpRequestFinished, this, &QvUpdateChecker::VersionUpdate); + connect(requestHelper, &QvHttpRequestHelper::OnRequestFinished, this, &QvUpdateChecker::VersionUpdate); } QvUpdateChecker::~QvUpdateChecker() { @@ -29,7 +29,7 @@ namespace Qv2ray::components #ifndef DISABLE_AUTO_UPDATE auto updateChannel = GlobalConfig.updateConfig.updateChannel; LOG(MODULE_NETWORK, "Start checking update for channel ID: " + QSTRN(updateChannel)) - requestHelper->get(UpdateChannelLink[updateChannel]); + requestHelper->AsyncGet(UpdateChannelLink[updateChannel]); #endif } void QvUpdateChecker::VersionUpdate(QByteArray &data) diff --git a/src/core/CoreUtils.cpp b/src/core/CoreUtils.cpp index d7e6c815..282a0b7c 100644 --- a/src/core/CoreUtils.cpp +++ b/src/core/CoreUtils.cpp @@ -56,7 +56,11 @@ namespace Qv2ray::core } else { - return false; + bool status; + auto info = PluginHost->TryGetOutboundInfo(*protocol, out["settings"].toObject(), &status); + *host = info.hostName; + *port = info.port; + return status; } } @@ -159,4 +163,23 @@ namespace Qv2ray::core 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()); + } + return inboundPorts; + } + + const QMap GetConfigInboundPorts(const ConnectionId &id) + { + return GetConfigInboundPorts(ConnectionManager->GetConnectionRoot(id)); + } } // namespace Qv2ray::core diff --git a/src/core/CoreUtils.hpp b/src/core/CoreUtils.hpp index b5c1af08..3a1f24ce 100644 --- a/src/core/CoreUtils.hpp +++ b/src/core/CoreUtils.hpp @@ -43,9 +43,11 @@ 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 QMap GetConfigInboundPorts(const CONFIGROOT &root); + const QMap GetConfigInboundPorts(const ConnectionId &id); + // } // namespace Qv2ray::core using namespace Qv2ray::core; diff --git a/src/core/connection/Generation.cpp b/src/core/connection/Generation.cpp index c03299cd..a37a8fae 100644 --- a/src/core/connection/Generation.cpp +++ b/src/core/connection/Generation.cpp @@ -308,7 +308,7 @@ namespace Qv2ray::core::connection if (!root.contains("inbounds") || root.value("inbounds").toArray().empty()) { INBOUNDS inboundsList; - + QJsonObject sniffingObject{ { "enabled", false } }; // HTTP Inbound if (GlobalConfig.inboundConfig.useHTTP) { @@ -317,6 +317,7 @@ namespace Qv2ray::core::connection 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) { @@ -335,6 +336,7 @@ namespace Qv2ray::core::connection 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); diff --git a/src/core/connection/Serialization.cpp b/src/core/connection/Serialization.cpp index 2b12af8e..b9aa8908 100644 --- a/src/core/connection/Serialization.cpp +++ b/src/core/connection/Serialization.cpp @@ -2,6 +2,7 @@ #include "Generation.hpp" #include "common/QvHelpers.hpp" +#include "components/plugins/QvPluginHost.hpp" #include "core/CoreUtils.hpp" #include "core/handler/ConfigHandler.hpp" @@ -11,29 +12,61 @@ namespace Qv2ray::core::connection { QMultiHash ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName) { - QMultiHash config; + QMultiHash connectionConf; if (link.startsWith("vmess://")) { auto conf = ConvertConfigFromVMessString(link, prefix, errMessage); - config.insert(*prefix, conf); + // + if (GlobalConfig.advancedConfig.setAllowInsecureCiphers || GlobalConfig.advancedConfig.setAllowInsecure) + { + 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; + } + // + connectionConf.insert(*prefix, conf); } else if (link.startsWith("ss://")) { auto conf = ConvertConfigFromSSString(link, prefix, errMessage); - config.insert(*prefix, conf); + connectionConf.insert(*prefix, conf); } else if (link.startsWith("ssd://")) { QStringList errMessageList; - config = ConvertConfigFromSSDString(link, newGroupName, &errMessageList); + connectionConf = ConvertConfigFromSSDString(link, newGroupName, &errMessageList); *errMessage = errMessageList.join(NEWLINE); } else { - *errMessage = QObject::tr("Unsupported share link format."); + bool ok = false; + auto configs = PluginHost->TryDeserializeShareLink(link, prefix, errMessage, newGroupName, &ok); + for (const auto &key : configs.keys()) + { + 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); + } + } + if (!ok) + { + *errMessage = QObject::tr("Unsupported share link format."); + } } - return config; + return connectionConf; } const QString ConvertConfigToString(const ConnectionId &id, bool isSip002) @@ -45,51 +78,51 @@ namespace Qv2ray::core::connection return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER; } auto server = ConnectionManager->GetConnectionRoot(id); - return ConvertConfigToString(alias, server, isSip002); + return ConvertConfigToString(alias, GetDisplayName(GetConnectionGroupId(id)), server, isSip002); } - const QString ConvertConfigToString(const QString &alias, const CONFIGROOT &server, bool isSip002) + const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002) { - OUTBOUND outbound = OUTBOUND(server["outbounds"].toArray().first().toObject()); - auto type = outbound["protocol"].toString(); + const auto outbound = OUTBOUND(server["outbounds"].toArray().first().toObject()); + const auto type = outbound["protocol"].toString(); + const auto &settings = outbound["settings"].toObject(); QString sharelink = ""; - if (type == "vmess") { - auto vmessServer = - StructFromJsonString(JsonToString(outbound["settings"].toObject()["vnext"].toArray().first().toObject())); + auto vmessServer = StructFromJsonString(JsonToString(settings["vnext"].toArray().first().toObject())); auto transport = StructFromJsonString(JsonToString(outbound["streamSettings"].toObject())); sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias); } else if (type == "shadowsocks") { - auto ssServer = StructFromJsonString( - JsonToString(outbound["settings"].toObject()["servers"].toArray().first().toObject())); + auto ssServer = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002); } else { - if (!type.isEmpty()) + if (type.isEmpty()) { - // DEBUG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + type) + DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") } else { - DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") + bool ok = false; + sharelink = PluginHost->TrySerializeShareLink(type, settings, alias, groupName, &ok); + Q_UNUSED(ok) } } return sharelink; } - QString DecodeSubscriptionString(QByteArray arr) + 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.startsWith("vmess://") ? result : Base64Decode(result); + return result.contains("://") ? result : Base64Decode(result); } } // namespace Serialization } // namespace Qv2ray::core::connection diff --git a/src/core/connection/Serialization.hpp b/src/core/connection/Serialization.hpp index 06068791..9db716ac 100644 --- a/src/core/connection/Serialization.hpp +++ b/src/core/connection/Serialization.hpp @@ -15,11 +15,11 @@ namespace Qv2ray::core::connection inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); // // General - QString DecodeSubscriptionString(QByteArray arr); + 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 CONFIGROOT &server, bool isSip002); + const QString ConvertConfigToString(const QString &alias, const QString &groupName, const CONFIGROOT &server, bool isSip002); // // VMess URI Protocol namespace vmess diff --git a/src/core/connection/Serialization_ss.cpp b/src/core/connection/Serialization_ss.cpp index 4f7c5e39..86259b59 100644 --- a/src/core/connection/Serialization_ss.cpp +++ b/src/core/connection/Serialization_ss.cpp @@ -2,7 +2,6 @@ #include "Serialization.hpp" #include "common/QvHelpers.hpp" #include "core/CoreUtils.hpp" -#include "core/handler/ConfigHandler.hpp" namespace Qv2ray::core::connection { diff --git a/src/core/connection/Serialization_ssd.cpp b/src/core/connection/Serialization_ssd.cpp index 8c43523b..f207c3d4 100644 --- a/src/core/connection/Serialization_ssd.cpp +++ b/src/core/connection/Serialization_ssd.cpp @@ -83,7 +83,7 @@ namespace Qv2ray::core::connection::Serialization } // decode base64 - const auto ssdURIBody = QStringRef(&uri, 6, uri.length() - 7); + const auto ssdURIBody = QStringRef(&uri, 6, uri.length() - 6); const auto decodedJSON = QByteArray::fromBase64(ssdURIBody.toUtf8()); if (decodedJSON.length() == 0) @@ -161,13 +161,12 @@ namespace Qv2ray::core::connection::Serialization { ssObject.port = port; } - else if (auto currPort = serverObject["port"].toInt(-1); port >= 0 && port <= 65535) + else if (auto currPort = serverObject["port"].toInt(-1); (currPort >= 0 && currPort <= 65535)) { ssObject.port = currPort; } else { - *logList << QObject::tr("Invalid port encountered. using fallback value."); ssObject.port = port; } @@ -186,7 +185,6 @@ namespace Qv2ray::core::connection::Serialization } else { - *logList << QObject::tr("Invalid name encountered. using fallback value."); nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port); } @@ -201,7 +199,7 @@ namespace Qv2ray::core::connection::Serialization } else if (!serverObject["ratio"].isUndefined()) { - *logList << QObject::tr("Invalid ratio encountered. using fallback value."); + //*logList << QObject::tr("Invalid ratio encountered. using fallback value."); } // format the total name of the node. @@ -209,10 +207,8 @@ namespace Qv2ray::core::connection::Serialization // appending to the total list CONFIGROOT root; OUTBOUNDS outbounds; - outbounds.append( - GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT(QList{ ssObject }), QJsonObject())); + outbounds.append(GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT({ ssObject }), {})); JADD(outbounds) - serverList.insertMulti(totalName, root); } diff --git a/src/core/handler/ConfigHandler.cpp b/src/core/handler/ConfigHandler.cpp index df1449bd..35e0cc82 100644 --- a/src/core/handler/ConfigHandler.cpp +++ b/src/core/handler/ConfigHandler.cpp @@ -1,6 +1,7 @@ #include "ConfigHandler.hpp" #include "common/QvHelpers.hpp" +#include "components/plugins/QvPluginHost.hpp" #include "core/connection/Serialization.hpp" #include "core/settings/SettingsBackend.hpp" @@ -12,7 +13,7 @@ namespace Qv2ray::core::handlers 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) + // Do not use: for (const auto &key : connections), why? for (auto i = 0; i < GlobalConfig.connections.count(); i++) { auto const &id = ConnectionId(GlobalConfig.connections.keys().at(i)); @@ -75,20 +76,20 @@ namespace Qv2ray::core::handlers groups[DefaultGroupId].displayName = tr("Default Group"); groups[DefaultGroupId].isSubscription = false; // - vCoreInstance = new V2rayKernelInstance(); - connect(vCoreInstance, &V2rayKernelInstance::OnProcessErrored, this, &QvConfigHandler::OnVCoreCrashed); - connect(vCoreInstance, &V2rayKernelInstance::OnNewStatsDataArrived, this, &QvConfigHandler::OnStatsDataArrived); - // Directly connected to a signal. - connect(vCoreInstance, &V2rayKernelInstance::OnProcessOutputReadyRead, this, &QvConfigHandler::OnVCoreLogAvailable); + kernelHandler = new KernelInstanceHandler(this); + connect(kernelHandler, &KernelInstanceHandler::OnCrashed, this, &QvConfigHandler::OnKernelCrashed_p); + connect(kernelHandler, &KernelInstanceHandler::OnStatsDataAvailable, this, &QvConfigHandler::OnStatsDataArrived_p); + connect(kernelHandler, &KernelInstanceHandler::OnKernelLogAvailable, this, &QvConfigHandler::OnKernelLogAvailable); + 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); + connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p); // - // Save per 2 minutes. - saveTimerId = startTimer(2 * 60 * 1000); + // Save per 1 minutes. + saveTimerId = startTimer(1 * 60 * 1000); // Do not ping all... - // pingAllTimerId = startTimer(5 * 60 * 1000); pingConnectionTimerId = startTimer(60 * 1000); } @@ -138,9 +139,10 @@ namespace Qv2ray::core::handlers } else if (event->timerId() == pingConnectionTimerId) { - if (currentConnectionId != NullConnectionId) + auto id = kernelHandler->CurrentConnection(); + if (id != NullConnectionId && GlobalConfig.advancedConfig.testLatencyPeriodcally) { - StartLatencyTest(currentConnectionId); + StartLatencyTest(id); } } } @@ -207,20 +209,28 @@ namespace Qv2ray::core::handlers return NullGroupId; } - - const optional QvConfigHandler::ClearConnectionUsage(const ConnectionId &id) + void QvConfigHandler::ClearGroupUsage(const GroupId &id) { - CheckConnectionExistance(id); + for (const auto &conn : groups[id].connections) + { + ClearConnectionUsage(conn); + } + } + void QvConfigHandler::ClearConnectionUsage(const ConnectionId &id) + { + CheckConnectionExistanceEx(id, nothing); connections[id].upLinkData = 0; connections[id].downLinkData = 0; emit OnStatsAvailable(id, 0, 0, 0, 0); - return {}; + PluginHost->Send_ConnectionStatsEvent({ GetDisplayName(id), 0, 0, 0, 0 }); + return; } const optional QvConfigHandler::RenameConnection(const ConnectionId &id, const QString &newName) { CheckConnectionExistance(id); OnConnectionRenamed(id, connections[id].displayName, newName); + PluginHost->Send_ConnectionEvent({ newName, connections[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed }); connections[id].displayName = newName; CHSaveConfigData_p(); return {}; @@ -232,6 +242,7 @@ namespace Qv2ray::core::handlers 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); // @@ -279,6 +290,8 @@ namespace Qv2ray::core::handlers 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 {}; @@ -308,6 +321,8 @@ namespace Qv2ray::core::handlers QDir(QV2RAY_CONNECTIONS_DIR + id.toString()).removeRecursively(); } // + PluginHost->Send_ConnectionEvent({ groups[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Deleted }); + // groups.remove(id); CHSaveConfigData_p(); emit OnGroupDeleted(id, list); @@ -321,55 +336,41 @@ namespace Qv2ray::core::handlers const optional QvConfigHandler::StartConnection(const ConnectionId &id) { CheckConnectionExistance(id); - - if (currentConnectionId != NullConnectionId) - { - StopConnection(); - } - + connections[id].lastConnected = system_clock::to_time_t(system_clock::now()); CONFIGROOT root = GetConnectionRoot(id); - return CHStartConnection_p(id, root); + return kernelHandler->StartConnection(id, root); } void QvConfigHandler::RestartConnection() // const ConnectionId &id { - auto conn = currentConnectionId; - if (conn != NullConnectionId) - { - StopConnection(); - StartConnection(conn); - } + kernelHandler->RestartConnection(); } void QvConfigHandler::StopConnection() // const ConnectionId &id { - // Currently just simply stop it. - //_UNUSED(id) - // if (currentConnectionId == id) { - //} - CHStopConnection_p(); + kernelHandler->StopConnection(); CHSaveConfigData_p(); } bool QvConfigHandler::IsConnected(const ConnectionId &id) const { - CheckConnectionExistanceEx(id, false); - return currentConnectionId == id; + return kernelHandler->isConnected(id); + } + + void QvConfigHandler::OnKernelCrashed_p(const ConnectionId &id, const QString &errMessage) + { + LOG(MODULE_CORE_HANDLER, "Kernel crashed: " + errMessage) + emit OnDisconnected(id); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected }); + emit OnKernelCrashed(id, errMessage); } QvConfigHandler::~QvConfigHandler() { LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor") - CHSaveConfigData_p(); - - if (vCoreInstance->KernelStarted) - { - vCoreInstance->StopConnection(); - LOG(MODULE_CORE_HANDLER, "Stopped connection from destructor.") - } - - delete vCoreInstance; + delete kernelHandler; delete httpHelper; + CHSaveConfigData_p(); } const CONFIGROOT QvConfigHandler::GetConnectionRoot(const ConnectionId &id) const @@ -378,7 +379,7 @@ namespace Qv2ray::core::handlers return connectionRootCache.value(id); } - void QvConfigHandler::OnLatencyDataArrived(const QvTCPingResultObject &result) + void QvConfigHandler::OnLatencyDataArrived_p(const QvTCPingResultObject &result) { CheckConnectionExistanceEx(result.connectionId, nothing); connections[result.connectionId].latency = result.avg; @@ -399,7 +400,8 @@ namespace Qv2ray::core::handlers connectionRootCache[id] = root; // emit OnConnectionModified(id); - if (!skipRestart && id == currentConnectionId) + PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Updated }); + if (!skipRestart && kernelHandler->isConnected(id)) { emit RestartConnection(); } @@ -412,6 +414,7 @@ namespace Qv2ray::core::handlers 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 }); emit OnGroupCreated(id, displayName); CHSaveConfigData_p(); return id; @@ -425,6 +428,7 @@ namespace Qv2ray::core::handlers return tr("Group does not exist"); } OnGroupRenamed(id, groups[id].displayName, newName); + PluginHost->Send_ConnectionEvent({ newName, groups[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed }); groups[id].displayName = newName; return {}; } @@ -468,7 +472,7 @@ namespace Qv2ray::core::handlers return false; } isHttpRequestInProgress = true; - auto data = httpHelper->syncget(groups[id].address, useSystemProxy); + auto data = httpHelper->Get(groups[id].address, useSystemProxy); isHttpRequestInProgress = false; return CHUpdateSubscription_p(id, data); } @@ -583,7 +587,17 @@ namespace Qv2ray::core::handlers return hasErrorOccured; } - const ConnectionId QvConfigHandler::CreateConnection(const QString &displayName, const GroupId &groupId, const CONFIGROOT &root) + void QvConfigHandler::OnStatsDataArrived_p(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed) + { + 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 ConnectionId QvConfigHandler::CreateConnection(const QString &displayName, const GroupId &groupId, const CONFIGROOT &root, + bool skipSaveConfig) { LOG(MODULE_CORE_HANDLER, "Creating new connection: " + displayName) ConnectionId newId(GenerateUuid()); @@ -592,8 +606,12 @@ namespace Qv2ray::core::handlers connections[newId].importDate = system_clock::to_time_t(system_clock::now()); connections[newId].displayName = displayName; emit OnConnectionCreated(newId, displayName); + PluginHost->Send_ConnectionEvent({ displayName, "", Events::ConnectionEntry::ConnectionEvent_Created }); UpdateConnection(newId, root); - CHSaveConfigData_p(); + if (!skipSaveConfig) + { + CHSaveConfigData_p(); + } return newId; } diff --git a/src/core/handler/ConfigHandler.hpp b/src/core/handler/ConfigHandler.hpp index 827850b0..d4220c28 100644 --- a/src/core/handler/ConfigHandler.hpp +++ b/src/core/handler/ConfigHandler.hpp @@ -6,7 +6,7 @@ #include "core/CoreSafeTypes.hpp" #include "core/CoreUtils.hpp" #include "core/connection/ConnectionIO.hpp" -#include "core/kernel/KernelInteractions.hpp" +#include "core/handler/KernelInstanceHandler.hpp" #define CheckIdExistance(type, id, val) \ if (!type.contains(id)) \ @@ -21,7 +21,6 @@ #define CheckConnectionExistance(id) CheckConnectionExistanceEx(id, tr("Connection does not exist")) namespace Qv2ray::core::handlers { - // class QvConfigHandler : public QObject { Q_OBJECT @@ -30,11 +29,6 @@ namespace Qv2ray::core::handlers ~QvConfigHandler(); public slots: - // - inline const ConnectionId CurrentConnection() const - { - return currentConnectionId; - } inline const QList Connections() const { return connections.keys(); @@ -80,11 +74,13 @@ namespace Qv2ray::core::handlers // // Connection Operations. bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false); - const optional ClearConnectionUsage(const ConnectionId &id); + 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); + const ConnectionId CreateConnection(const QString &displayName, const GroupId &groupId, const CONFIGROOT &root, + bool skipSaveConfig = false); // // Get Conncetion Property const CONFIGROOT GetConnectionRoot(const ConnectionId &id) const; @@ -107,10 +103,7 @@ namespace Qv2ray::core::handlers const tuple GetSubscriptionData(const GroupId &id) const; signals: - void OnCrashed(); - void OnConnected(const ConnectionId &id); - void OnDisconnected(const ConnectionId &id); - void OnVCoreLogAvailable(const ConnectionId &id, const QString &log); + 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 OnConnectionCreated(const ConnectionId &id, const QString &displayName); @@ -127,20 +120,20 @@ namespace Qv2ray::core::handlers 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); + // private slots: - void OnStatsDataArrived(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed); - void OnVCoreCrashed(const ConnectionId &id); - void OnLatencyDataArrived(const QvTCPingResultObject &data); + 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); protected: void timerEvent(QTimerEvent *event) override; private: void CHSaveConfigData_p(); - // - optional CHStartConnection_p(const ConnectionId &id, const CONFIGROOT &root); - void CHStopConnection_p(); bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData); private: @@ -155,13 +148,7 @@ namespace Qv2ray::core::handlers QvHttpRequestHelper *httpHelper; bool isHttpRequestInProgress = false; QvTCPingHelper *tcpingHelper; - // We only support one cuncurrent connection currently. -#ifdef QV2RAY_MULTIPlE_ONNECTION - QHash kernelInstances; -#else - ConnectionId currentConnectionId = NullConnectionId; - V2rayKernelInstance *vCoreInstance = nullptr; -#endif + KernelInstanceHandler *kernelHandler; }; inline ::Qv2ray::core::handlers::QvConfigHandler *ConnectionManager = nullptr; diff --git a/src/core/handler/KernelInstanceHandler.cpp b/src/core/handler/KernelInstanceHandler.cpp new file mode 100644 index 00000000..80408904 --- /dev/null +++ b/src/core/handler/KernelInstanceHandler.cpp @@ -0,0 +1,277 @@ +#include "KernelInstanceHandler.hpp" + +#include "core/CoreUtils.hpp" +#include "core/connection/Generation.hpp" +namespace Qv2ray::core::handlers +{ +#define isConnected (vCoreInstance->KernelStarted || !activeKernels.isEmpty()) + 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::OnProcessErrored, this, &KernelInstanceHandler::OnKernelCrashed_p); + // + kernels = PluginHost->GetPluginKernels(); + for (const auto &kernel : kernels) + { + connect(kernel.get(), &QvPluginKernel::OnKernelCrashed, this, &KernelInstanceHandler::OnKernelCrashed_p); + connect(kernel.get(), &QvPluginKernel::OnKernelLogAvaliable, this, &KernelInstanceHandler::OnKernelLogAvailable_p); + } + } + + KernelInstanceHandler::~KernelInstanceHandler() + { + } + + std::optional KernelInstanceHandler::StartConnection(const ConnectionId &id, const CONFIGROOT &root) + { + if (isConnected) + { + StopConnection(); + } + activeKernels.clear(); + this->root = root; + bool isComplex = IsComplexConfig(root); + auto fullConfig = GenerateRuntimeConfig(root); + inboundPorts = GetConfigInboundPorts(fullConfig); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connecting }); + QList> inboundInfo; + for (const auto &inbound_v : fullConfig["inbounds"].toArray()) + { + const auto &inbound = inbound_v.toObject(); + inboundInfo.push_back({ inbound["protocol"].toString(), inbound["port"].toInt(), inbound["tag"].toString() }); + } + // + 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; + auto pluginPort = GlobalConfig.pluginConfig.portAllocationStart; + // + /// Key = Original Outbound Tag, Value = QStringList containing new outbound lists. + for (const auto &outbound_v : fullConfig["outbounds"].toArray()) + { + const auto &outbound = outbound_v.toObject(); + const auto &outProtocol = outbound["protocol"].toString(); + // + if (!kernels.contains(outProtocol)) + { + // Normal outbound, or the one without a plugin supported. + new_outbounds.push_back(outbound); + continue; + } + LOG(MODULE_CONNECTION, "Get kernel plugin: " + outProtocol) + auto kernel = kernels[outProtocol].get(); + 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); + pluginProcessedOutboundList.append({ originalOutboundTag, inTag, freedomTag }); + pluginPort++; + } + kernel->SetConnectionSettings(GlobalConfig.inboundConfig.listenip, pluginInboundPort, outbound["settings"].toObject()); + } + fullConfig["outbounds"] = new_outbounds; + } + // + // Process routing entries + { + 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) + { + 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) + { + 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()) + { + 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), {}, Events::Connectivity::QvConnecticity_Disconnected }); + } + return result; + } + else + { + auto firstOutbound = fullConfig["outbounds"].toArray().first().toObject(); + const auto protocol = firstOutbound["protocol"].toString(); + if (kernels.contains(protocol)) + { + auto kernel = kernels[firstOutbound["protocol"].toString()].get(); + activeKernels[protocol] = kernel; + QMap pluginInboundPort; + for (const auto &[_protocol, _port, _tag] : inboundInfo) + { + pluginInboundPort[_protocol] = _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) + { + emit OnConnected(currentConnectionId); + return {}; + } + else + { + return tr("A plugin kernel failed to start. Please check the outbound settings."); + } + } + else + { + currentConnectionId = id; + lastConnectionId = id; + auto result = vCoreInstance->StartConnection(fullConfig); + if (result.has_value()) + { + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected }); + } + else + { + emit OnConnected(currentConnectionId); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), inboundPorts, Events::Connectivity::QvConnecticity_Connected }); + } + return result; + } + } + } + + void KernelInstanceHandler::RestartConnection() + { + StopConnection(); + StartConnection(lastConnectionId, root); + } + + void KernelInstanceHandler::OnKernelCrashed_p(const QString &msg) + { + StopConnection(); + emit OnCrashed(currentConnectionId, msg); + emit OnDisconnected(currentConnectionId); + lastConnectionId = currentConnectionId; + currentConnectionId = NullConnectionId; + } + + void KernelInstanceHandler::OnKernelLogAvailable_p(const QString &log) + { + emit OnKernelLogAvailable(currentConnectionId, log); + } + + void KernelInstanceHandler::StopConnection() + { + if (isConnected) + { + PluginHost->Send_ConnectivityEvent({ GetDisplayName(currentConnectionId), {}, Events::Connectivity::QvConnecticity_Disconnecting }); + if (vCoreInstance->KernelStarted) + { + vCoreInstance->StopConnection(); + } + // + for (const auto &kernel : activeKernels.keys()) + { + LOG(MODULE_CONNECTION, "Stopping plugin kernel: " + kernel) + activeKernels[kernel]->StopKernel(); + } + // Copy + ConnectionId id = currentConnectionId; + currentConnectionId = NullConnectionId; + emit OnDisconnected(id); + PluginHost->Send_ConnectivityEvent({ GetDisplayName(id), {}, Events::Connectivity::QvConnecticity_Disconnected }); + } + else + { + LOG(MODULE_CORE_HANDLER, "Cannot disconnect when there's nothing connected.") + } + } + + void KernelInstanceHandler::OnStatsDataArrived_p(const quint64 uploadSpeed, const quint64 downloadSpeed) + { + if (isConnected) + { + emit OnStatsDataAvailable(currentConnectionId, uploadSpeed, downloadSpeed); + } + } +} // namespace Qv2ray::core::handlers diff --git a/src/core/handler/KernelInstanceHandler.hpp b/src/core/handler/KernelInstanceHandler.hpp new file mode 100644 index 00000000..d766a97c --- /dev/null +++ b/src/core/handler/KernelInstanceHandler.hpp @@ -0,0 +1,56 @@ +#pragma once +#include "components/plugins/QvPluginHost.hpp" +#include "core/CoreSafeTypes.hpp" +#include "core/kernel/V2rayKernelInteractions.hpp" + +#include +#include + +namespace Qv2ray::core::handlers +{ + class KernelInstanceHandler : public QObject + { + Q_OBJECT + public: + explicit KernelInstanceHandler(QObject *parent = nullptr); + ~KernelInstanceHandler(); + + std::optional StartConnection(const ConnectionId &id, const CONFIGROOT &root); + void RestartConnection(); + void StopConnection(); + const ConnectionId CurrentConnection() const + { + return currentConnectionId; + } + bool isConnected(const ConnectionId &id) const + { + return id == currentConnectionId; + } + const QMap InboundPorts() const + { + return inboundPorts; + } + + 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); + + private slots: + void OnKernelCrashed_p(const QString &msg); + void OnKernelLogAvailable_p(const QString &log); + void OnStatsDataArrived_p(const quint64 uploadSpeed, const quint64 downloadSpeed); + + private: + QMap> kernels; + QMap activeKernels; + QMap inboundPorts; + CONFIGROOT root; + V2rayKernelInstance *vCoreInstance = nullptr; + ConnectionId currentConnectionId = NullConnectionId; + ConnectionId lastConnectionId = NullConnectionId; + }; + inline const KernelInstanceHandler *KernelInstance; +} // namespace Qv2ray::core::handlers diff --git a/src/core/handler/V2rayInstanceHandler.cpp b/src/core/handler/V2rayInstanceHandler.cpp deleted file mode 100644 index e0c43045..00000000 --- a/src/core/handler/V2rayInstanceHandler.cpp +++ /dev/null @@ -1,49 +0,0 @@ -#include "ConfigHandler.hpp" -#include "core/connection/Generation.hpp" - -optional QvConfigHandler::CHStartConnection_p(const ConnectionId &id, const CONFIGROOT &root) -{ - connections[id].lastConnected = system_clock::to_time_t(system_clock::now()); - // - auto fullConfig = GenerateRuntimeConfig(root); - auto result = vCoreInstance->StartConnection(id, fullConfig); - - if (!result.has_value()) - { - currentConnectionId = id; - emit OnConnected(currentConnectionId); - } - - return result; -} - -void QvConfigHandler::CHStopConnection_p() -{ - if (vCoreInstance->KernelStarted) - { - vCoreInstance->StopConnection(); - // Copy - ConnectionId id = currentConnectionId; - currentConnectionId = NullConnectionId; - emit OnDisconnected(id); - } - else - { - LOG(MODULE_CORE_HANDLER, "VCore is not started, not disconnecting") - } -} - -void QvConfigHandler::OnStatsDataArrived(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed) -{ - connections[id].upLinkData += uploadSpeed; - connections[id].downLinkData += downloadSpeed; - emit OnStatsAvailable(id, uploadSpeed, downloadSpeed, connections[id].upLinkData, connections[id].downLinkData); -} - -void QvConfigHandler::OnVCoreCrashed(const ConnectionId &id) -{ - LOG(MODULE_CORE_HANDLER, "V2ray core crashed!") - currentConnectionId = NullConnectionId; - emit OnDisconnected(id); - emit OnCrashed(); -} diff --git a/src/core/kernel/APIBackend.cpp b/src/core/kernel/APIBackend.cpp index cf6c24e6..96b87f6c 100644 --- a/src/core/kernel/APIBackend.cpp +++ b/src/core/kernel/APIBackend.cpp @@ -91,7 +91,7 @@ namespace Qv2ray::core::kernel qint64 value_up = 0; qint64 value_down = 0; - for (auto tag : inboundTags) + for (const auto &tag : inboundTags) { value_up += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink"); value_down += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink"); @@ -105,7 +105,6 @@ namespace Qv2ray::core::kernel if (running) { - apiFailedCounter = 0; emit OnDataReady(value_up, value_down); } @@ -169,6 +168,10 @@ namespace Qv2ray::core::kernel LOG(MODULE_VCORE, "API call returns: " + QSTRN(status.error_code()) + " (" + QString::fromStdString(status.error_message()) + ")") apiFailedCounter++; } + else + { + apiFailedCounter = 0; + } qint64 data = response.stat().value(); #else diff --git a/src/core/kernel/PluginKernelInteractions.cpp b/src/core/kernel/PluginKernelInteractions.cpp new file mode 100644 index 00000000..8b520237 --- /dev/null +++ b/src/core/kernel/PluginKernelInteractions.cpp @@ -0,0 +1 @@ +#include "PluginKernelInteractions.hpp" diff --git a/src/core/kernel/PluginKernelInteractions.hpp b/src/core/kernel/PluginKernelInteractions.hpp new file mode 100644 index 00000000..2ccd2667 --- /dev/null +++ b/src/core/kernel/PluginKernelInteractions.hpp @@ -0,0 +1,6 @@ +#pragma once +#include "components/plugins/QvPluginHost.hpp" +namespace Qv2ray::core::kernel +{ + +} diff --git a/src/core/kernel/QvKernelABIChecker.cpp b/src/core/kernel/QvKernelABIChecker.cpp index 473427cd..d2c57b5f 100644 --- a/src/core/kernel/QvKernelABIChecker.cpp +++ b/src/core/kernel/QvKernelABIChecker.cpp @@ -4,13 +4,14 @@ namespace Qv2ray::core::kernel::abi { - [[nodiscard]] QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType) + QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType) { switch (hostType) { - case ABI_WIN32: [[fallthrough]]; - case ABI_MACH_O: [[fallthrough]]; - case ABI_ELF_AARCH64: [[fallthrough]]; + case ABI_WIN32: + case ABI_MACH_O: + case ABI_ELF_AARCH64: + case ABI_ELF_ARM: 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; @@ -18,7 +19,7 @@ namespace Qv2ray::core::kernel::abi } } - [[nodiscard]] std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable) + std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable) { QFile file(pathCoreExecutable); if (!file.exists()) @@ -43,6 +44,8 @@ namespace Qv2ray::core::kernel::abi return { QvKernelABIType::ABI_ELF_X86, std::nullopt }; else if (elfInstruction == 0xB700u) return { QvKernelABIType::ABI_ELF_AARCH64, std::nullopt }; + else if (elfInstruction == 0x2800u) + return { QvKernelABIType::ABI_ELF_ARM, std::nullopt }; else return { QvKernelABIType::ABI_ELF_OTHER, std::nullopt }; } @@ -54,7 +57,7 @@ namespace Qv2ray::core::kernel::abi return { std::nullopt, QObject::tr("cannot deduce the type of core executable file %1").arg(pathCoreExecutable) }; } - [[nodiscard]] QString abiToString(QvKernelABIType abi) + QString abiToString(QvKernelABIType abi) { switch (abi) { @@ -63,6 +66,7 @@ namespace Qv2ray::core::kernel::abi case ABI_ELF_X86: return QObject::tr("ELF x86 executable"); case ABI_ELF_X86_64: return QObject::tr("ELF amd64 executable"); 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"); default: return QObject::tr("unknown abi"); } diff --git a/src/core/kernel/QvKernelABIChecker.hpp b/src/core/kernel/QvKernelABIChecker.hpp index b95f1c2d..d0346388 100644 --- a/src/core/kernel/QvKernelABIChecker.hpp +++ b/src/core/kernel/QvKernelABIChecker.hpp @@ -17,6 +17,7 @@ namespace Qv2ray::core::kernel ABI_ELF_X86, ABI_ELF_X86_64, ABI_ELF_AARCH64, + ABI_ELF_ARM, ABI_ELF_OTHER, }; @@ -38,10 +39,14 @@ namespace Qv2ray::core::kernel QvKernelABIType::ABI_WIN32; #elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_64) QvKernelABIType::ABI_ELF_AARCH64; +#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_V7) + QvKernelABIType::ABI_ELF_ARM; +#else + #error "unknown architecture" #endif - [[nodiscard]] std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable); - [[nodiscard]] QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType); - [[nodiscard]] QString abiToString(QvKernelABIType abi); + std::pair, std::optional> deduceKernelABI(const QString &pathCoreExecutable); + QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType); + QString abiToString(QvKernelABIType abi); } // namespace abi } // namespace Qv2ray::core::kernel diff --git a/src/core/kernel/KernelInteractions.cpp b/src/core/kernel/V2rayKernelInteractions.cpp similarity index 88% rename from src/core/kernel/KernelInteractions.cpp rename to src/core/kernel/V2rayKernelInteractions.cpp index 6db9fa39..5c365bc0 100644 --- a/src/core/kernel/KernelInteractions.cpp +++ b/src/core/kernel/V2rayKernelInteractions.cpp @@ -1,4 +1,4 @@ -#include "KernelInteractions.hpp" +#include "V2rayKernelInteractions.hpp" #include "APIBackend.hpp" #include "common/QvHelpers.hpp" @@ -31,33 +31,42 @@ namespace Qv2ray::core::kernel } coreFile.close(); - // Get Core ABI. auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath); if (err) { - LOG(MODULE_VCORE, "Core ABI deduction failed: " + err.value()) - *message = err.value(); + LOG(MODULE_VCORE, "Core ABI deduction failed: " + ACCESS_OPTIONAL_VALUE(err)) + *message = ACCESS_OPTIONAL_VALUE(err); return false; } - LOG(MODULE_VCORE, "Core ABI: " + kernel::abi::abiToString(abi.value())) + LOG(MODULE_VCORE, "Core ABI: " + kernel::abi::abiToString(ACCESS_OPTIONAL_VALUE(abi))) // Get Compiled ABI auto compiledABI = kernel::abi::COMPILED_ABI_TYPE; LOG(MODULE_VCORE, "Host ABI: " + kernel::abi::abiToString(compiledABI)) // Check ABI Compatibility. - switch (kernel::abi::checkCompatibility(compiledABI, abi.value())) + switch (kernel::abi::checkCompatibility(compiledABI, ACCESS_OPTIONAL_VALUE(abi))) { case kernel::abi::ABI_NOPE: + { LOG(MODULE_VCORE, "Host is incompatible with core") *message = tr("V2Ray core is incompatible with your platform.\r\n" // "Expected core ABI is %1, but got actual %2.\r\n" // "Maybe you have downloaded the wrong core?") - .arg(kernel::abi::abiToString(compiledABI), kernel::abi::abiToString(abi.value())); + .arg(kernel::abi::abiToString(compiledABI), kernel::abi::abiToString(ACCESS_OPTIONAL_VALUE(abi))); return false; - case kernel::abi::ABI_MAYBE: LOG(MODULE_VCORE, "WARNING: Host maybe incompatible with core"); [[fallthrough]]; - case kernel::abi::ABI_PERFECT: LOG(MODULE_VCORE, "Host is compatible with core"); + } + case kernel::abi::ABI_MAYBE: + { + LOG(MODULE_VCORE, "WARNING: Host maybe incompatible with core"); + break; + } + case kernel::abi::ABI_PERFECT: + { + LOG(MODULE_VCORE, "Host is compatible with core"); + break; + } } // @@ -156,17 +165,18 @@ namespace Qv2ray::core::kernel } else { - QvMessageBoxWarn(nullptr, tr("Cannot start V2ray"), - tr("V2ray core settings is incorrect.") + NEWLINE + NEWLINE + tr("The error is: ") + NEWLINE + v2rayCheckResult); + QvMessageBoxWarn(nullptr, tr("Cannot start V2ray"), // + tr("V2ray core settings is incorrect.") + NEWLINE + NEWLINE + // + tr("The error is: ") + NEWLINE + v2rayCheckResult); return false; } } - V2rayKernelInstance::V2rayKernelInstance() + V2rayKernelInstance::V2rayKernelInstance(QObject *parent) : QObject(parent) { vProcess = new QProcess(); connect(vProcess, &QProcess::readyReadStandardOutput, this, - [&]() { emit OnProcessOutputReadyRead(id, vProcess->readAllStandardOutput().trimmed()); }); + [&]() { emit OnProcessOutputReadyRead(vProcess->readAllStandardOutput().trimmed()); }); connect(vProcess, &QProcess::stateChanged, [&](QProcess::ProcessState state) { DEBUG(MODULE_VCORE, "V2ray kernel process status changed: " + QVariant::fromValue(state).toString()) @@ -175,7 +185,7 @@ namespace Qv2ray::core::kernel { LOG(MODULE_VCORE, "V2ray kernel crashed.") StopConnection(); - emit OnProcessErrored(id); + emit OnProcessErrored("V2ray kernel crashed."); } }); apiWorker = new APIWorker(); @@ -183,7 +193,7 @@ namespace Qv2ray::core::kernel KernelStarted = false; } - optional V2rayKernelInstance::StartConnection(const ConnectionId &id, const CONFIGROOT &root) + optional V2rayKernelInstance::StartConnection(const CONFIGROOT &root) { if (KernelStarted) { @@ -206,8 +216,6 @@ namespace Qv2ray::core::kernel vProcess->waitForStarted(); DEBUG(MODULE_VCORE, "V2ray core started.") KernelStarted = true; - // Set Connection ID - this->id = id; QStringList inboundTags; for (auto item : root["inbounds"].toArray()) @@ -285,6 +293,6 @@ namespace Qv2ray::core::kernel void V2rayKernelInstance::onAPIDataReady(const quint64 speedUp, const quint64 speedDown) { - emit OnNewStatsDataArrived(id, speedUp, speedDown); + emit OnNewStatsDataArrived(speedUp, speedDown); } } // namespace Qv2ray::core::kernel diff --git a/src/core/kernel/KernelInteractions.hpp b/src/core/kernel/V2rayKernelInteractions.hpp similarity index 70% rename from src/core/kernel/KernelInteractions.hpp rename to src/core/kernel/V2rayKernelInteractions.hpp index 825c15f0..2b3a1ff7 100644 --- a/src/core/kernel/KernelInteractions.hpp +++ b/src/core/kernel/V2rayKernelInteractions.hpp @@ -12,7 +12,7 @@ namespace Qv2ray::core::kernel { Q_OBJECT public: - explicit V2rayKernelInstance(); + explicit V2rayKernelInstance(QObject *parent = nullptr); ~V2rayKernelInstance() override; // // Speed @@ -21,7 +21,7 @@ namespace Qv2ray::core::kernel qulonglong getAllSpeedUp(); qulonglong getAllSpeedDown(); // - optional StartConnection(const ConnectionId &id, const CONFIGROOT &root); + optional StartConnection(const CONFIGROOT &root); void StopConnection(); bool KernelStarted = false; // @@ -29,19 +29,17 @@ namespace Qv2ray::core::kernel static bool ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message); signals: - void OnProcessErrored(const ConnectionId &id); - void OnProcessOutputReadyRead(const ConnectionId &id, const QString &output); - void OnNewStatsDataArrived(const ConnectionId &id, const quint64 speedUp, const quint64 speedDown); + void OnProcessErrored(const QString &errMessage); + void OnProcessOutputReadyRead(const QString &output); + void OnNewStatsDataArrived(const quint64 speedUp, const quint64 speedDown); - public slots: + private slots: void onAPIDataReady(const quint64 speedUp, const quint64 speedDown); private: APIWorker *apiWorker; QProcess *vProcess; bool apiEnabled; - // - ConnectionId id = NullConnectionId; }; } // namespace Qv2ray::core::kernel diff --git a/src/main.cpp b/src/main.cpp index f0da90a2..f642e15c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -4,6 +4,7 @@ #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 @@ -139,7 +140,7 @@ bool initialiseQv2ray() Qv2rayConfig conf; conf.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH)); conf.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH)); - conf.logLevel = 2; + conf.logLevel = 3; conf.uiConfig.language = QLocale::system().name(); // // Save initial config. @@ -229,7 +230,7 @@ int main(int argc, char *argv[]) #endif if (StartupOption.noScaleFactors) { - LOG(MODULE_INIT, "Force set QT_SCALE_FACTOR to 0.") + 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"); } @@ -237,6 +238,9 @@ int main(int argc, char *argv[]) { 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 } SingleApplication _qApp(argc, argv, false, SingleApplication::User | SingleApplication::ExcludeAppPath | SingleApplication::ExcludeAppVersion); @@ -250,25 +254,25 @@ int main(int argc, char *argv[]) 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 yhirose (@yhirose): cpp-httplib (MIT)" NEWLINE NEWLINE) // + 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 yhirose (@yhirose): cpp-httplib (MIT)" NEWLINE NEWLINE) // // LOG(MODULE_INIT, "Qv2ray Start Time: " + QSTRN(QTime::currentTime().msecsSinceStartOfDay())) // @@ -404,11 +408,8 @@ int main(int argc, char *argv[]) #endif //_qApp.setAttribute(Qt::AA_DontUseNativeMenuBar); // Initialise Connection Handler + PluginHost = new QvPluginHost(); ConnectionManager = new QvConfigHandler(); - // Handler for session logout, shutdown, etc. - // Will not block. - QGuiApplication::setFallbackSessionManagementEnabled(false); - QObject::connect(&_qApp, &QGuiApplication::commitDataRequest, [] { LOG(MODULE_INIT, "Quit triggered by session manager.") }); // Show MainWindow MainWindow w; QObject::connect(&_qApp, &SingleApplication::instanceStarted, [&]() { @@ -423,6 +424,7 @@ int main(int argc, char *argv[]) #endif auto rcode = _qApp.exec(); delete ConnectionManager; + delete PluginHost; LOG(MODULE_INIT, "Quitting normally") return rcode; #ifndef QT_DEBUG diff --git a/src/ui/editors/w_OutboundEditor.cpp b/src/ui/editors/w_OutboundEditor.cpp index 4fbf4be6..21724205 100644 --- a/src/ui/editors/w_OutboundEditor.cpp +++ b/src/ui/editors/w_OutboundEditor.cpp @@ -9,78 +9,53 @@ #include #include -OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), Tag(""), Mux(), vmess(), shadowsocks() +OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), tag(OUTBOUND_TAG_PROXY) { QvMessageBusConnect(OutboundEditor); setupUi(this); // - ssWidget = new StreamSettingsWidget(this); - transportFrame->addWidget(ssWidget); + outboundType = "vmess"; // - shadowsocks = ShadowSocksServerObject(); - socks = SocksServerObject(); - vmess = VMessServerObject(); - socks.users.push_back(SocksServerObject::UserObject()); - vmess.users.push_back(VMessServerObject::UserObject()); + streamSettingsWidget = new StreamSettingsWidget(this); + streamSettingsWidget->SetStreamObject({}); + transportFrame->addWidget(streamSettingsWidget); // - auto stream = StreamSettingsObject(); - ssWidget->SetStreamObject(stream); + socks.users.push_back({}); + vmess.users.push_back({}); // - OutboundType = "vmess"; - Tag = OUTBOUND_TAG_PROXY; - useFProxy = false; - ReloadGUI(); - Result = GenerateConnectionJson(); + auto pluginEditorWidgetsInfo = PluginHost->GetOutboundEditorWidgets(); + for (const auto &plugin : pluginEditorWidgetsInfo) + { + for (const auto &_d : plugin->OutboundCapabilities()) + { + outBoundTypeCombo->addItem(_d.displayName, _d.protocol); + auto index = outboundTypeStackView->addWidget(plugin); + pluginWidgets.insert(index, { _d, plugin }); + } + } + // + outboundType = "vmess"; + useForwardProxy = false; } QvMessageBusSlotImpl(OutboundEditor) { switch (msg) { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl + case UPDATE_COLORSCHEME: + { + break; + }; + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; } } -OutboundEditor::OutboundEditor(OUTBOUND outboundEntry, QWidget *parent) : OutboundEditor(parent) +OutboundEditor::OutboundEditor(const OUTBOUND &outboundEntry, QWidget *parent) : OutboundEditor(parent) { - Original = outboundEntry; - Tag = outboundEntry["tag"].toString(); - tagTxt->setText(Tag); - OutboundType = outboundEntry["protocol"].toString(); - Mux = outboundEntry["mux"].toObject(); - useFProxy = outboundEntry[QV2RAY_USE_FPROXY_KEY].toBool(false); - ssWidget->SetStreamObject(StructFromJsonString(JsonToString(outboundEntry["streamSettings"].toObject()))); - - if (OutboundType == "vmess") - { - vmess = - StructFromJsonString(JsonToString(outboundEntry["settings"].toObject()["vnext"].toArray().first().toObject())); - shadowsocks.port = vmess.port; - shadowsocks.address = vmess.address; - socks.address = vmess.address; - socks.port = vmess.port; - } - else if (OutboundType == "shadowsocks") - { - shadowsocks = StructFromJsonString( - JsonToString(outboundEntry["settings"].toObject()["servers"].toArray().first().toObject())); - vmess.address = shadowsocks.address; - vmess.port = shadowsocks.port; - socks.address = shadowsocks.address; - socks.port = shadowsocks.port; - } - else if (OutboundType == "socks") - { - socks = - StructFromJsonString(JsonToString(outboundEntry["settings"].toObject()["servers"].toArray().first().toObject())); - vmess.address = socks.address; - vmess.port = socks.port; - shadowsocks.address = socks.address; - shadowsocks.port = socks.port; - } - + originalConfig = outboundEntry; ReloadGUI(); - Result = GenerateConnectionJson(); } OutboundEditor::~OutboundEditor() @@ -90,124 +65,187 @@ OutboundEditor::~OutboundEditor() OUTBOUND OutboundEditor::OpenEditor() { int resultCode = this->exec(); - return resultCode == QDialog::Accepted ? Result : Original; + return resultCode == QDialog::Accepted ? resultConfig : originalConfig; } QString OutboundEditor::GetFriendlyName() { auto host = ipLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_"); auto port = portLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_"); - auto type = OutboundType; - QString name = Tag.isEmpty() ? host + "-[" + port + "]-" + type : Tag; + auto type = outboundType; + QString name = tag.isEmpty() ? host + "-[" + port + "]-" + type : tag; return name; } OUTBOUND OutboundEditor::GenerateConnectionJson() { OUTBOUNDSETTING settings; - auto streaming = GetRootObject(ssWidget->GetStreamSettings()); + auto streaming = GetRootObject(streamSettingsWidget->GetStreamSettings()); - if (OutboundType == "vmess") + if (outboundType == "vmess") { // VMess is only a ServerObject, and we need an array { "vnext": [] } QJsonArray vnext; + vmess.address = address; + vmess.port = port; vnext.append(GetRootObject(vmess)); settings.insert("vnext", vnext); } - else if (OutboundType == "shadowsocks") + else if (outboundType == "shadowsocks") { streaming = QJsonObject(); LOG(MODULE_CONNECTION, "Shadowsocks outbound does not need StreamSettings.") QJsonArray servers; + shadowsocks.address = address; + shadowsocks.port = port; servers.append(GetRootObject(shadowsocks)); settings["servers"] = servers; } - else if (OutboundType == "socks") + else if (outboundType == "socks") { if (!socks.users.isEmpty() && socks.users.first().user.isEmpty() && socks.users.first().pass.isEmpty()) { LOG(MODULE_UI, "Removed empty user form SOCKS settings") socks.users.clear(); } + socks.address = address; + socks.port = port; streaming = QJsonObject(); LOG(MODULE_CONNECTION, "Socks outbound does not need StreamSettings.") QJsonArray servers; servers.append(GetRootObject(socks)); settings["servers"] = servers; } + else + { + bool processed = false; + for (const auto &plugin : pluginWidgets) + { + if (plugin.first.protocol == outboundType) + { + plugin.second->SetHostInfo(address, port); + settings = OUTBOUNDSETTING(plugin.second->GetContent()); + processed = true; + break; + } + } + if (!processed) + { + QvMessageBoxWarn(this, tr("Unknown outbound type."), + tr("The specified outbound type is not supported, this may happen due to a plugin failure.")); + } + } - auto root = GenerateOutboundEntry(OutboundType, settings, streaming, Mux, "0.0.0.0", Tag); - root[QV2RAY_USE_FPROXY_KEY] = useFProxy; + auto root = GenerateOutboundEntry(outboundType, settings, streaming, muxConfig, "0.0.0.0", tag); + root[QV2RAY_USE_FPROXY_KEY] = useForwardProxy; return root; } void OutboundEditor::ReloadGUI() { - if (OutboundType == "vmess") + tag = originalConfig["tag"].toString(); + tagTxt->setText(tag); + outboundType = originalConfig["protocol"].toString("vmess"); + muxConfig = originalConfig["mux"].toObject(); + useForwardProxy = originalConfig[QV2RAY_USE_FPROXY_KEY].toBool(false); + streamSettingsWidget->SetStreamObject(StructFromJsonString(JsonToString(originalConfig["streamSettings"].toObject()))); + // + useFPCB->setChecked(useForwardProxy); + muxEnabledCB->setChecked(muxConfig["enabled"].toBool()); + muxConcurrencyTxt->setValue(muxConfig["concurrency"].toInt()); + // + const auto &settings = originalConfig["settings"].toObject(); + // + if (outboundType == "vmess") { outBoundTypeCombo->setCurrentIndex(0); - ipLineEdit->setText(vmess.address); - portLineEdit->setText(QSTRN(vmess.port)); + vmess = StructFromJsonString(JsonToString(settings["vnext"].toArray().first().toObject())); + if (vmess.users.empty()) + { + vmess.users.push_back({}); + } + address = vmess.address; + port = vmess.port; idLineEdit->setText(vmess.users.front().id); alterLineEdit->setValue(vmess.users.front().alterId); securityCombo->setCurrentText(vmess.users.front().security); } - else if (OutboundType == "shadowsocks") + else if (outboundType == "shadowsocks") { outBoundTypeCombo->setCurrentIndex(1); + shadowsocks = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); + address = shadowsocks.address; + port = shadowsocks.port; // ShadowSocks Configs - ipLineEdit->setText(shadowsocks.address); - portLineEdit->setText(QSTRN(shadowsocks.port)); ss_emailTxt->setText(shadowsocks.email); ss_levelSpin->setValue(shadowsocks.level); ss_otaCheckBox->setChecked(shadowsocks.ota); ss_passwordTxt->setText(shadowsocks.password); ss_encryptionMethod->setCurrentText(shadowsocks.method); } - else if (OutboundType == "socks") + else if (outboundType == "socks") { outBoundTypeCombo->setCurrentIndex(2); - ipLineEdit->setText(socks.address); - portLineEdit->setText(QSTRN(socks.port)); - + socks = StructFromJsonString(JsonToString(settings["servers"].toArray().first().toObject())); + address = socks.address; + port = socks.port; if (socks.users.empty()) - socks.users.push_back(SocksServerObject::UserObject()); - + { + socks.users.push_back({}); + } socks_PasswordTxt->setText(socks.users.front().pass); socks_UserNameTxt->setText(socks.users.front().user); } - - useFPCB->setChecked(useFProxy); - muxEnabledCB->setChecked(Mux["enabled"].toBool()); - muxConcurrencyTxt->setValue(Mux["concurrency"].toInt()); + else + { + bool processed = false; + for (const auto &index : pluginWidgets.keys()) + { + const auto &plugin = pluginWidgets.value(index); + if (plugin.first.protocol == outboundType) + { + plugin.second->SetContent(settings); + outBoundTypeCombo->setCurrentIndex(index); + auto [_address, _port] = plugin.second->GetHostInfo(); + address = _address; + port = _port; + processed = true; + break; + } + } + if (!processed) + { + LOG(MODULE_UI, "Outbound type: " + outboundType + " is not supported.") + QvMessageBoxWarn(this, tr("Unknown outbound."), + tr("The specified outbound type is invalid, this may be caused by a plugin failure.") + NEWLINE + + tr("Please use the JsonEditor or reload the plugin.")); + reject(); + } + } + // + ipLineEdit->setText(address); + portLineEdit->setText(QSTRN(port)); } void OutboundEditor::on_buttonBox_accepted() { - Result = GenerateConnectionJson(); + resultConfig = GenerateConnectionJson(); } void OutboundEditor::on_ipLineEdit_textEdited(const QString &arg1) { - vmess.address = arg1; - shadowsocks.address = arg1; - socks.address = arg1; + address = arg1; } void OutboundEditor::on_portLineEdit_textEdited(const QString &arg1) { - if (arg1 != "") - { - vmess.port = arg1.toInt(); - shadowsocks.port = arg1.toInt(); - socks.port = arg1.toInt(); - } + port = arg1.toInt(); } void OutboundEditor::on_idLineEdit_textEdited(const QString &arg1) { if (vmess.users.empty()) - vmess.users.push_back(VMessServerObject::UserObject()); + vmess.users.push_back({}); vmess.users.front().id = arg1; } @@ -215,43 +253,51 @@ void OutboundEditor::on_idLineEdit_textEdited(const QString &arg1) void OutboundEditor::on_securityCombo_currentIndexChanged(const QString &arg1) { if (vmess.users.empty()) - vmess.users.push_back(VMessServerObject::UserObject()); + vmess.users.push_back({}); vmess.users.front().security = arg1; } void OutboundEditor::on_tagTxt_textEdited(const QString &arg1) { - Tag = arg1; + tag = arg1; } void OutboundEditor::on_muxEnabledCB_stateChanged(int arg1) { - Mux["enabled"] = arg1 == Qt::Checked; + muxConfig["enabled"] = arg1 == Qt::Checked; } void OutboundEditor::on_muxConcurrencyTxt_valueChanged(int arg1) { - Mux["concurrency"] = arg1; + muxConfig["concurrency"] = arg1; } void OutboundEditor::on_alterLineEdit_valueChanged(int arg1) { if (vmess.users.empty()) - vmess.users.push_back(VMessServerObject::UserObject()); + vmess.users.push_back({}); vmess.users.front().alterId = arg1; } void OutboundEditor::on_useFPCB_stateChanged(int arg1) { - useFProxy = arg1 == Qt::Checked; + useForwardProxy = arg1 == Qt::Checked; } void OutboundEditor::on_outBoundTypeCombo_currentIndexChanged(int index) { + // 0, 1, 2 as built-in vmess, ss, socks outboundTypeStackView->setCurrentIndex(index); - OutboundType = outBoundTypeCombo->currentText().toLower(); + if (index < 3) + { + outboundType = outBoundTypeCombo->currentText().toLower(); + } + else + { + outboundType = pluginWidgets.value(index).first.protocol; + } } void OutboundEditor::on_ss_emailTxt_textEdited(const QString &arg1) @@ -282,13 +328,13 @@ void OutboundEditor::on_ss_otaCheckBox_stateChanged(int arg1) void OutboundEditor::on_socks_UserNameTxt_textEdited(const QString &arg1) { if (socks.users.isEmpty()) - socks.users.push_back(SocksServerObject::UserObject()); + socks.users.push_back({}); socks.users.front().user = arg1; } void OutboundEditor::on_socks_PasswordTxt_textEdited(const QString &arg1) { if (socks.users.isEmpty()) - socks.users.push_back(SocksServerObject::UserObject()); + socks.users.push_back({}); socks.users.front().pass = arg1; } diff --git a/src/ui/editors/w_OutboundEditor.hpp b/src/ui/editors/w_OutboundEditor.hpp index 34a81964..4038ee01 100644 --- a/src/ui/editors/w_OutboundEditor.hpp +++ b/src/ui/editors/w_OutboundEditor.hpp @@ -1,5 +1,6 @@ #pragma once #include "base/Qv2rayBase.hpp" +#include "components/plugins/QvPluginHost.hpp" #include "ui/messaging/QvMessageBus.hpp" #include "ui/widgets/StreamSettingsWidget.hpp" #include "ui_w_OutboundEditor.h" @@ -13,68 +14,55 @@ class OutboundEditor { Q_OBJECT public: - explicit OutboundEditor(QWidget *parent = nullptr); - explicit OutboundEditor(OUTBOUND outboundEntry, QWidget *parent = nullptr); + explicit OutboundEditor(const OUTBOUND &outboundEntry, QWidget *parent = nullptr); ~OutboundEditor(); OUTBOUND OpenEditor(); QString GetFriendlyName(); private: + explicit OutboundEditor(QWidget *parent = nullptr); QvMessageBusSlotDecl; signals: void s_reload_config(bool need_restart); private slots: void on_buttonBox_accepted(); - void on_ipLineEdit_textEdited(const QString &arg1); - void on_portLineEdit_textEdited(const QString &arg1); - void on_idLineEdit_textEdited(const QString &arg1); - void on_tagTxt_textEdited(const QString &arg1); - void on_muxEnabledCB_stateChanged(int arg1); - void on_muxConcurrencyTxt_valueChanged(int arg1); - void on_alterLineEdit_valueChanged(int arg1); - void on_useFPCB_stateChanged(int arg1); - void on_outBoundTypeCombo_currentIndexChanged(int index); - void on_ss_emailTxt_textEdited(const QString &arg1); - void on_ss_passwordTxt_textEdited(const QString &arg1); - void on_ss_encryptionMethod_currentIndexChanged(const QString &arg1); - void on_ss_levelSpin_valueChanged(int arg1); - void on_ss_otaCheckBox_stateChanged(int arg1); - void on_socks_UserNameTxt_textEdited(const QString &arg1); - void on_socks_PasswordTxt_textEdited(const QString &arg1); - void on_securityCombo_currentIndexChanged(const QString &arg1); private: - QString Tag; + QString tag; void ReloadGUI(); - bool useFProxy; + bool useForwardProxy; OUTBOUND GenerateConnectionJson(); - OUTBOUND Original; - OUTBOUND Result; - QJsonObject Mux; + OUTBOUND originalConfig; + OUTBOUND resultConfig; + QJsonObject muxConfig; // // Connection Configs - QString OutboundType; + QString outboundType; + QString address; + int port; // VMessServerObject vmess; ShadowSocksServerObject shadowsocks; SocksServerObject socks; // - StreamSettingsWidget *ssWidget; + StreamSettingsWidget *streamSettingsWidget; + // + QMap> pluginWidgets; }; diff --git a/src/ui/w_ImportConfig.cpp b/src/ui/w_ImportConfig.cpp index 7d82dd8b..95f6d991 100644 --- a/src/ui/w_ImportConfig.cpp +++ b/src/ui/w_ImportConfig.cpp @@ -5,7 +5,7 @@ #include "core/connection/ConnectionIO.hpp" #include "core/connection/Serialization.hpp" #include "core/handler/ConfigHandler.hpp" -#include "core/kernel/KernelInteractions.hpp" +#include "core/kernel/V2rayKernelInteractions.hpp" #include "ui/editors/w_JsonEditor.hpp" #include "ui/editors/w_OutboundEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp" @@ -37,7 +37,10 @@ QvMessageBusSlotImpl(ImportConfigWindow) { switch (msg) { - MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl MBUpdateColorSchemeDefaultImpl + MBShowDefaultImpl; + MBHideDefaultImpl; + MBRetranslateDefaultImpl; + MBUpdateColorSchemeDefaultImpl; } } @@ -53,7 +56,7 @@ QMultiHash ImportConfigWindow::SelectConnection(bool outbou routeEditBtn->setEnabled(!outboundsOnly); this->exec(); QMultiHash conn; - for (const auto connEntry : connections.values()) + for (const auto &connEntry : connections.values()) { conn += connEntry; } @@ -64,11 +67,11 @@ int ImportConfigWindow::ImportConnection() { this->exec(); int count = 0; - for (const auto groupName : connections.keys()) + for (const auto &groupName : connections.keys()) { GroupId groupId = groupName.isEmpty() ? DefaultGroupId : ConnectionManager->CreateGroup(groupName, false); const auto groupObject = connections[groupName]; - for (const auto connConf : groupObject) + for (const auto &connConf : groupObject) { auto connName = groupObject.key(connConf); @@ -77,7 +80,7 @@ int ImportConfigWindow::ImportConnection() { connName = protocol + "/" + host + ":" + QSTRN(port) + "-" + GenerateRandomString(5); } - ConnectionManager->CreateConnection(connName, groupId, connConf); + ConnectionManager->CreateConnection(connName, groupId, connConf, true); } } @@ -164,7 +167,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked() } else { - for (auto conf : config) + for (const auto &conf : config) { AddToGroup(newGroupName, config.key(conf), conf); } @@ -173,7 +176,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked() if (!linkErrors.isEmpty()) { - for (auto item : linkErrors) + for (const auto &item : linkErrors) { vmessConnectionStringTxt->appendPlainText(linkErrors.key(item)); errorsList->addItem(item); @@ -213,6 +216,8 @@ void ImportConfigWindow::on_selectImageBtn_clicked() imageFileEdit->setText(dir); // QFile file(dir); + if (!file.exists()) + return; file.open(QFile::OpenModeFlag::ReadOnly); auto buf = file.readAll(); file.close(); @@ -258,7 +263,7 @@ void ImportConfigWindow::on_errorsList_currentItemChanged(QListWidgetItem *curre void ImportConfigWindow::on_connectionEditBtn_clicked() { - OutboundEditor w(this); + OutboundEditor w(OUTBOUND(), this); auto outboundEntry = w.OpenEditor(); bool isChanged = w.result() == QDialog::Accepted; QString alias = w.GetFriendlyName(); @@ -283,7 +288,7 @@ void ImportConfigWindow::on_cancelImportBtn_clicked() void ImportConfigWindow::on_subscriptionButton_clicked() { hide(); - SubscriptionEditor w; + SubscriptionEditor w(this); w.exec(); auto importToComplex = !keepImportedInboundCheckBox->isEnabled(); connections.clear(); diff --git a/src/ui/w_MainWindow.cpp b/src/ui/w_MainWindow.cpp index 45e6c0c3..3ecc22ef 100644 --- a/src/ui/w_MainWindow.cpp +++ b/src/ui/w_MainWindow.cpp @@ -1,6 +1,6 @@ #include "w_MainWindow.hpp" -#include "components/pac/QvPACHandler.hpp" +#include "components/plugins/QvPluginHost.hpp" #include "components/plugins/toolbar/QvToolbar.hpp" #include "components/proxy/QvProxyConfigurator.hpp" #include "components/update/UpdateChecker.hpp" @@ -9,6 +9,7 @@ #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" @@ -50,11 +51,11 @@ QvMessageBusSlotImpl(MainWindow) void MainWindow::UpdateColorScheme() { - hTray.setIcon(QIcon(GlobalConfig.uiConfig.useDarkTrayIcon ? ":/assets/icons/ui_dark/tray.png" : ":/assets/icons/ui_light/tray.png")); + hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png")); // importConfigButton->setIcon(QICON_R("import.png")); - updownImageBox->setStyleSheet("image: url(" + QV2RAY_UI_COLORSCHEME_ROOT + "netspeed_arrow.png)"); - updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_UI_COLORSCHEME_ROOT + "netspeed_arrow.png)"); + updownImageBox->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)"); + updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)"); // tray_action_ShowHide->setIcon(this->windowIcon()); action_RCM_Start->setIcon(QICON_R("connect.png")); @@ -131,17 +132,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) UpdateColorScheme(); // // - connect(ConnectionManager, &QvConfigHandler::OnCrashed, [&] { + connect(ConnectionManager, &QvConfigHandler::OnKernelCrashed, [&](const ConnectionId &, const QString &reason) { this->show(); - QvMessageBoxWarn(this, tr("V2ray vcore terminated."), - tr("V2ray vcore terminated unexpectedly.") + NEWLINE + NEWLINE + - tr("To solve the problem, read the V2ray log in the log text browser.")); + QvMessageBoxWarn(this, tr("Kernel terminated."), + tr("The kernel terminated unexpectedly:") + NEWLINE + reason + NEWLINE + NEWLINE + + tr("To solve the problem, read the kernel log in the log text browser.")); }); // connect(ConnectionManager, &QvConfigHandler::OnConnected, this, &MainWindow::OnConnected); connect(ConnectionManager, &QvConfigHandler::OnDisconnected, this, &MainWindow::OnDisconnected); connect(ConnectionManager, &QvConfigHandler::OnStatsAvailable, this, &MainWindow::OnStatsAvailable); - connect(ConnectionManager, &QvConfigHandler::OnVCoreLogAvailable, this, &MainWindow::OnVCoreLogAvailable); + connect(ConnectionManager, &QvConfigHandler::OnKernelLogAvailable, this, &MainWindow::OnVCoreLogAvailable); // connect(ConnectionManager, &QvConfigHandler::OnConnectionDeleted, this, &MainWindow::OnConnectionDeleted); connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &MainWindow::OnConnectionCreated); @@ -229,7 +230,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) connectionListRCM_Menu->addAction(action_RCM_Delete); connect(action_RCM_Start, &QAction::triggered, this, &MainWindow::on_action_StartThis_triggered); connect(action_RCM_SetAutoConnection, &QAction::triggered, this, &MainWindow::on_action_RCM_SetAutoConnection_triggered); - connect(action_RCM_Edit, &QAction::triggered, this, &MainWindow::on_action_RCM_EditThis_triggered); connect(action_RCM_EditJson, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsJson_triggered); connect(action_RCM_EditComplex, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsComplex_triggered); @@ -357,6 +357,13 @@ void MainWindow::keyPressEvent(QKeyEvent *e) this->close(); } } + 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(); + } + } } void MainWindow::keyReleaseEvent(QKeyEvent *e) @@ -383,11 +390,6 @@ void MainWindow::on_action_StartThis_triggered() MainWindow::~MainWindow() { - if (GlobalConfig.inboundConfig.pacConfig.enablePAC && pacServer != nullptr && pacServer->isRunning()) - { - // Wait for PAC server to finish. - pacServer->wait(); - } hTray.hide(); } @@ -478,10 +480,16 @@ void MainWindow::on_connectionListWidget_customContextMenuRequested(const QPoint auto item = connectionListWidget->itemAt(connectionListWidget->mapFromGlobal(_pos)); if (item != nullptr) { - if (GetItemWidget(item)->IsConnection()) - { - connectionListRCM_Menu->popup(_pos); - } + bool isConnection = GetItemWidget(item)->IsConnection(); + // Disable connection-specific settings. + action_RCM_Start->setEnabled(isConnection); + action_RCM_SetAutoConnection->setEnabled(isConnection); + action_RCM_Edit->setEnabled(isConnection); + action_RCM_EditJson->setEnabled(isConnection); + action_RCM_EditComplex->setEnabled(isConnection); + action_RCM_Rename->setEnabled(isConnection); + action_RCM_Duplicate->setEnabled(isConnection); + connectionListRCM_Menu->popup(_pos); } } @@ -492,9 +500,16 @@ void MainWindow::on_action_RCM_DeleteThese_triggered() for (auto item : connectionListWidget->selectedItems()) { auto widget = GetItemWidget(item); - if (widget->IsConnection()) + if (widget) { - connlist.append(get<1>(widget->Identifier())); + if (widget->IsConnection()) + { + connlist.append(get<1>(widget->Identifier())); + } + else + { + connlist.append(ConnectionManager->GetGroupMetaObject(get<0>(widget->Identifier())).connections); + } } } @@ -568,13 +583,17 @@ void MainWindow::on_connectionListWidget_itemDoubleClicked(QTreeWidgetItem *item void MainWindow::OnDisconnected(const ConnectionId &id) { Q_UNUSED(id) + hTray.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; locateBtn->setEnabled(false); - this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon()); + if (!GlobalConfig.uiConfig.quietMode) + { + this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon()); + } hTray.setToolTip(TRAY_TOOLTIP_PREFIX); netspeedLabel->setText("0.00 B/s" NEWLINE "0.00 B/s"); dataamountLabel->setText("0.00 B" NEWLINE "0.00 B"); @@ -583,16 +602,12 @@ void MainWindow::OnDisconnected(const ConnectionId &id) { ClearSystemProxy(); } - - if (GlobalConfig.inboundConfig.pacConfig.enablePAC) - { - pacServer->stopServer(); - } } void MainWindow::OnConnected(const ConnectionId &id) { Q_UNUSED(id) + hTray.setIcon(Q_TRAYICON("tray-connected.png")); tray_action_Start->setEnabled(false); tray_action_Stop->setEnabled(true); tray_action_Restart->setEnabled(true); @@ -601,70 +616,14 @@ void MainWindow::OnConnected(const ConnectionId &id) locateBtn->setEnabled(true); on_clearlogButton_clicked(); auto name = GetDisplayName(id); - this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon()); + if (!GlobalConfig.uiConfig.quietMode) + { + this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon()); + } hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name); connetionStatusLabel->setText(tr("Connected: ") + name); // ConnectionManager->StartLatencyTest(id); - bool usePAC = GlobalConfig.inboundConfig.pacConfig.enablePAC; - bool pacUseSocks = GlobalConfig.inboundConfig.pacConfig.useSocksProxy; - bool httpEnabled = GlobalConfig.inboundConfig.useHTTP; - bool socksEnabled = GlobalConfig.inboundConfig.useSocks; - - if (usePAC) - { - bool canStartPAC = true; - QString pacProxyString; // Something like this --> SOCKS5 127.0.0.1:1080; SOCKS - // 127.0.0.1:1080; DIRECT; http://proxy:8080 - auto pacIP = GlobalConfig.inboundConfig.pacConfig.localIP; - - if (pacIP.isEmpty()) - { - LOG(MODULE_PROXY, "PAC Local IP is empty, default to 127.0.0.1") - pacIP = "127.0.0.1"; - } - - if (pacUseSocks) - { - if (socksEnabled) - { - pacProxyString = "SOCKS5 " + pacIP + ":" + QSTRN(GlobalConfig.inboundConfig.socks_port); - } - else - { - LOG(MODULE_UI, "PAC is using SOCKS, but it is not enabled") - QvMessageBoxWarn(this, tr("Configuring PAC"), - tr("Could not start PAC server as it is configured to use SOCKS, but it is not enabled")); - canStartPAC = false; - } - } - else - { - if (httpEnabled) - { - pacProxyString = "PROXY " + pacIP + ":" + QSTRN(GlobalConfig.inboundConfig.http_port); - } - else - { - LOG(MODULE_UI, "PAC is using HTTP, but it is not enabled") - QvMessageBoxWarn(this, tr("Configuring PAC"), - tr("Could not start PAC server as it is configured to use HTTP, but it is not enabled")); - canStartPAC = false; - } - } - - if (canStartPAC) - { - pacServer = new PACServer(this); - pacServer->setPACProxyString(pacProxyString); - pacServer->start(); - } - else - { - LOG(MODULE_PROXY, "Not starting PAC due to previous error.") - } - } - if (GlobalConfig.inboundConfig.setSystemProxy) { MWSetSystemProxy(); @@ -860,7 +819,7 @@ void MainWindow::OnGroupDeleted(const GroupId &id, const QList &co void MainWindow::on_locateBtn_clicked() { - auto id = ConnectionManager->CurrentConnection(); + auto id = KernelInstance->CurrentConnection(); if (id != NullConnectionId) { connectionListWidget->setCurrentItem(connectionNodes.value(id).get()); @@ -964,7 +923,10 @@ void MainWindow::on_action_RCM_SetAutoConnection_triggered() auto widget = GetItemWidget(current); auto &conn = get<1>(widget->Identifier()); GlobalConfig.autoStartId = conn.toString(); - hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn))); + if (!GlobalConfig.uiConfig.quietMode) + { + hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn))); + } SaveGlobalSettings(); } } @@ -975,6 +937,17 @@ void MainWindow::on_action_RCM_ClearUsage_triggered() if (current != nullptr) { auto widget = GetItemWidget(current); - ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier())); + if (widget) + { + if (widget->IsConnection()) + ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier())); + else + ConnectionManager->ClearGroupUsage(get<0>(widget->Identifier())); + } } } + +void MainWindow::on_pluginsBtn_clicked() +{ + PluginManageWindow(this).exec(); +} diff --git a/src/ui/w_MainWindow.hpp b/src/ui/w_MainWindow.hpp index 01aa9846..fbcba6e6 100644 --- a/src/ui/w_MainWindow.hpp +++ b/src/ui/w_MainWindow.hpp @@ -2,7 +2,6 @@ #include "common/HTTPRequestHelper.hpp" #include "common/LogHighlighter.hpp" -#include "components/pac/QvPACHandler.hpp" #include "components/speedchart/speedwidget.hpp" #include "core/handler/ConfigHandler.hpp" #include "ui/messaging/QvMessageBus.hpp" @@ -61,6 +60,8 @@ class MainWindow void on_connectionListWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); void on_masterLogBrowser_textChanged(); + void on_pluginsBtn_clicked(); + private: void on_actionExit_triggered(); void on_action_StartThis_triggered(); @@ -108,7 +109,6 @@ class MainWindow // Charts SpeedWidget *speedChartWidget; QSystemTrayIcon hTray; - PACServer *pacServer; SyntaxHighlighter *vCoreLogHighlighter; ConnectionInfoWidget *infoWidget; // diff --git a/src/ui/w_MainWindow.ui b/src/ui/w_MainWindow.ui index 6a555483..de8685e8 100644 --- a/src/ui/w_MainWindow.ui +++ b/src/ui/w_MainWindow.ui @@ -22,7 +22,7 @@ - + 5 @@ -33,6 +33,13 @@ + + + + Plugins + + + @@ -553,7 +560,6 @@ subsButton - preferencesBtn connectionListWidget importConfigButton diff --git a/src/ui/w_MainWindow_extra.cpp b/src/ui/w_MainWindow_extra.cpp index 0cca3f7b..62c87969 100644 --- a/src/ui/w_MainWindow_extra.cpp +++ b/src/ui/w_MainWindow_extra.cpp @@ -4,79 +4,39 @@ void MainWindow::MWSetSystemProxy() { - bool usePAC = GlobalConfig.inboundConfig.pacConfig.enablePAC; - bool pacUseSocks = GlobalConfig.inboundConfig.pacConfig.useSocksProxy; - bool httpEnabled = GlobalConfig.inboundConfig.useHTTP; - bool socksEnabled = GlobalConfig.inboundConfig.useSocks; - // - bool isComplex = IsComplexConfig(ConnectionManager->CurrentConnection()); + auto inboundPorts = KernelInstance->InboundPorts(); + bool httpEnabled = inboundPorts.contains("http"); + bool socksEnabled = inboundPorts.contains("socks"); + auto httpPort = inboundPorts["http"]; + auto socksPort = inboundPorts["socks"]; - if (!isComplex) + QString proxyAddress; + + if (httpEnabled || socksEnabled) { - // Is simple config and we will try to set system proxy. - LOG(MODULE_UI, "Preparing to set system proxy") - // - QString proxyAddress; - bool canSetSystemProxy = true; - - if (usePAC) + proxyAddress = "127.0.0.1"; + SetSystemProxy(proxyAddress, httpPort, socksPort); + hTray.setIcon(Q_TRAYICON("tray-systemproxy.png")); + if (!GlobalConfig.uiConfig.quietMode) { - if ((httpEnabled && !pacUseSocks) || (socksEnabled && pacUseSocks)) - { - // If we use PAC and socks/http are properly configured for PAC - LOG(MODULE_PROXY, "System proxy uses PAC") - proxyAddress = "http://" + GlobalConfig.inboundConfig.listenip + ":" + QSTRN(GlobalConfig.inboundConfig.pacConfig.port) + "/pac"; - } - else - { - // Not properly configured - LOG(MODULE_PROXY, "Failed to process pac due to following reasons:") - LOG(MODULE_PROXY, " --> PAC is configured to use socks but socks is not enabled.") - LOG(MODULE_PROXY, " --> PAC is configuted to use http but http is not enabled.") - QvMessageBoxWarn(this, tr("PAC Processing Failed"), - tr("HTTP or SOCKS inbound is not properly configured for PAC") + NEWLINE + - tr("Qv2ray will continue, but will not set system proxy.")); - canSetSystemProxy = false; - } - } - else - { - // Not using PAC - if (httpEnabled || socksEnabled) - { - // Not use PAC, System proxy should use HTTP or SOCKS - LOG(MODULE_PROXY, "Setting up system proxy.") - // A 'proxy host' should be a host WITHOUT `http://` uri scheme - proxyAddress = "localhost"; - } - 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")); - canSetSystemProxy = false; - } - } - - if (canSetSystemProxy) - { - LOG(MODULE_UI, "Setting system proxy for simple config.") - auto httpPort = GlobalConfig.inboundConfig.useHTTP ? GlobalConfig.inboundConfig.http_port : 0; - auto socksPort = GlobalConfig.inboundConfig.useSocks ? GlobalConfig.inboundConfig.socks_port : 0; - // - SetSystemProxy(proxyAddress, httpPort, socksPort, usePAC); hTray.showMessage("Qv2ray", tr("System proxy configured.")); } } else { - hTray.showMessage("Qv2ray", tr("Didn't set proxy for complex config."), windowIcon()); + 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.showMessage("Qv2ray", tr("System proxy removed.")); + 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() @@ -84,22 +44,26 @@ void MainWindow::CheckSubscriptionsUpdate() QStringList updateList; auto subscriptions = ConnectionManager->Subscriptions(); - for (auto entry : subscriptions) + for (const auto &entry : subscriptions) { - auto into = ConnectionManager->GetGroupMetaObject(entry); + const auto info = ConnectionManager->GetGroupMetaObject(entry); // - auto lastRenewDate = QDateTime::fromTime_t(into.lastUpdated); - auto renewTime = lastRenewDate.addSecs(into.updateInterval * 86400); + // 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 \"" + entry.toString() + "\": " + // + "Subscription \"" + info.displayName + "\": " + // NEWLINE + " --> Last renewal time: " + lastRenewDate.toString() + // - NEWLINE + " --> Renew interval: " + QSTRN(into.updateInterval) + // + NEWLINE + " --> Renew interval: " + QSTRN(info.updateInterval) + // NEWLINE + " --> Ideal renew time: " + renewTime.toString()) // if (renewTime <= QDateTime::currentDateTime()) { - LOG(MODULE_SUBSCRIPTION, "Subscription: " + entry.toString() + " needs to be updated.") - updateList.append(entry.toString()); + LOG(MODULE_SUBSCRIPTION, "Subscription: " + info.displayName + " needs to be updated.") + updateList.append(info.displayName); } } diff --git a/src/ui/w_PluginManager.cpp b/src/ui/w_PluginManager.cpp new file mode 100644 index 00000000..69ea3c77 --- /dev/null +++ b/src/ui/w_PluginManager.cpp @@ -0,0 +1,132 @@ +#include "w_PluginManager.hpp" + +#include "common/QvHelpers.hpp" +#include "components/plugins/QvPluginHost.hpp" +#include "core/settings/SettingsBackend.hpp" +#include "ui/editors/w_JsonEditor.hpp" + +PluginManageWindow::PluginManageWindow(QWidget *parent) : QDialog(parent) +{ + setupUi(this); + for (auto &plugin : PluginHost->AvailablePlugins()) + { + const auto &info = PluginHost->GetPluginMetadata(plugin); + auto item = new QListWidgetItem(pluginListWidget); + item->setCheckState(PluginHost->GetPluginEnableState(info.InternalName) ? Qt::Checked : Qt::Unchecked); + item->setData(Qt::UserRole, info.InternalName); + item->setText(info.Name + " (" + (PluginHost->GetPluginLoadState(info.InternalName) ? tr("Loaded") : tr("Not loaded")) + ")"); + pluginListWidget->addItem(item); + } + isLoading = false; +} + +PluginManageWindow::~PluginManageWindow() +{ +} + +void PluginManageWindow::on_pluginListWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + Q_UNUSED(previous) + auto &info = PluginHost->GetPluginMetadata(current->data(Qt::UserRole).toString()); + + pluginIconLabel->setPixmap(info.Icon.pixmap(pluginIconLabel->size() * devicePixelRatio())); + // + pluginNameLabel->setText(info.Name); + pluginAuthorLabel->setText(info.Author); + pluginDescriptionLabel->setText(info.Description); + pluginLibPathLabel->setText(PluginHost->GetPluginLibraryPath(info.InternalName)); + pluginStateLabel->setText(PluginHost->GetPluginLoadState(info.InternalName) ? tr("Loaded") : tr("Not loaded")); + pluginTypeLabel->setText(GetPluginTypeString(info.SpecialPluginType)); + pluginHookTypeLabel->setText(GetPluginCapabilityString(info.Capabilities)); + // + if (!current) + { + return; + } + if (settingsWidget || settingsWidget.get()) + { + pluginSettingsLayout->removeWidget(settingsWidget.get()); + settingsWidget.reset(); + } + if (!PluginHost->GetPluginLoadState(info.InternalName)) + { + pluginUnloadLabel->setVisible(true); + pluginUnloadLabel->setText(tr("Plugin Not Loaded")); + return; + } + settingsWidget = PluginHost->GetPluginSettingsWidget(info.InternalName); + if (settingsWidget) + { + pluginUnloadLabel->setVisible(false); + settingsWidget.get()->setParent(this); + pluginSettingsLayout->addWidget(settingsWidget.get()); + } + else + { + pluginUnloadLabel->setVisible(true); + pluginUnloadLabel->setText(tr("Plugin does not have settings widget.")); + } +} + +void PluginManageWindow::on_pluginListWidget_itemClicked(QListWidgetItem *item) +{ + Q_UNUSED(item) + // on_pluginListWidget_currentItemChanged(item, nullptr); +} + +void PluginManageWindow::on_pluginListWidget_itemChanged(QListWidgetItem *item) +{ + if (isLoading) + return; + bool isEnabled = item->checkState() == Qt::Checked; + auto pluginInternalName = item->data(Qt::UserRole).toString(); + PluginHost->SetPluginEnableState(pluginInternalName, isEnabled); + auto &info = PluginHost->GetPluginMetadata(pluginInternalName); + item->setText(info.Name + " (" + (PluginHost->GetPluginLoadState(info.InternalName) ? tr("Loaded") : tr("Not loaded")) + ")"); + // + if (!isEnabled) + { + QvMessageBoxInfo(this, tr("Disabling a plugin"), tr("This plugin will keep loaded until the next time Qv2ray starts.")); + } +} + +void PluginManageWindow::on_pluginEditSettingsJsonBtn_clicked() +{ + if (const auto ¤t = pluginListWidget->currentItem(); current != nullptr) + { + const auto &info = PluginHost->GetPluginMetadata(current->data(Qt::UserRole).toString()); + if (!PluginHost->GetPluginLoadState(info.InternalName)) + { + QvMessageBoxWarn(this, tr("Plugin not loaded"), tr("This plugin is not loaded, please enable or reload the plugin to continue.")); + return; + } + JsonEditor w(PluginHost->GetPluginSettings(info.InternalName)); + auto newConf = w.OpenEditor(); + if (w.result() == QDialog::Accepted) + { + PluginHost->SetPluginSettings(info.InternalName, newConf); + } + } +} + +void PluginManageWindow::on_pluginListWidget_itemSelectionChanged() +{ + auto needEnable = !pluginListWidget->selectedItems().isEmpty(); + pluginEditSettingsJsonBtn->setEnabled(needEnable); +} + +void PluginManageWindow::on_openPluginFolder_clicked() +{ + QDir pluginPath(QV2RAY_CONFIG_DIR + "plugins/"); + if (!pluginPath.exists()) + { + pluginPath.mkpath(QV2RAY_CONFIG_DIR + "plugins/"); + } + QDesktopServices::openUrl(QUrl(pluginPath.absolutePath())); +} + +void PluginManageWindow::on_toolButton_clicked() +{ + auto address = GlobalConfig.uiConfig.language.contains("zh") ? "https://qv2ray.github.io/plugins/" : "https://qv2ray.github.io/en/plugins/"; + QDesktopServices::openUrl(QUrl(address)); +} diff --git a/src/ui/w_PluginManager.hpp b/src/ui/w_PluginManager.hpp new file mode 100644 index 00000000..fa40d450 --- /dev/null +++ b/src/ui/w_PluginManager.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "ui_w_PluginManager.h" + +#include +#include + +class PluginManageWindow + : public QDialog + , private Ui::w_PluginManager +{ + Q_OBJECT + + public: + explicit PluginManageWindow(QWidget *parent = nullptr); + ~PluginManageWindow(); + 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: + std::unique_ptr settingsWidget; + bool isLoading = true; +}; diff --git a/src/ui/w_PluginManager.ui b/src/ui/w_PluginManager.ui new file mode 100644 index 00000000..a9cb9baa --- /dev/null +++ b/src/ui/w_PluginManager.ui @@ -0,0 +1,356 @@ + + + w_PluginManager + + + + 0 + 0 + 757 + 518 + + + + Plugin Manager + + + + + + Plugins + + + + + + + + + + + + Open Local Plugin Folder + + + + + + + Online help about plugins + + + ? + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + 0 + + + + Plugin Metadata + + + + + + + + Name + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Author + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Description + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Library Path + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + State + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Capability + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Special Type + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + IBeamCursor + + + + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + + + 64 + 64 + + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + Plugin Settings + + + + + + + + Plugin Not Loaded + + + Qt::AlignCenter + + + + + + + + + Manually Edit Settings + + + + + + + + + + + + + buttonBox + accepted() + w_PluginManager + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + w_PluginManager + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/ui/w_PreferencesWindow.cpp b/src/ui/w_PreferencesWindow.cpp index e95c0aa1..e653cd45 100644 --- a/src/ui/w_PreferencesWindow.cpp +++ b/src/ui/w_PreferencesWindow.cpp @@ -4,10 +4,11 @@ #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 "core/connection/ConnectionIO.hpp" #include "core/handler/ConfigHandler.hpp" -#include "core/kernel/KernelInteractions.hpp" +#include "core/kernel/V2rayKernelInteractions.hpp" #include "core/settings/SettingsBackend.hpp" #include "ui/widgets/RouteSettingsMatrix.hpp" @@ -69,6 +70,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current qvBuildInfo->setText(QV2RAY_BUILD_INFO); qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO); qvBuildTime->setText(__DATE__ " " __TIME__); + qvPluginInterfaceVersionLabel->setText(tr("Version: %1").arg(QSTRN(QV2RAY_PLUGIN_INTERFACE_VERSION))); // // Deep copy CurrentConfig = GlobalConfig; @@ -87,14 +89,6 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current // // listenIPTxt->setText(CurrentConfig.inboundConfig.listenip); - bool pacEnabled = CurrentConfig.inboundConfig.pacConfig.enablePAC; - pacGroupBox->setChecked(pacEnabled); - setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy); - // - // PAC - pacPortSB->setValue(CurrentConfig.inboundConfig.pacConfig.port); - pacProxyTxt->setText(CurrentConfig.inboundConfig.pacConfig.localIP); - pacProxyCB->setCurrentIndex(CurrentConfig.inboundConfig.pacConfig.useSocksProxy ? 1 : 0); // bool have_http = CurrentConfig.inboundConfig.useHTTP; httpGroupBox->setChecked(have_http); @@ -134,12 +128,26 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current // localDNSCb->setChecked(CurrentConfig.connectionConfig.withLocalDNS); // + pluginKernelV2rayIntegrationCB->setChecked(CurrentConfig.pluginConfig.v2rayIntegration); + pluginKernelPortAllocateCB->setValue(CurrentConfig.pluginConfig.portAllocationStart); + // + qvProxyPortCB->setValue(CurrentConfig.networkConfig.port); + qvProxyAddressTxt->setText(CurrentConfig.networkConfig.address); + qvProxyTypeCombo->setCurrentText(CurrentConfig.networkConfig.type); + qvNetworkUATxt->setText(CurrentConfig.networkConfig.userAgent); + qvUseProxyCB->setChecked(CurrentConfig.networkConfig.useCustomProxy); + // + quietModeCB->setChecked(CurrentConfig.uiConfig.quietMode); + // + // Advanced config. + setAllowInsecureCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecure); + setAllowInsecureCiphersCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecureCiphers); + setTestLatenctCB->setChecked(CurrentConfig.advancedConfig.testLatencyPeriodcally); + // DNSListTxt->clear(); - for (auto dnsStr : CurrentConfig.connectionConfig.dnsList) { auto str = dnsStr.trimmed(); - if (!str.isEmpty()) { DNSListTxt->appendPlainText(str); @@ -150,11 +158,11 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current updateSettingsGroupBox->setEnabled(false); updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor.")); #endif - + // updateChannelCombo->setCurrentIndex(CurrentConfig.updateConfig.updateChannel); 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()) + " " + @@ -213,8 +221,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current // maxLogLinesSB->setValue(CurrentConfig.uiConfig.maximumLogLines); // - pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" + - QSTRN(pacPortSB->value()) + "/pac"); + setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy); // finishedLoading = true; routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this); @@ -256,12 +263,6 @@ void PreferencesWindow::on_buttonBox_accepted() ports << CurrentConfig.inboundConfig.socks_port; } - if (CurrentConfig.inboundConfig.pacConfig.enablePAC) - { - size++; - ports << CurrentConfig.inboundConfig.pacConfig.port; - } - if (!StartupOption.noAPI) { size++; @@ -932,84 +933,6 @@ void PreferencesWindow::on_darkTrayCB_stateChanged(int arg1) CurrentConfig.uiConfig.useDarkTrayIcon = arg1 == Qt::Checked; } -void PreferencesWindow::on_pacGoBtn_clicked() -{ - LOADINGCHECK - QString gfwLocation; - QString fileContent; - pacGoBtn->setEnabled(false); - gfwListCB->setEnabled(false); - QvHttpRequestHelper request; - LOG(MODULE_PROXY, "Downloading GFWList file.") - bool withProxy = getGFWListWithProxyCB->isChecked(); - - switch (gfwListCB->currentIndex()) - { - case 0: - gfwLocation = "https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 1: - gfwLocation = "https://pagure.io/gfwlist/raw/master/f/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 2: - gfwLocation = "http://repo.or.cz/gfwlist.git/blob_plain/HEAD:/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 3: - gfwLocation = "https://bitbucket.org/gfwlist/gfwlist/raw/HEAD/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 4: - gfwLocation = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 5: - gfwLocation = "https://git.tuxfamily.org/gfwlist/gfwlist.git/plain/gfwlist.txt"; - fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy)); - break; - - case 6: - auto file = QFileDialog::getOpenFileName(this, tr("Select GFWList in base64")); - - if (file.isEmpty()) - { - QvMessageBoxWarn(this, tr("Download GFWList"), tr("Operation is cancelled.")); - return; - } - - fileContent = StringFromFile(file); - break; - } - - LOG(MODULE_NETWORK, "Fetched: " + gfwLocation) - QvMessageBoxInfo(this, tr("Download GFWList"), tr("Successfully downloaded GFWList.")); - pacGoBtn->setEnabled(true); - gfwListCB->setEnabled(true); - - if (!QDir(QV2RAY_RULES_DIR).exists()) - { - QDir(QV2RAY_RULES_DIR).mkpath(QV2RAY_RULES_DIR); - } - - StringToFile(fileContent, QV2RAY_RULES_GFWLIST_PATH); -} - -void PreferencesWindow::on_pacPortSB_valueChanged(int arg1) -{ - LOADINGCHECK - NEEDRESTART - CurrentConfig.inboundConfig.pacConfig.port = arg1; - pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" + - QSTRN(pacPortSB->value()) + "/pac"); -} - void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1) { LOADINGCHECK @@ -1017,28 +940,12 @@ void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1) CurrentConfig.inboundConfig.setSystemProxy = arg1 == Qt::Checked; } -void PreferencesWindow::on_pacProxyCB_currentIndexChanged(int index) -{ - LOADINGCHECK - NEEDRESTART - // 0 -> http - // 1 -> socks - CurrentConfig.inboundConfig.pacConfig.useSocksProxy = index == 1; -} - void PreferencesWindow::on_pushButton_clicked() { LOADINGCHECK QDesktopServices::openUrl(QUrl::fromUserInput(QV2RAY_RULES_DIR)); } -void PreferencesWindow::on_pacProxyTxt_textEdited(const QString &arg1) -{ - LOADINGCHECK - NEEDRESTART - CurrentConfig.inboundConfig.pacConfig.localIP = arg1; -} - void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString &arg1) { LOADINGCHECK if (arg1.isEmpty()) @@ -1147,23 +1054,6 @@ void PreferencesWindow::on_fpPortSB_valueChanged(int arg1) CurrentConfig.connectionConfig.forwardProxyConfig.port = arg1; } -void PreferencesWindow::on_pacProxyTxt_textChanged(const QString &arg1) -{ - Q_UNUSED(arg1) - - if (IsValidIPAddress(arg1)) - { - BLACK(pacProxyTxt) - } - else - { - RED(pacProxyTxt) - } - - pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" + - QSTRN(pacPortSB->value()) + "/pac"); -} - void PreferencesWindow::on_checkVCoreSettings_clicked() { auto vcorePath = vCorePathTxt->text(); @@ -1196,20 +1086,6 @@ void PreferencesWindow::on_socksGroupBox_clicked(bool checked) CurrentConfig.inboundConfig.useSocks = checked; } -void PreferencesWindow::on_pacGroupBox_clicked(bool checked) -{ - LOADINGCHECK - NEEDRESTART - CurrentConfig.inboundConfig.pacConfig.enablePAC = checked; - if (checked) - { - QvMessageBoxWarn(this, QObject::tr("Deprecated"), - QObject::tr("PAC is now deprecated and is not encouraged to be used anymore.") + NEWLINE + - QObject::tr("It will be removed or be provided as a plugin in the future.") + NEWLINE + NEWLINE + - QObject::tr("PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible.")); - } -} - void PreferencesWindow::on_fpGroupBox_clicked(bool checked) { LOADINGCHECK @@ -1237,3 +1113,81 @@ void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index) CurrentConfig.updateConfig.updateChannel = index; CurrentConfig.updateConfig.ignoredVersion.clear(); } + +void PreferencesWindow::on_pluginKernelV2rayIntegrationCB_stateChanged(int arg1) +{ + LOADINGCHECK + CurrentConfig.pluginConfig.v2rayIntegration = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_pluginKernelPortAllocateCB_valueChanged(int arg1) +{ + LOADINGCHECK + CurrentConfig.pluginConfig.portAllocationStart = arg1; +} + +void PreferencesWindow::on_qvProxyAddressTxt_textEdited(const QString &arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.address = arg1; +} + +void PreferencesWindow::on_qvProxyTypeCombo_currentTextChanged(const QString &arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.type = arg1.toLower(); +} + +void PreferencesWindow::on_qvProxyPortCB_valueChanged(int arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.port = arg1; +} + +void PreferencesWindow::on_qvNetworkUATxt_textEdited(const QString &arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.userAgent = arg1; +} + +void PreferencesWindow::on_qvUseProxyCB_stateChanged(int arg1) +{ + LOADINGCHECK + CurrentConfig.networkConfig.useCustomProxy = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_setAllowInsecureCB_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.")); + } + CurrentConfig.advancedConfig.setAllowInsecure = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_setTestLatenctCB_stateChanged(int arg1) +{ + LOADINGCHECK + if (arg1 == Qt::Checked) + { + QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("This will (probably) makes it easy to fingerprint your connection.")); + } + CurrentConfig.advancedConfig.testLatencyPeriodcally = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_setAllowInsecureCiphersCB_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.")); + } + CurrentConfig.advancedConfig.setAllowInsecureCiphers = arg1 == Qt::Checked; +} + +void PreferencesWindow::on_quietModeCB_stateChanged(int arg1) +{ + LOADINGCHECK + CurrentConfig.uiConfig.quietMode = arg1 == Qt::Checked; +} diff --git a/src/ui/w_PreferencesWindow.hpp b/src/ui/w_PreferencesWindow.hpp index 5cdca72a..051a4587 100644 --- a/src/ui/w_PreferencesWindow.hpp +++ b/src/ui/w_PreferencesWindow.hpp @@ -118,18 +118,10 @@ class PreferencesWindow void on_darkTrayCB_stateChanged(int arg1); - void on_pacGoBtn_clicked(); - - void on_pacPortSB_valueChanged(int arg1); - void on_setSysProxyCB_stateChanged(int arg1); - void on_pacProxyCB_currentIndexChanged(int index); - void on_pushButton_clicked(); - void on_pacProxyTxt_textEdited(const QString &arg1); - void on_autoStartSubsCombo_currentIndexChanged(const QString &arg1); void on_autoStartConnCombo_currentIndexChanged(const QString &arg1); @@ -148,16 +140,12 @@ class PreferencesWindow void on_fpPortSB_valueChanged(int arg1); - void on_pacProxyTxt_textChanged(const QString &arg1); - void on_checkVCoreSettings_clicked(); void on_httpGroupBox_clicked(bool checked); void on_socksGroupBox_clicked(bool checked); - void on_pacGroupBox_clicked(bool checked); - void on_fpGroupBox_clicked(bool checked); void on_maxLogLinesSB_valueChanged(int arg1); @@ -168,6 +156,28 @@ class PreferencesWindow 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_qvUseProxyCB_stateChanged(int arg1); + + void on_setAllowInsecureCB_stateChanged(int arg1); + + void on_setTestLatenctCB_stateChanged(int arg1); + + void on_setAllowInsecureCiphersCB_stateChanged(int arg1); + + void on_quietModeCB_stateChanged(int arg1); + private: // RouteSettingsMatrixWidget *routeSettingsWidget; diff --git a/src/ui/w_PreferencesWindow.ui b/src/ui/w_PreferencesWindow.ui index bf2e110f..feb043bd 100644 --- a/src/ui/w_PreferencesWindow.ui +++ b/src/ui/w_PreferencesWindow.ui @@ -9,8 +9,8 @@ 0 0 - 1010 - 629 + 840 + 566 @@ -19,7 +19,17 @@ true - + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + @@ -29,447 +39,631 @@ General Settings - - - - - QFrame::NoFrame + + + + + + + Appearance + + + + + + Darkmode UI Icons + + + + + + + Enabled + + + + + + + Darkmode Tray Icon + + + + + + + Enabled + + + + + + + UI Theme + + + + + + + + 200 + 0 + + + + + + + + Language + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + Maximum log lines + + + + + + + + 0 + 0 + + + + true + + + lines + + + 10 + + + 2500 + + + 200 + + + + + + + + + + Behavior + + + + + + Launch at Login + + + + + + + Enabled + + + + + + + Transparent Proxy + + + + + + + Enabled + + + + + + + Enabled + + + + + + + Auto Connect + + + + + + + + + + + + Config + + + + + + + + 180 + 0 + + + + + + + + + + + + + Group + + + + + + + + + Quiet Mode + + + + + + + + + + Network Settings + + + + + + Use Custom Proxy + + + + + + + Enabled + + + + + + + Custom Proxy Server + + + + + + + + + + + + : + + + Qt::AlignCenter + + + + + + + 0 + + + 65535 + + + + + + + + + + http + + + + + socks + + + + + + + + Proxy Type + + + + + + + User-Agent + + + + + + + + + + + + + + + + + Advanced Behavior + + + + + + AllowInsecure By Default + + + + + + + Enable "AllowInsecure" settings for all connections when importing. +This could resolve the certificate issues, but also could let one performing TLS MITM attack. + + + Enabled + + + + + + + Test Latency Periodcally + + + + + + + Run TCPing or ICMPing periodcally after connecting to a server. +Qv2ray will give a more accurate latency value if Enabled, but makes it easy to fingerprint the connection. + + + Enabled + + + + + + + + 50 + true + false + + + + color: red; + + + These settings may be useful. +But could damage your server if improperly used. + + + + + + + AllowInsecureCiphers By Default + + + + + + + Enabled + + + + + + + + + + + + Qt::Vertical - - true + + + 20 + 40 + - - - - 0 - 0 - 964 - 546 - - - - - - - - - General Settings - - - - - - - - - - - - 0 - 0 - - - - true - - - lines - - - 10 - - - 2500 - - - 200 - - - - - - - UI Theme - - - - - - - Darkmode UI Icons - - - - - - - QFrame::Plain - - - 2 - - - Qt::Vertical - - - - - - - Language - - - - - - - - 0 - 0 - - - - - 100 - 0 - - - - - - - - Darkmode Tray Icon - - - - - - - Enabled - - - - - - - Maximum log lines - - - - - - - Enabled - - - - - - - - 200 - 0 - - - - - - - - Enabled - - - - - - - Launch at Login - - - - - - - Transparent Proxy Support - - - - - - - Enabled - - - - - - - - - - - Connection - - - - - - - Group/Subscription - - - - - - - - - - - 180 - 0 - - - - - - - - - - - - - QFrame::Plain - - - 2 - - - Qt::Vertical - - - - - - - - - - - V2ray Settings - - - - - - - - - - - Check V2ray Core Settings - - - - - - - Core Executable Path - - - - - - - - - - - - - - - - - Select - - - - - - - V2ray Assets Directory - - - - - - - Select - - - - - - - Log Level - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - - none - - - - - debug - - - - - info - - - - - warning - - - - - error - - - - - - - - QFrame::Plain - - - 2 - - - Qt::Vertical - - - - - - - - - - - API Subsystem - - - - - - - - - - - API Port - - - - - - - Status - - - - - - - - 116 - 30 - - - - 1024 - - - 65535 - - - 15934 - - - - - - - Enabled - - - - - - - QFrame::Plain - - - 2 - - - Qt::Vertical - - - - - - - - - Qt::Vertical + + + + + + + Kernel Settings + + + + + + V2ray Settings + + + + + + Log Level + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + none - - - 20 - 40 - + + + + debug - - - - - - - - Auto Connect - - - - - - - + + + + info + + + + + warning + + + + + error + + + + + + + + Core Executable Path + + + + + + + + + + + + + + + + Select + + + + + + + + + V2ray Assets Directory + + + + + + + + + + + + Select + + + + + + + + + API SubSystem + + + + + + + Enabled + + + + + + + API Port + + + + + + + + 116 + 30 + + + + 1024 + + + 65535 + + + 15934 + + + + + + + Check V2ray Core Settings + + + + + + + + Plugin Kernel Settings + + + + + + Enabling V2ray Integration will allow the kernel benefit from the V2ray routing engine. + + + + + + + V2ray Integration + + + + + + + If not checked, these features will be disabled: + +Advanced Routing Settings +Bypass CN websites and IPs +Direct connection of Local LAN addresses +Custom DNS Settings + + + Enabled + + + + + + + Qv2ray will allocate ports, for HTTP and SOCKS respectively, if enabled, for each kernel plugin. + + + + + + + Port Allocation Start + + + + + + + 1 + + + 65535 + + + 13000 + + + + + + + + + + Qt::Vertical + + + + 20 + 106 + + + + @@ -490,8 +684,8 @@ 0 0 - 1016 - 604 + 794 + 603 @@ -529,83 +723,6 @@ - - - - HTTP Settings - - - true - - - false - - - - - - Port - - - - - - - 1 - - - 65535 - - - 18001 - - - - - - - Authentication - - - - - - - Enabled - - - - - - - Username - - - - - - - user - - - - - - - Password - - - - - - - pass - - - - - - @@ -711,10 +828,10 @@ - - + + - Transparent Proxy Settings + HTTP Settings true @@ -722,199 +839,16 @@ false - - - - - - 75 - true - true - - - - All settings below will only be applied onto simple connection. - - - - - - - Add Docodemo-door inbound - - - - - - - - - Address - - - - - - - 127.0.0.1 - - - - - - - Port - - - - - - - 1 - - - 65535 - - - 12345 - - - - - - - TCP - - - - - - - UDP - - - - - - - Follow Redirect - - - - - - - - - Override Connection SockOpt Settings - - - true - - - false - - - - - - Enabled - - - - - - - TCP Fast Open - - - - - - - Mark - - - - - - - Match Contains - - - - - - - - - - TProxy Mode - - - - - - - - - - - 75 - true - true - - - - Settings will be added to the StreamSettings for matched connections. - - - - - - - - - - - - - - - - Qt::PreventContextMenu - - - PAC Settings - - - true - - - false - - - - - - - true - - - - The system proxy will be configured to use the PAC instead of HTTP and SOCKS. - - - - - + + + Port - - + + 1 @@ -922,140 +856,49 @@ 65535 - 8088 + 18001 + + + + + + + Authentication + + + + + + + Enabled - + - Local IP for PAC + Username - + - 127.0.0.1 + user - + - Use Proxy + Password - - - - HTTP - - - - - SOCKS - - - - - - - - Import GFWList - - - - - - - - - - Mirror: Gitlab - - - - - Github - - - - - Mirror: Pagure - - - - - Mirror: Repo.or.cz - - - - - Mirror: Bitbucket - - - - - Mirror: TuxFamily - - - - - GFWList File - - - - - - - - Download with System Proxy - - - - - - - Go - - - - - - - - - Edit PAC - - - - - - - Open PAC Folder - - - - - - - PAC Access Path - - - - - - - IBeamCursor - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + pass @@ -1064,6 +907,174 @@ + + + + Transparent Proxy Settings + + + true + + + false + + + + + + + 75 + true + true + + + + All settings below will only be applied on simple connection. + + + + + + + Add Docodemo-door inbound + + + + + + + + + Address + + + + + + + 127.0.0.1 + + + + + + + Port + + + + + + + 1 + + + 65535 + + + 12345 + + + + + + + TCP + + + + + + + UDP + + + + + + + Follow Redirect + + + + + + + + + Override Connection SockOpt Settings + + + true + + + false + + + + + + Enabled + + + + + + + TCP Fast Open + + + + + + + Mark + + + + + + + Match Contains + + + + + + + + + + TProxy Mode + + + + + + + + + + + 75 + true + true + + + + Settings will be added to the StreamSettings for matched connections. + + + + + + + + + + + + @@ -1303,7 +1314,7 @@ Items - + @@ -1345,7 +1356,7 @@ - Page Y Offset + Page Y Axis Offset @@ -1466,6 +1477,18 @@ + + 0 + + + 0 + + + 0 + + + 0 + @@ -1553,67 +1576,67 @@ - - + + A: - - - - 255 - - - - - - - R: - - - - - - - 255 - - - - + G: - + + + + 255 + + + + 255 - - - - B: - - - - + 255 - - + + + + 255 + + + + + - ... + R: + + + + + + + Select Color + + + + + + + B: @@ -1630,14 +1653,7 @@ - - - Apply Network Speed Bar UI Settings - - - - - + Qt::Vertical @@ -1649,6 +1665,13 @@ + + + + Apply Network Speed Bar UI Settings + + + @@ -1693,318 +1716,297 @@ - - - - - - - - - - 24 - - - - Qv2ray - - - - - - - - - - 15 - - + + + + + - Version: + Ignored Version - - - - - 15 - - - - IBeamCursor - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - - + + - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - License - - - - - - - Official Repo - - - - - - - - 0 - 0 - - - - <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> - - - Qt::RichText - - - false - - - true - - - - - - - - 0 - 0 - - - - - 10 - - - - <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> - - - Qt::RichText - - - false - - - true - - - - - - - Built Time - - + - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + Cancel - - + + - Build Info + Update Channel - - - - Extra Build Info - - - - - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - + + + + + Stable Release + + + + + Testing + + - - - - - QTextEdit::NoWrap - - - true - - - <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> + + + + + + + + + + + 26 + + + + Qv2ray + + + + + + + + + + 14 + + + + Version: + + + + + + + + 14 + + + + IBeamCursor + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Plugin Interface + + + + + + + + 0 + 0 + + + + <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> + + + Qt::RichText + + + false + + + true + + + + + + + Extra Build Info + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + + + + + + + + + 0 + 0 + + + + + 10 + + + + <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> + + + Qt::RichText + + + false + + + true + + + + + + + Built Time + + + + + + + Build Info + + + + + + + Official Repo + + + + + + + + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + + + + + + + License + + + + + + + + + QTextEdit::NoWrap + + + true + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'WenQuanYi Micro Hei'; font-size:10pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html> - - - - - - - - - - Update Settings - - - - - - Update Channel - - - - - - - Ignore Next Version - - - - - - - - - Cancel - - - - - - - IBeamCursor - - - - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse - - - - - - - - - - Stable Release - - - - - Testing - - - - - - - - - - - - 206 - 0 - - - - About Qt - - - - - + + + + + + + + + + + 206 + 0 + + + + About Qt + + + + - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - tabWidget - scrollArea darkThemeCB darkTrayCB themeCombo languageComboBox - startWithLoginCB - tProxyCheckBox - maxLogLinesSB autoStartSubsCombo autoStartConnCombo logLevelComboBox - vCorePathTxt - selectVCoreBtn - vCoreAssetsPathTxt - selectVAssetBtn checkVCoreSettings - enableAPI - statsPortBox scrollArea_2 listenIPTxt setSysProxyCB @@ -2057,15 +2059,7 @@ p, li { white-space: pre-wrap; } nsBarFontItalicCB nsBarFontSizeSB nsBarFontASB - nsBarFontRSB - nsBarFontGSB - nsBarFontBSB - chooseColorBtn applyNSBarSettingsBtn - textBrowser - updateChannelCombo - cancelIgnoreVersionBtn - aboutQt diff --git a/src/ui/w_SubscriptionManager.ui b/src/ui/w_SubscriptionManager.ui index 78daa6bc..57eddf13 100644 --- a/src/ui/w_SubscriptionManager.ui +++ b/src/ui/w_SubscriptionManager.ui @@ -181,9 +181,6 @@ - - 0.500000000000000 - 365.000000000000000 diff --git a/src/ui/widgets/ConnectionItemWidget.cpp b/src/ui/widgets/ConnectionItemWidget.cpp index cadaeab7..ebfa22e4 100644 --- a/src/ui/widgets/ConnectionItemWidget.cpp +++ b/src/ui/widgets/ConnectionItemWidget.cpp @@ -145,11 +145,14 @@ void ConnectionItemWidget::CancelRename() void ConnectionItemWidget::BeginRename() { - stackedWidget->setCurrentIndex(1); - renameTxt->setStyle(QStyleFactory::create("Fusion")); - renameTxt->setStyleSheet("background-color: " + this->palette().color(this->backgroundRole()).name(QColor::HexRgb)); - renameTxt->setText(originalItemName); - renameTxt->setFocus(); + if (IsConnection()) + { + stackedWidget->setCurrentIndex(1); + renameTxt->setStyle(QStyleFactory::create("Fusion")); + renameTxt->setStyleSheet("background-color: " + this->palette().color(this->backgroundRole()).name(QColor::HexRgb)); + renameTxt->setText(originalItemName); + renameTxt->setFocus(); + } } ConnectionItemWidget::~ConnectionItemWidget() diff --git a/src/ui/widgets/RouteSettingsMatrix.cpp b/src/ui/widgets/RouteSettingsMatrix.cpp index 0869b2b6..1a6ff3d7 100644 --- a/src/ui/widgets/RouteSettingsMatrix.cpp +++ b/src/ui/widgets/RouteSettingsMatrix.cpp @@ -103,7 +103,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(filePath.value()); + auto content = StringFromFile(ACCESS_OPTIONAL_VALUE(filePath)); auto scheme = StructFromJsonString(content); // show the information of this scheme to user, @@ -175,7 +175,7 @@ void RouteSettingsMatrixWidget::on_exportSchemeBtn_clicked() // serialize and write out auto content = StructToJsonString(scheme); - StringToFile(content, savePath.value()); + StringToFile(content, ACCESS_OPTIONAL_VALUE(savePath)); // done // TODO: Give some success as Notification diff --git a/src/ui/widgets/RouteSettingsMatrix.ui b/src/ui/widgets/RouteSettingsMatrix.ui index c8094b5b..9fd91a20 100644 --- a/src/ui/widgets/RouteSettingsMatrix.ui +++ b/src/ui/widgets/RouteSettingsMatrix.ui @@ -6,8 +6,8 @@ 0 0 - 600 - 409 + 582 + 404 @@ -45,93 +45,84 @@ - - - Route Settings + + + Lines start with "geoip:" or "geosite:" will have its autocompletion from geoip.dat and geosite.dat - - - - - Lines start with "geoip:" or "geosite:" will have its autocompletion from geoip.dat and geosite.dat - - - - - - - - - Block - - - Qt::AlignCenter - - - - - - - - - - Direct - - - Qt::AlignCenter - - - - - - - Domain - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - - - - - Proxy - - - Qt::AlignCenter - - - - - - - - - - - - - IP - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - - - + + + + + + Block + + + Qt::AlignCenter + + + + + + + + + + Direct + + + Qt::AlignCenter + + + + + + + Domain + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + Proxy + + + Qt::AlignCenter + + + + + + + + + + + + + IP + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + diff --git a/src/ui/widgets/StreamSettingsWidget.cpp b/src/ui/widgets/StreamSettingsWidget.cpp index a0f1825c..eea7fa3e 100644 --- a/src/ui/widgets/StreamSettingsWidget.cpp +++ b/src/ui/widgets/StreamSettingsWidget.cpp @@ -22,7 +22,7 @@ QvMessageBusSlotImpl(StreamSettingsWidget) } } -StreamSettingsObject StreamSettingsWidget::GetStreamSettings() +StreamSettingsObject StreamSettingsWidget::GetStreamSettings() const { return stream; } @@ -36,6 +36,7 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso) tlsCB->setChecked(stream.security == "tls"); serverNameTxt->setText(stream.tlsSettings.serverName); allowInsecureCB->setChecked(stream.tlsSettings.allowInsecure); + allowInsecureCiphersCB->setChecked(stream.tlsSettings.allowInsecureCiphers); alpnTxt->setPlainText(stream.tlsSettings.alpn.join(NEWLINE)); // TCP tcpHeaderTypeCB->setCurrentText(stream.tcpSettings.header.type); @@ -47,9 +48,9 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso) // WS wsPathTxt->setText(stream.wsSettings.path); QString wsHeaders; - for (auto index = 0; index < stream.wsSettings.headers.count(); index++) + for (auto i = 0; i < stream.wsSettings.headers.count(); i++) { - wsHeaders = wsHeaders % stream.wsSettings.headers.keys().at(index) % "|" % stream.wsSettings.headers.values().at(index) % NEWLINE; + wsHeaders = wsHeaders % stream.wsSettings.headers.keys().at(i) % "|" % stream.wsSettings.headers.values().at(i) % NEWLINE; } wsHeadersTxt->setPlainText(wsHeaders); @@ -284,3 +285,8 @@ void StreamSettingsWidget::on_alpnTxt_textChanged() { stream.tlsSettings.alpn = SplitLines(alpnTxt->toPlainText()); } + +void StreamSettingsWidget::on_allowInsecureCiphersCB_stateChanged(int arg1) +{ + stream.tlsSettings.allowInsecureCiphers = arg1 == Qt::Checked; +} diff --git a/src/ui/widgets/StreamSettingsWidget.hpp b/src/ui/widgets/StreamSettingsWidget.hpp index 32c309bb..8393a9a4 100644 --- a/src/ui/widgets/StreamSettingsWidget.hpp +++ b/src/ui/widgets/StreamSettingsWidget.hpp @@ -14,7 +14,7 @@ class StreamSettingsWidget public: explicit StreamSettingsWidget(QWidget *parent = nullptr); void SetStreamObject(const StreamSettingsObject &sso); - StreamSettingsObject GetStreamSettings(); + StreamSettingsObject GetStreamSettings() const; private slots: void on_httpPathTxt_textEdited(const QString &arg1); @@ -77,6 +77,8 @@ class StreamSettingsWidget void on_alpnTxt_textChanged(); + void on_allowInsecureCiphersCB_stateChanged(int arg1); + private: QvMessageBusSlotDecl; StreamSettingsObject stream; diff --git a/src/ui/widgets/StreamSettingsWidget.ui b/src/ui/widgets/StreamSettingsWidget.ui index 66620515..0b401485 100644 --- a/src/ui/widgets/StreamSettingsWidget.ui +++ b/src/ui/widgets/StreamSettingsWidget.ui @@ -635,37 +635,37 @@ - + Server - + - + ALPN - + - - + + - TLS + Enable TLS - - + + - Enabled + Allow Insecure Ciphers diff --git a/translations/en_US.ts b/translations/en_US.ts index 08e9b046..00358bfe 100644 --- a/translations/en_US.ts +++ b/translations/en_US.ts @@ -335,12 +335,12 @@ - + QRCode scanning failed - + Cannot find any QRCode from the image. @@ -797,14 +797,14 @@ - + Hide - - + + Show @@ -844,121 +844,90 @@ - - - - + + + + Connected: - - - Configuring PAC - - - - - Could not start PAC server as it is configured to use SOCKS, but it is not enabled - - - - - Could not start PAC server as it is configured to use HTTP, but it is not enabled - - - - + Duplicating Connection(s) - + Are you sure to duplicate these connection(s)? - + (Copy) - + Set auto connection - + Set %1 as auto connect. - - PAC Processing Failed - - - - - HTTP or SOCKS inbound is not properly configured for PAC - - - - - Qv2ray will continue, but will not set system proxy. - - - - + Cannot set system proxy - + Both HTTP and SOCKS inbounds are not enabled - + System proxy configured. - + Didn't set proxy for complex config. - + System proxy removed. - + Update Subscriptions - + There are subscriptions need to be updated, please go to subscriptions window to update them. - + These subscriptions are out-of-date: - + V2ray vcore terminated. - + V2ray vcore terminated unexpectedly. - + To solve the problem, read the V2ray log in the log text browser. @@ -968,17 +937,17 @@ - + Disconnected from: - + Removing Connection(s) - + Are you sure to remove selected connection(s)? @@ -994,7 +963,7 @@ - + Not Connected @@ -1083,6 +1052,11 @@ Switch to Qv2ray log + + + Plugins + + OutboundEditor @@ -1207,15 +1181,54 @@ + + PluginManageWindow + + + + + Loaded + + + + + + + Not loaded + + + + + Disabling a plugin + + + + + This plugin will keep loaded until the next time Qv2ray starts. + + + + + + Plugin not loaded + + + + + + This plugin has been unloaded or has been disabled, please enable or reload the plugin to continue. + + + PreferencesWindow - - - - - + + + + + Preferences @@ -1294,11 +1307,6 @@ HTTP - - - SOCKS - - Set System Proxy @@ -1349,96 +1357,11 @@ HTTP Settings - - - PAC Settings - - - - - The system proxy will be configured to use the PAC instead of HTTP and SOCKS. - - - - - Local IP for PAC - - 127.0.0.1 - - - Use Proxy - - - - - Import GFWList - - - - - Mirror: Gitlab - - - - - Github - - - - - Mirror: Pagure - - - - - Mirror: Repo.or.cz - - - - - Mirror: Bitbucket - - - - - Mirror: TuxFamily - - - - - GFWList File - - - - - Download with System Proxy - - - - - Go - - - - - Edit PAC - - - - - Open PAC Folder - - - - - PAC Access Path - - Connection Settings @@ -1601,13 +1524,13 @@ - + Bold - + Italic @@ -1707,23 +1630,23 @@ - + Page - + Item(s) - - + + Enable tProxy Support - + to this path: @@ -1733,120 +1656,99 @@ - + Update is disabled by your vendor. - + Duplicated port numbers detected, please check the port number settings. - + Invalid inbound listening address. - + Open V2ray assets folder - + Open V2ray core file - + This will append capabilities to the V2ray executable. - + Qv2ray will copy your V2ray core to this path: - + If anything goes wrong after enabling this, please check issue #57 or the link below: - + Qv2ray cannot copy one or both V2ray files from: - - + + Failed to setcap onto V2ray executable. You may need to run `setcap` manually. - + tProxy is not supported on macOS and Windows - + Apply network toolbar settings - + All other modified settings will be applied as well after this object. - + Do you want to continue? - - Select GFWList in base64 - - - - - - Download GFWList - - - - - Operation is cancelled. - - - - - Successfully downloaded GFWList. - - - - + Start with boot - + Failed to set auto start option. - - + + V2ray Core Settings - + V2ray path configuration check passed. - + Current version of V2ray is: @@ -2054,114 +1956,114 @@ p, li { white-space: pre-wrap; } - + Cannot Start Qv2ray - + Cannot find a place to store config files. - + Qv2ray has searched these paths below: - + It usually means you don't have the write permission to all of those locations. - - - + + + Qv2ray will now exit. - + Failed to initialise Qv2ray - + Please report if you think it's a bug. - + You cannot run Qv2ray as root, please use --I-just-wanna-run-with-root if you REALLY want to do so. - + --> USE IT AT YOUR OWN RISK! - + Debug version - + Qv2ray Cannot Continue - + You are running a lower version of Qv2ray compared to the current config file. - + Please check if there's an issue explaining about it. - + Or submit a new issue if you think this is an error. - + Dependency Missing - + This could be caused by a missing of `openssl` package in your system. - + If you are using an AppImage from Github Action, please report a bug. - + Cannot find openssl libs - + Failed to determine the location of config file: - + Qv2ray has found a config file, but it failed to be loaded due to some errors. - + A workaround is to remove the this file and restart Qv2ray: - + Technical Details @@ -2173,7 +2075,7 @@ p, li { white-space: pre-wrap; } - + Qv2ray - A cross-platform Qt frontend for V2ray. @@ -2184,25 +2086,21 @@ p, li { white-space: pre-wrap; } - Deprecated - PAC is now deprecated and is not encouraged to be used anymore. - It will be removed or be provided as a plugin in the future. - PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible. @@ -2382,65 +2280,75 @@ p, li { white-space: pre-wrap; } - + core executable file %1 does not exist - + cannot open core executable file %1 in read-only mode - + core executable file %1 is an empty file - + core executable file %1 is too short to be executed - + cannot deduce the type of core executable file %1 - + Windows PE executable - + macOS Mach-O executable - + ELF x86 executable - + ELF amd64 executable - + ELF arm64 executable - + + ELF arm executable + + + + other ELF executable - + unknown abi + + + Please contact the plugin provider or report the issue to Qv2ray Workgroup. + + Qv2ray::common::QvCommandArgParser @@ -2466,6 +2374,11 @@ p, li { white-space: pre-wrap; } + Disable plugin feature + + + + Enable Qv2ray network toolbar plugin @@ -2484,44 +2397,89 @@ p, li { white-space: pre-wrap; } - Qv2ray::components::pac::PACServer + Qv2ray::components::plugins::QvPluginHost - - PAC Handler + + This plugin was built against an incompactable version of Qv2ray Plugin Interface. - - Failed to listen PAC request on this port, please verify the permissions + + No Special Type + + + + + Connection Kernel + + + + + Connection String Serializer/Deserializer + + + + + Unknown/unsupported plugin type. + + + + + No hook + + + + + Connection State Change + + + + + Connection Change + + + + + Statistics Event + + + + + System Proxy + + + + + Unknown/unsupported hook type. Qv2ray::core::handlers::QvConfigHandler - - + + Default Group - + File does not exist. - - + + Group does not exist - + Update Subscription - + %1 entrie(s) have been found from the subscription source, do you want to continue? @@ -3497,6 +3455,79 @@ Maybe you have downloaded the wrong core? + + w_PluginManager + + + Plugin Manager + + + + + Plugins + + + + + Plugin Info + + + + + Name + + + + + Author + + + + + Description + + + + + Library Path + + + + + State + + + + + Capability + + + + + Special Type + + + + + Error Message + + + + + Open settings page for current plugin + + + + + Open plugin settings + + + + + Manually Edit Settings + + + w_SubscribeEditor diff --git a/translations/ja_JP.ts b/translations/ja_JP.ts index db153445..ff7fb549 100644 --- a/translations/ja_JP.ts +++ b/translations/ja_JP.ts @@ -445,12 +445,12 @@ インポートする画像を選択してください - + QRCode scanning failed QRコードのスキャンに失敗しました - + Cannot find any QRCode from the image. 画像からQRコードが見つかりません。 @@ -1017,7 +1017,7 @@ - + Not Connected 接続されていません @@ -1084,7 +1084,7 @@ #Restart - + Hide 非表示 @@ -1147,8 +1147,8 @@ Subscription: - - + + Show 表示 @@ -1226,58 +1226,53 @@ ダウンロードリンク: - - - - + + + + Connected: 接続済み: - - Configuring PAC - PAC設定 + PAC設定 - Could not start PAC server as it is configured to use SOCKS, but it is not enabled - SOCKSを使用するため、PACサーバーを起動できませんでした + SOCKSを使用するため、PACサーバーを起動できませんでした - Could not start PAC server as it is configured to use HTTP, but it is not enabled - HTTPを使用するため、PACサーバーを起動できませんでした + HTTPを使用するため、PACサーバーを起動できませんでした - + Duplicating Connection(s) 項目をコピー - + Are you sure to duplicate these connection(s)? 選択した項目をコピーしますか? - + (Copy) (コピー) - + Set auto connection 自動接続設定 - + Set %1 as auto connect. %1 を自動接続として設定します。 - PAC Processing Failed - PAC処理に失敗しました + PAC処理に失敗しました Please reset the settings in Preference Window @@ -1288,37 +1283,35 @@ System proxy cleared. - HTTP or SOCKS inbound is not properly configured for PAC - HTTPまたはSOCKSインバウンドがPAC用に正しく設定されていません + HTTPまたはSOCKSインバウンドがPAC用に正しく設定されていません - Qv2ray will continue, but will not set system proxy. - Qv2rayは続行されますが、システムプロキシは設定されません。 + Qv2rayは続行されますが、システムプロキシは設定されません。 - + Cannot set system proxy Cannot set system proxy - + Both HTTP and SOCKS inbounds are not enabled HTTPインバウンドとSOCKSインバウンドの両方とも無効です - + System proxy configured. システムプロキシが設定されています。 - + Didn't set proxy for complex config. 複雑構成のプロキシ設定を行いませんでした。 - + System proxy removed. システムプロキシが削除されました。 @@ -1331,17 +1324,17 @@ Cannot set proxy for complex config. - + Update Subscriptions サブスクリプションを更新 - + There are subscriptions need to be updated, please go to subscriptions window to update them. 更新が必要なサブスクリプションがあります。サブスクリプションウィンドウに移動して更新してください。 - + These subscriptions are out-of-date: これらのサブスクリプションは最新ではありません: @@ -1378,17 +1371,17 @@ Rename a Connection - + V2ray vcore terminated. V2ray コアが終了しました。 - + V2ray vcore terminated unexpectedly. V2rayコアが終了しました。 - + To solve the problem, read the V2ray log in the log text browser. 問題を解決するには、ログテキストブラウザーでV2rayログを確認してください。 @@ -1424,7 +1417,7 @@ Already connected to: - + Disconnected from: 接続切断されました: @@ -1441,12 +1434,12 @@ The name you suggested is not valid, please try another. - + Removing Connection(s) 項目を削除 - + Are you sure to remove selected connection(s)? 選択した項目を削除しますか? @@ -1494,6 +1487,11 @@ Share Connection Share Connection + + + Plugins + + OutboundEditor @@ -1622,15 +1620,54 @@ ユーザー名 + + PluginManageWindow + + + + + Loaded + + + + + + + Not loaded + + + + + Disabling a plugin + + + + + This plugin will keep loaded until the next time Qv2ray starts. + + + + + + Plugin not loaded + + + + + + This plugin has been unloaded or has been disabled, please enable or reload the plugin to continue. + + + PreferencesWindow - - - - - + + + + + Preferences 設定 @@ -1723,9 +1760,8 @@ HTTP - SOCKS - SOCKS + SOCKS @@ -1778,19 +1814,16 @@ HTTP設定 - PAC Settings - PAC設定 + PAC設定 - The system proxy will be configured to use the PAC instead of HTTP and SOCKS. - システムプロキシは、HTTPもしくはSOCKSではなく、PACを使用するように設定されました。 + システムプロキシは、HTTPもしくはSOCKSではなく、PACを使用するように設定されました。 - Local IP for PAC - PACのローカルIP + PACのローカルIP @@ -1798,74 +1831,60 @@ 127.0.0.1 - Use Proxy - プロキシを使う + プロキシを使う - Import GFWList - GFWListをインポートする + GFWListをインポートする - Mirror: Gitlab - Mirror: Gitlab + Mirror: Gitlab - Github - Github + Github - Mirror: Pagure - Mirror: Pagure + Mirror: Pagure - Mirror: Repo.or.cz - Mirror: Repo.or.cz + Mirror: Repo.or.cz - Mirror: Bitbucket - Mirror: Bitbucket + Mirror: Bitbucket - Mirror: TuxFamily - Mirror: TuxFamily + Mirror: TuxFamily - GFWList File - GFWListファイル + GFWListファイル - Download with System Proxy - システムプロキシでダウンロード + システムプロキシでダウンロード - Go - 実行 + 実行 - Edit PAC - PACを編集 + PACを編集 - Open PAC Folder - PACフォルダーを開く + PACフォルダーを開く - PAC Access Path - PACアクセスパス + PACアクセスパス @@ -2098,13 +2117,13 @@ - + Bold Bold - + Italic 斜体 @@ -2216,23 +2235,23 @@ ダークモードテーマを使用する - + Page ページ - + Item(s) アイテム - - + + Enable tProxy Support tProxyサポートを有効にする - + to this path: このパスへ: @@ -2242,73 +2261,73 @@ Qv2rayネットワークツールバーは無効になっており、まだテスト中です。 --withToolbarPluginを追加して有効にします。 - + Update is disabled by your vendor. ベンダーによって更新が無効になっています。 - + Duplicated port numbers detected, please check the port number settings. 重複したポート番号が検出されました。ポート番号の設定を確認してください。 - + Invalid inbound listening address. 無効なインバウンドリスニングアドレス。 - + Open V2ray assets folder V2rayアセットフォルダーを開く - + Open V2ray core file V2rayコアファイルを開く - + This will append capabilities to the V2ray executable. これにより、V2ray実行可能ファイルに機能が追加されます。 - + Qv2ray will copy your V2ray core to this path: Qv2rayは、V2rayコアを次のパスにコピーします: - + If anything goes wrong after enabling this, please check issue #57 or the link below: これを有効にした後に何か問題が発生した場合は、問題#57または以下のリンクを確認してください: - + Qv2ray cannot copy one or both V2ray files from: Qv2rayは、V2rayファイルの一つまたは二つを以下からコピーできません: - - + + Failed to setcap onto V2ray executable. You may need to run `setcap` manually. V2ray実行可能ファイルへのsetcapに失敗しました。`setcap`を手動で実行する必要がある可能性があります。 - + tProxy is not supported on macOS and Windows tProxyはmacOSおよびWindowsではサポートされていません - + Apply network toolbar settings ネットワークツールバー設定を適用する - + All other modified settings will be applied as well after this object. All other modified settings will be applied as well after this object. - + Do you want to continue? 続けますか? @@ -2321,49 +2340,44 @@ Please restart Qv2ray to fully apply this feature. - Select GFWList in base64 - Base64でGFWListを選択します + Base64でGFWListを選択します - - Download GFWList - GFWListをダウンロードする + GFWListをダウンロードする - Operation is cancelled. - 操作はキャンセルされます。 + 操作はキャンセルされます。 - Successfully downloaded GFWList. - GFWListを正常にダウンロードしました。 + GFWListを正常にダウンロードしました。 - + Start with boot システム起動時に起動します - + Failed to set auto start option. 自動起動オプションの設定に失敗しました。 - - + + V2ray Core Settings V2ray Core設定 - + V2ray path configuration check passed. V2rayパス構成チェックに通過しました。 - + Current version of V2ray is: V2rayの現在のバージョンは次のとおりです: @@ -2517,34 +2531,34 @@ p, li { white-space: pre-wrap; } Qv2rayはここから設定ファイルをロードできません: - + Cannot Start Qv2ray Qv2rayを起動できません - + Cannot find a place to store config files. 設定ファイルの保存先が見つかりません。 - + Qv2ray has searched these paths below: Qv2rayは以下のパスを検索しました: - + It usually means you don't have the write permission to all of those locations. 通常、これらの場所すべてに対する書き込み権限がないことを意味します。 - - - + + + Qv2ray will now exit. Qv2rayは終了します。 - + Failed to initialise Qv2ray Qv2rayの初期化に失敗しました @@ -2553,17 +2567,17 @@ p, li { white-space: pre-wrap; } 構成ファイルの場所を特定できませんでした。 - + Please report if you think it's a bug. バグだと思われる場合は報告してください。 - + You cannot run Qv2ray as root, please use --I-just-wanna-run-with-root if you REALLY want to do so. Qv2rayをルートとして実行することはできません。本当に実行したい場合は、--I-just-wanna-run-with-rootを使用してください。 - + --> USE IT AT YOUR OWN RISK! --> 自分の責任で使用してください! @@ -2576,67 +2590,67 @@ p, li { white-space: pre-wrap; } Qv2ray will continue running, but you cannot change the UI language. - + Qv2ray Cannot Continue Qv2rayを続行できません - + You are running a lower version of Qv2ray compared to the current config file. 現在の構成ファイルと比較して、より低いバージョンのQv2rayを実行しています。 - + Please check if there's an issue explaining about it. 関連説明があるかどうかを確認してください。 - + Or submit a new issue if you think this is an error. または、これがエラーだと思う場合は、新しい問題として送信してください。 - + Dependency Missing 依存関係がありません - + This could be caused by a missing of `openssl` package in your system. これは、システムに `openssl`パッケージが存在しないことが原因である可能性があります。 - + If you are using an AppImage from Github Action, please report a bug. Github ActionsのAppImageを使用している場合は、バグを報告してください。 - + Cannot find openssl libs OpenSSLライブラリが見つかりません - + Failed to determine the location of config file: 構成ファイルの場所を特定できませんでした: - + Qv2ray has found a config file, but it failed to be loaded due to some errors. Qv2rayは設定ファイルを見つけましたが、いくつかのエラーのためにロードに失敗しました。 - + A workaround is to remove the this file and restart Qv2ray: 回避策は、このファイルを削除してQv2rayを再起動することです: - + Debug version デバッグバージョン - + Technical Details 技術詳細 @@ -2753,7 +2767,7 @@ p, li { white-space: pre-wrap; } なし - + Qv2ray - A cross-platform Qt frontend for V2ray. Qv2ray - V2ray用のクロスプラットフォームQtフロントエンド。 @@ -2855,25 +2869,21 @@ p, li { white-space: pre-wrap; } - Deprecated 非推奨 - PAC is now deprecated and is not encouraged to be used anymore. PACは非推奨になり、使用を推奨されなくなりました。 - It will be removed or be provided as a plugin in the future. 今後削除されるか、プラグインとして提供されます。 - PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible. PACは現在も機能しますが、できるだけ早くV2Rayの組み込みルーティングに切り替えてください。 @@ -2913,65 +2923,75 @@ p, li { white-space: pre-wrap; } インバウンド - + core executable file %1 does not exist コア実行可能ファイル%1は存在しません - + cannot open core executable file %1 in read-only mode コア実行可能ファイル%1を読み取り専用モードで開けません - + core executable file %1 is an empty file コア実行可能ファイル%1は空のファイルです - + core executable file %1 is too short to be executed コア実行可能ファイル%1は実行するには短すぎます - + cannot deduce the type of core executable file %1 コア実行可能ファイル%1のタイプを推測できません - + Windows PE executable Windows PE実行可能ファイル - + macOS Mach-O executable macOS Mach-O実行可能ファイル - + ELF x86 executable ELF x86実行可能ファイル - + ELF amd64 executable ELF amd64実行可能ファイル - + ELF arm64 executable ELF arm64実行可能ファイル - + + ELF arm executable + + + + other ELF executable 他のELF実行可能ファイル - + unknown abi 不明なABI + + + Please contact the plugin provider or report the issue to Qv2ray Workgroup. + + Qv2ray::common::QvCommandArgParser @@ -2995,6 +3015,11 @@ p, li { white-space: pre-wrap; } Disable manually set QT_SCALE_FACTOR QT_SCALE_FACTORの手動設定を無効にする + + + Disable plugin feature + + Enable HiDPI support for Qt QtのHiDPIサポートを有効にする @@ -3004,7 +3029,7 @@ p, li { white-space: pre-wrap; } QtのHiDPIサポートを強制的に有効にする - + Enable Qv2ray network toolbar plugin Qv2rayネットワークツールバープラグインを有効にする @@ -3025,42 +3050,98 @@ p, li { white-space: pre-wrap; } Qv2ray::components::pac::PACServer - PAC Handler - PACハンドラー + PACハンドラー - Failed to listen PAC request on this port, please verify the permissions - このポートでPACリクエストをリッスンできませんでした、権限を確認してください + このポートでPACリクエストをリッスンできませんでした、権限を確認してください + + + + Qv2ray::components::plugins::QvPluginHost + + + This plugin was built against an incompactable version of Qv2ray Plugin Interface. + + + + + No Special Type + + + + + Connection Kernel + + + + + Connection String Serializer/Deserializer + + + + + Unknown/unsupported plugin type. + + + + + No hook + + + + + Connection State Change + + + + + Connection Change + + + + + Statistics Event + + + + + System Proxy + システムプロキシ + + + + Unknown/unsupported hook type. + Qv2ray::core::handlers::QvConfigHandler - - + + Default Group デフォルトグループ - + File does not exist. ファイルが存在しません。 - - + + Group does not exist グループが存在しません - + Update Subscription サブスクリプションを更新 - + %1 entrie(s) have been found from the subscription source, do you want to continue? アップストリームから返されるノードは %1 つだけです。続行してもよろしいですか? @@ -4118,6 +4199,79 @@ Maybe you have downloaded the wrong core? /s + + w_PluginManager + + + Plugin Manager + + + + + Plugins + + + + + Plugin Info + + + + + Name + + + + + Author + + + + + Description + + + + + Library Path + + + + + State + + + + + Capability + + + + + Special Type + + + + + Error Message + + + + + Open settings page for current plugin + + + + + Open plugin settings + + + + + Manually Edit Settings + + + w_SubscribeEditor diff --git a/translations/ru_RU.ts b/translations/ru_RU.ts index 68492008..14c8f70e 100644 --- a/translations/ru_RU.ts +++ b/translations/ru_RU.ts @@ -429,12 +429,12 @@ Выберите изображение для импорта - + QRCode scanning failed Ошибка сканирования QRCode - + Cannot find any QRCode from the image. Не удается найти QRCode в изображении. @@ -1001,7 +1001,7 @@ - + Not Connected @@ -1068,7 +1068,7 @@ #Перезапуск - + Hide Скрыть @@ -1131,8 +1131,8 @@ Подписка: - - + + Show Показать @@ -1210,58 +1210,53 @@ Ссылка для скачивания: - - - - + + + + Connected: Подключен: - - Configuring PAC - Настройка PAC + Настройка PAC - Could not start PAC server as it is configured to use SOCKS, but it is not enabled - Не удалось запустить PAC сервер, так как он настроен на использование SOCKS, но он не включен + Не удалось запустить PAC сервер, так как он настроен на использование SOCKS, но он не включен - Could not start PAC server as it is configured to use HTTP, but it is not enabled - Не удалось запустить PAC сервер, так как он настроен на использование HTTP, но он не включен + Не удалось запустить PAC сервер, так как он настроен на использование HTTP, но он не включен - + Duplicating Connection(s) - + Are you sure to duplicate these connection(s)? - + (Copy) - + Set auto connection - + Set %1 as auto connect. - PAC Processing Failed - Ошибка обработки PAC + Ошибка обработки PAC Please reset the settings in Preference Window @@ -1272,37 +1267,35 @@ Системный прокси очищен. - HTTP or SOCKS inbound is not properly configured for PAC - Входящие HTTP или SOCKS не настроены должным образом для PAC + Входящие HTTP или SOCKS не настроены должным образом для PAC - Qv2ray will continue, but will not set system proxy. - Qv2ray будет работать, но не сможет изменить системный прокси. + Qv2ray будет работать, но не сможет изменить системный прокси. - + Cannot set system proxy Не удается задать системный прокси - + Both HTTP and SOCKS inbounds are not enabled И HTTP, и SOCKS отключены - + System proxy configured. - + Didn't set proxy for complex config. - + System proxy removed. @@ -1315,17 +1308,17 @@ Не удается установить прокси для комплексной конфигурации. - + Update Subscriptions Обновить подписку - + There are subscriptions need to be updated, please go to subscriptions window to update them. Есть подписки, которые необходимо обновить, перейдите в окно подписок, чтобы обновить их. - + These subscriptions are out-of-date: Эти подписки устарели: @@ -1362,17 +1355,17 @@ Переименовать соединение - + V2ray vcore terminated. V2ray ядро прекращено. - + V2ray vcore terminated unexpectedly. Ядро V2ray неожиданно прервано. - + To solve the problem, read the V2ray log in the log text browser. Чтобы решить эту проблему, прочитайте журнал V2ray в текстовом браузере журнала. @@ -1408,7 +1401,7 @@ Уже подключен к: - + Disconnected from: Отключен от: @@ -1425,12 +1418,12 @@ Предложенное вами имя неверно, пожалуйста, попробуйте другое. - + Removing Connection(s) Удаление подключения(й) - + Are you sure to remove selected connection(s)? Вы уверены, что хотите удалить выбранные подключение(и)? @@ -1478,6 +1471,11 @@ Share Connection Поделиться подключением + + + Plugins + + OutboundEditor @@ -1606,15 +1604,54 @@ Имя пользователя + + PluginManageWindow + + + + + Loaded + + + + + + + Not loaded + + + + + Disabling a plugin + + + + + This plugin will keep loaded until the next time Qv2ray starts. + + + + + + Plugin not loaded + + + + + + This plugin has been unloaded or has been disabled, please enable or reload the plugin to continue. + + + PreferencesWindow - - - - - + + + + + Preferences Настройки @@ -1707,9 +1744,8 @@ HTTP - SOCKS - SOCKS + SOCKS @@ -1762,19 +1798,16 @@ Настройки HTTP - PAC Settings - Настройки PAC + Настройки PAC - The system proxy will be configured to use the PAC instead of HTTP and SOCKS. - Системный прокси-сервер будет настроен на использование PAC вместо HTTP и SOCKS. + Системный прокси-сервер будет настроен на использование PAC вместо HTTP и SOCKS. - Local IP for PAC - Локальный IP для PAC + Локальный IP для PAC @@ -1782,74 +1815,60 @@ 127.0.0.1 - Use Proxy - Использовать прокси + Использовать прокси - Import GFWList - Импорт GFWList + Импорт GFWList - Mirror: Gitlab - Mirror: Gitlab + Mirror: Gitlab - Github - GitHub + GitHub - Mirror: Pagure - Зеркало: Страсть + Зеркало: Страсть - Mirror: Repo.or.cz - Зеркало: Repo.or.cz + Зеркало: Repo.or.cz - Mirror: Bitbucket - Зеркало: Bitbucket + Зеркало: Bitbucket - Mirror: TuxFamily - Зеркало: TuxFamily + Зеркало: TuxFamily - GFWList File - GFWList файл + GFWList файл - Download with System Proxy - Скачать с системным прокси + Скачать с системным прокси - Go - Далее + Далее - Edit PAC - Изменить PAC + Изменить PAC - Open PAC Folder - Открыть папку PAC + Открыть папку PAC - PAC Access Path - PAC Access Path + PAC Access Path @@ -2082,13 +2101,13 @@ - + Bold Жирный - + Italic Курсив @@ -2200,23 +2219,23 @@ Использовать темную тему - + Page Стр. - + Item(s) Объекты - - + + Enable tProxy Support Включить tProxy поддержку - + to this path: на этот путь: @@ -2226,73 +2245,73 @@ Панель инструментов сети Qv2ray отключена и все еще тестируется. Добавьте --withToolbarPlugin для включения. - + Update is disabled by your vendor. - + Duplicated port numbers detected, please check the port number settings. Обнаружены дублированные номера портов, проверьте настройки номера порта. - + Invalid inbound listening address. Неверный входящий адрес прослушивания. - + Open V2ray assets folder Откройте папку активов v2ray - + Open V2ray core file Открыть основной файл V2ray - + This will append capabilities to the V2ray executable. Это добавит возможности к исполняемому файлу V2ray. - + Qv2ray will copy your V2ray core to this path: Qv2ray скопирует ваше ядро V2ray по этому пути: - + If anything goes wrong after enabling this, please check issue #57 or the link below: Если после включения этого параметра что-то пойдет не так, проверьте проблему № 57 или ссылку ниже: - + Qv2ray cannot copy one or both V2ray files from: Qv2ray не может скопировать один или оба файла V2ray из: - - + + Failed to setcap onto V2ray executable. You may need to run `setcap` manually. Не удалось установить setcap на исполняемый файл V2ray. Возможно, вам придется запустить `setcap` вручную. - + tProxy is not supported on macOS and Windows tProxy не поддерживается на macOS и Windows - + Apply network toolbar settings Применить настройки панели инструментов сети - + All other modified settings will be applied as well after this object. Все остальные измененные настройки будут применены и после этого объекта. - + Do you want to continue? Вы хотите продолжить? @@ -2305,49 +2324,44 @@ Пожалуйста, перезапустите Qv2ray, чтобы полностью применить эту функцию. - Select GFWList in base64 - Выберите GFWList в base64 + Выберите GFWList в base64 - - Download GFWList - Скачать GFWList + Скачать GFWList - Operation is cancelled. - Операция отменена. + Операция отменена. - Successfully downloaded GFWList. - Успешно загружено %1$s. + Успешно загружено %1$s. - + Start with boot Автозапуск - + Failed to set auto start option. Не удалось установить опцию автозапуска. - - + + V2ray Core Settings Настройки ядра V2ray - + V2ray path configuration check passed. Проверка конфигурации пути V2ray пройдена. - + Current version of V2ray is: Текущая версия V2ray: @@ -2489,34 +2503,34 @@ p, li { white-space: pre-wrap; } Qv2ray не может загрузить файл конфигурации отсюда: - + Cannot Start Qv2ray Невозможно запустить Qv2ray - + Cannot find a place to store config files. Не удается найти место для хранения конфигурационных файлов. - + Qv2ray has searched these paths below: Qv2ray искал эти пути ниже: - + It usually means you don't have the write permission to all of those locations. - - - + + + Qv2ray will now exit. Qv2ray теперь выйдет. - + Failed to initialise Qv2ray Не удалось инициализировать Qv2ray @@ -2525,17 +2539,17 @@ p, li { white-space: pre-wrap; } Не удалось определить местоположение конфигурационного файла. - + Please report if you think it's a bug. Пожалуйста, сообщите, если вы считаете об этом, если это ошибка. - + You cannot run Qv2ray as root, please use --I-just-wanna-run-with-root if you REALLY want to do so. Вы не можете запустить Qv2ray как root, пожалуйста, используйте --I-just-wanna-run-with-root, если вы хотите это сделать. - + --> USE IT AT YOUR OWN RISK! --> ИСПОЛЬЗУЙТЕ ЕГО НА СВОЙ СТРАХ И РИСК! @@ -2548,67 +2562,67 @@ p, li { white-space: pre-wrap; } Qv2ray продолжит работу, но вы не сможете изменить язык интерфейса. - + Qv2ray Cannot Continue Qv2ray не может продолжить - + You are running a lower version of Qv2ray compared to the current config file. Вы используете более низкую версию Qv2ray по сравнению с текущим файлом конфигурации. - + Please check if there's an issue explaining about it. Пожалуйста, проверьте, есть ли проблема с объяснением этого. - + Or submit a new issue if you think this is an error. Или отправьте новую проблему, если вы считаете, что это ошибка. - + Dependency Missing Зависимость отсутствует - + This could be caused by a missing of `openssl` package in your system. Это может быть вызвано отсутствием пакета `openssl` в вашей системе. - + If you are using an AppImage from Github Action, please report a bug. Если вы используете AppImage из Github Action, пожалуйста, сообщите об ошибке. - + Cannot find openssl libs Не удается найти openssl libs - + Failed to determine the location of config file: - + Qv2ray has found a config file, but it failed to be loaded due to some errors. - + A workaround is to remove the this file and restart Qv2ray: - + Debug version - + Technical Details Технические детали @@ -2725,7 +2739,7 @@ p, li { white-space: pre-wrap; } N/A - + Qv2ray - A cross-platform Qt frontend for V2ray. Qv2ray - кросс-платформенный Qt фронтенд для V2ray. @@ -2827,25 +2841,21 @@ p, li { white-space: pre-wrap; } - Deprecated - PAC is now deprecated and is not encouraged to be used anymore. - It will be removed or be provided as a plugin in the future. - PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible. @@ -2885,65 +2895,75 @@ p, li { white-space: pre-wrap; } Входящее - + core executable file %1 does not exist - + cannot open core executable file %1 in read-only mode - + core executable file %1 is an empty file - + core executable file %1 is too short to be executed - + cannot deduce the type of core executable file %1 - + Windows PE executable - + macOS Mach-O executable - + ELF x86 executable - + ELF amd64 executable - + ELF arm64 executable - + + ELF arm executable + + + + other ELF executable - + unknown abi + + + Please contact the plugin provider or report the issue to Qv2ray Workgroup. + + Qv2ray::common::QvCommandArgParser @@ -2969,6 +2989,11 @@ p, li { white-space: pre-wrap; } + Disable plugin feature + + + + Enable Qv2ray network toolbar plugin Включить плагин панели инструментов сети Qv2ray @@ -2989,42 +3014,98 @@ p, li { white-space: pre-wrap; } Qv2ray::components::pac::PACServer - PAC Handler - PAC Handler + PAC Handler - Failed to listen PAC request on this port, please verify the permissions - Не удалось прослушать PAC-запрос для этого порта, проверьте разрешения + Не удалось прослушать PAC-запрос для этого порта, проверьте разрешения + + + + Qv2ray::components::plugins::QvPluginHost + + + This plugin was built against an incompactable version of Qv2ray Plugin Interface. + + + + + No Special Type + + + + + Connection Kernel + + + + + Connection String Serializer/Deserializer + + + + + Unknown/unsupported plugin type. + + + + + No hook + + + + + Connection State Change + + + + + Connection Change + + + + + Statistics Event + + + + + System Proxy + Системный прокси + + + + Unknown/unsupported hook type. + Qv2ray::core::handlers::QvConfigHandler - - + + Default Group - + File does not exist. - - + + Group does not exist - + Update Subscription Обновить подписку - + %1 entrie(s) have been found from the subscription source, do you want to continue? @@ -4043,6 +4124,79 @@ Maybe you have downloaded the wrong core? /s + + w_PluginManager + + + Plugin Manager + + + + + Plugins + + + + + Plugin Info + + + + + Name + + + + + Author + + + + + Description + + + + + Library Path + + + + + State + + + + + Capability + + + + + Special Type + + + + + Error Message + + + + + Open settings page for current plugin + + + + + Open plugin settings + + + + + Manually Edit Settings + + + w_SubscribeEditor diff --git a/translations/zh_CN.ts b/translations/zh_CN.ts index a35f721b..5a0e0427 100644 --- a/translations/zh_CN.ts +++ b/translations/zh_CN.ts @@ -350,12 +350,12 @@ 选择要导入的图像 - + QRCode scanning failed 二维码扫描失败 - + Cannot find any QRCode from the image. 无法从图像中找到任何二维码。 @@ -834,14 +834,14 @@ 0.00 B - + Hide 隐藏 - - + + Show 显示 @@ -893,121 +893,114 @@ 下载链接: - - - - + + + + Connected: 已连接: - - Configuring PAC - 配置 PAC + 配置 PAC - Could not start PAC server as it is configured to use SOCKS, but it is not enabled - 无法启动 PAC 服务器,因为它被配置为使用 SOCKS,但它没有启用 + 无法启动 PAC 服务器,因为它被配置为使用 SOCKS,但它没有启用 - Could not start PAC server as it is configured to use HTTP, but it is not enabled - 无法启动 PAC 服务器,因为它被配置为使用 HTTP ,但它尚未启用 + 无法启动 PAC 服务器,因为它被配置为使用 HTTP ,但它尚未启用 - + Duplicating Connection(s) 复制连接 - + Are you sure to duplicate these connection(s)? 您确定要复制这(些)连接吗? - + (Copy) (副本) - + Set auto connection 设置自动连接 - + Set %1 as auto connect. 已将 %1 设为自动连接。 - PAC Processing Failed - PAC 处理失败 + PAC 处理失败 - HTTP or SOCKS inbound is not properly configured for PAC - HTTP 或 SOCKS 入站配置不正确 + HTTP 或 SOCKS 入站配置不正确 - Qv2ray will continue, but will not set system proxy. - Qv2ray 将继续,但不会设置系统代理。 + Qv2ray 将继续,但不会设置系统代理。 - + Cannot set system proxy 无法设置系统代理 - + Both HTTP and SOCKS inbounds are not enabled HTTP 和 SOCKS 都没有启用 - + System proxy configured. 系统代理已设置。 - + Didn't set proxy for complex config. 未对复杂配置设置代理。 - + System proxy removed. 系统代理已清除。 - + Update Subscriptions 更新订阅 - + There are subscriptions need to be updated, please go to subscriptions window to update them. 有订阅需要更新,请转到订阅窗口进行更新。 - + These subscriptions are out-of-date: 这些订阅已过期: - + V2ray vcore terminated. V2ray 核心已终止。 - + V2ray vcore terminated unexpectedly. V2ray 核心意外终止。 - + To solve the problem, read the V2ray log in the log text browser. 要解决问题,请阅读日志文本浏览器中的 V2ray 日志。 @@ -1017,17 +1010,17 @@ 系统代理 - + Disconnected from: 已断开连接: - + Removing Connection(s) 删除连接 - + Are you sure to remove selected connection(s)? 您确定要删除这(些)连接吗? @@ -1043,7 +1036,7 @@ - + Not Connected 未连接 @@ -1132,6 +1125,11 @@ Switch to Qv2ray log 切换到 Qv2ray 日志 + + + Plugins + + OutboundEditor @@ -1260,15 +1258,54 @@ 用户名 + + PluginManageWindow + + + + + Loaded + + + + + + + Not loaded + + + + + Disabling a plugin + + + + + This plugin will keep loaded until the next time Qv2ray starts. + + + + + + Plugin not loaded + + + + + + This plugin has been unloaded or has been disabled, please enable or reload the plugin to continue. + + + PreferencesWindow - - - - - + + + + + Preferences 首选项 @@ -1348,9 +1385,8 @@ HTTP - SOCKS - SOCKS + SOCKS @@ -1403,19 +1439,16 @@ HTTP 设置 - PAC Settings - PAC 设置 + PAC 设置 - The system proxy will be configured to use the PAC instead of HTTP and SOCKS. - 系统代理将被配置为使用 PAC 而不是 HTTP 和 SOCKS。 + 系统代理将被配置为使用 PAC 而不是 HTTP 和 SOCKS。 - Local IP for PAC - PAC 本地 IP 地址 + PAC 本地 IP 地址 @@ -1423,74 +1456,60 @@ 127.0.0.1 - Use Proxy - 使用代理服务器 + 使用代理服务器 - Import GFWList - 导入 GFWList + 导入 GFWList - Mirror: Gitlab - 镜像源:Gitlab + 镜像源:Gitlab - Github - GitHub + GitHub - Mirror: Pagure - 镜像源:Pagure + 镜像源:Pagure - Mirror: Repo.or.cz - 镜像源:Repo.or.cz + 镜像源:Repo.or.cz - Mirror: Bitbucket - 镜像源:Bitbucket + 镜像源:Bitbucket - Mirror: TuxFamily - 镜像源:TuxFamily + 镜像源:TuxFamily - GFWList File - GFWList 文件 + GFWList 文件 - Download with System Proxy - 使用系统代理下载 + 使用系统代理下载 - Go - 开始 + 开始 - Edit PAC - 编辑 PAC + 编辑 PAC - Open PAC Folder - 打开 PAC 文件夹 + 打开 PAC 文件夹 - PAC Access Path - PAC 访问路径 + PAC 访问路径 @@ -1658,13 +1677,13 @@ - + Bold 粗体 - + Italic 斜体 @@ -1776,23 +1795,23 @@ 使用暗色模式主题 - + Page - + Item(s) 项目 - - + + Enable tProxy Support 启用 tProxy 支持 - + to this path: 到此路径: @@ -1802,120 +1821,115 @@ Qv2ray 的网络工具栏已被禁用,请使用 --withToolbarPlugin 来启用。 - + Update is disabled by your vendor. 自动更新已被供应者禁用。 - + Duplicated port numbers detected, please check the port number settings. 检测到重复的端口号,请检查端口号设置。 - + Invalid inbound listening address. 入站监听地址不可用。 - + Open V2ray assets folder 打开 V2ray 资源文件夹 - + Open V2ray core file 打开 V2ray 核心文件 - + This will append capabilities to the V2ray executable. 这会将功能附加到 V2ray 可执行文件。 - + Qv2ray will copy your V2ray core to this path: Qv2ray 会将您的 V2ray 核心复制到以下路径: - + If anything goes wrong after enabling this, please check issue #57 or the link below: 如果有出现任何问题,请参阅 Issue #57 或以下链接: - + Qv2ray cannot copy one or both V2ray files from: Qv2ray 无法从以下位置复制一个或两个 V2ray 文件: - - + + Failed to setcap onto V2ray executable. You may need to run `setcap` manually. 无法将 Capcap 设置到 V2ray 可执行文件上。 您可能需要手动运行“ setcap”。 - + tProxy is not supported on macOS and Windows 在 macOS 和 Windows 上不支持 tProxy - + Apply network toolbar settings 应用网络工具栏设置 - + All other modified settings will be applied as well after this object. 已经编辑的其它设置也会被同时应用。 - + Do you want to continue? 你想继续吗? - Select GFWList in base64 - 选择 base64 格式的 GFWList + 选择 base64 格式的 GFWList - - Download GFWList - 下载 GFWList + 下载 GFWList - Operation is cancelled. - 操作已取消。 + 操作已取消。 - Successfully downloaded GFWList. - 下载 GFWList 成功。 + 下载 GFWList 成功。 - + Start with boot 开机启动 - + Failed to set auto start option. 无法设置自动启动选项。 - - + + V2ray Core Settings V2ray 核心设置 - + V2ray path configuration check passed. V2ray 路径配置检查通过。 - + Current version of V2ray is: V2ray 当前版本是: @@ -2143,114 +2157,114 @@ p, li { white-space: pre-wrap; } Qv2ray 无法从这里加载配置文件: - + Cannot Start Qv2ray 无法启动 Qv2ray - + Cannot find a place to store config files. 找不到保存配置文件的地方。 - + Qv2ray has searched these paths below: Qv2ray 搜索了以下路径: - + It usually means you don't have the write permission to all of those locations. 这通常意味着您没有所有这些位置的文件写入权限。 - - - + + + Qv2ray will now exit. Qv2ray 现在将退出。 - + Failed to initialise Qv2ray 初始化 Qv2ray 失败 - + Please report if you think it's a bug. 如果您认为它是一个bug,请'报告。 - + You cannot run Qv2ray as root, please use --I-just-wanna-run-with-root if you REALLY want to do so. 您不能以root用户身份运行Qv2ray,如果您确实想这样做,请使用 --I-just-wanna-run-with-root。 - + --> USE IT AT YOUR OWN RISK! --> 请自负风险! - + Debug version 调试版 - + Qv2ray Cannot Continue Qv2ray 无法继续 - + You are running a lower version of Qv2ray compared to the current config file. 与当前配置文件相比,您正在运行一个较低版本的 Qv2ray。 - + Please check if there's an issue explaining about it. 请检查是否存在'有关此问题的说明。 - + Or submit a new issue if you think this is an error. 或者如果你认为这是一个错误,请提交一个新问题。 - + Dependency Missing 缺少依赖关系 - + This could be caused by a missing of `openssl` package in your system. 这可能是系统中缺少`openssl`软件包造成的。 - + If you are using an AppImage from Github Action, please report a bug. 如果您使用的是来自 Github Action 的 AppImage,请报告错误。 - + Cannot find openssl libs 找不到 OpenSSL 库 - + Failed to determine the location of config file: 无法确定配置文件的位置: - + Qv2ray has found a config file, but it failed to be loaded due to some errors. Qv2ray已找到配置文件,但由于某些错误而无法加载。 - + A workaround is to remove the this file and restart Qv2ray: 一种解决方法是删除此文件并重新启动Qv2ray: - + Technical Details 技术细节 @@ -2262,7 +2276,7 @@ p, li { white-space: pre-wrap; } 不适用 - + Qv2ray - A cross-platform Qt frontend for V2ray. Qv2ray - V2ray的跨平台Qt前端。 @@ -2289,25 +2303,21 @@ p, li { white-space: pre-wrap; } - Deprecated 已经过时 - PAC is now deprecated and is not encouraged to be used anymore. PAC 已经被标记为过时的功能,并且我们不建议您继续使用。 - It will be removed or be provided as a plugin in the future. 它可能会在将来某个版本被移除,或者作为单独的插件提供。 - PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible. PAC 目前将会继续工作,但是我们强烈建议您切换使用 V2ray 自带的路由功能。 @@ -2487,65 +2497,75 @@ p, li { white-space: pre-wrap; } 入站 - + core executable file %1 does not exist 核心可执行文件 %1 不存在 - + cannot open core executable file %1 in read-only mode 无法以只读模式打开核心可执行文件 %1 - + core executable file %1 is an empty file 核心可执行文件 %1 是空文件 - + core executable file %1 is too short to be executed 核心可执行文件 %1 文件过小 - + cannot deduce the type of core executable file %1 无法推测核心可执行文件 %1 的类型 - + Windows PE executable Windows PE 可执行文件 - + macOS Mach-O executable macOS Mach-O 可执行文件 - + ELF x86 executable ELF x86 可执行文件 - + ELF amd64 executable ELF amd64 可执行文件 - + ELF arm64 executable ELF arm64 可执行文件 - + + ELF arm executable + + + + other ELF executable 其他 ELF 可执行文件 - + unknown abi 未知 ABI + + + Please contact the plugin provider or report the issue to Qv2ray Workgroup. + + Qv2ray::common::QvCommandArgParser @@ -2569,6 +2589,11 @@ p, li { white-space: pre-wrap; } Disable manually set QT_SCALE_FACTOR 禁用 QT_SCALE_FACTOR + + + Disable plugin feature + + Enable HiDPI support for Qt 启用 Qt HiDPI 支持 @@ -2578,7 +2603,7 @@ p, li { white-space: pre-wrap; } 强制启用 Qt HiDPI 支持 - + Enable Qv2ray network toolbar plugin 启用 Qv2ray 网络工具栏插件 @@ -2599,42 +2624,98 @@ p, li { white-space: pre-wrap; } Qv2ray::components::pac::PACServer - PAC Handler - PAC 处理器 + PAC 处理器 - Failed to listen PAC request on this port, please verify the permissions - 无法在此端口监听 PAC 请求,请检查是否具有足够的权限 + 无法在此端口监听 PAC 请求,请检查是否具有足够的权限 + + + + Qv2ray::components::plugins::QvPluginHost + + + This plugin was built against an incompactable version of Qv2ray Plugin Interface. + + + + + No Special Type + + + + + Connection Kernel + + + + + Connection String Serializer/Deserializer + + + + + Unknown/unsupported plugin type. + + + + + No hook + + + + + Connection State Change + + + + + Connection Change + + + + + Statistics Event + + + + + System Proxy + 系统代理 + + + + Unknown/unsupported hook type. + Qv2ray::core::handlers::QvConfigHandler - - + + Default Group 默认分组 - + File does not exist. 文件不存在。 - - + + Group does not exist 分组不存在 - + Update Subscription 更新订阅 - + %1 entrie(s) have been found from the subscription source, do you want to continue? 订阅源仅返回了 %1 个节点,确定要继续吗? @@ -3656,6 +3737,79 @@ Maybe you have downloaded the wrong core? /s + + w_PluginManager + + + Plugin Manager + + + + + Plugins + + + + + Plugin Info + + + + + Name + + + + + Author + + + + + Description + + + + + Library Path + + + + + State + + + + + Capability + + + + + Special Type + + + + + Error Message + + + + + Open settings page for current plugin + + + + + Open plugin settings + + + + + Manually Edit Settings + + + w_SubscribeEditor