Merge pull request #515 from Qv2ray/dev

Qv2ray v2.5.0-pre1
This commit is contained in:
Qv2ray-dev 2020-04-18 08:42:55 +08:00 committed by GitHub
commit 8bc4e95442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
99 changed files with 6833 additions and 3526 deletions

View File

@ -81,10 +81,7 @@ jobs:
- name: macOS - ${{ matrix.qt_version }} - Build preparation - Install Packages - name: macOS - ${{ matrix.qt_version }} - Build preparation - Install Packages
if: matrix.platform == 'macos-latest' if: matrix.platform == 'macos-latest'
run: | run: |
brew install protobuf grpc ninja wget brew install protobuf grpc ninja
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
# -------------------------------------------------------- # --------------------------------------------------------
- name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Build preparation - Download Dependencies - name: Win-${{ matrix.arch }} - ${{ matrix.qt_version }} - Build preparation - Download Dependencies
shell: bash shell: bash
@ -99,18 +96,23 @@ jobs:
pathSource: ./libs/Qv2ray-deps-grpc-${{ matrix.arch }}-windows.7z pathSource: ./libs/Qv2ray-deps-grpc-${{ matrix.arch }}-windows.7z
pathTarget: ./libs pathTarget: ./libs
# ========================================================================================================= Generate MakeFile and Build # ========================================================================================================= 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 - name: macOS - ${{ matrix.qt_version }} - Generate Dependencies and Build
shell: bash shell: bash
if: matrix.platform == 'macos-latest' if: matrix.platform == 'macos-latest'
run: | run: |
mkdir build mkdir build
cd 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 cmake .. -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_DEPLOYMENT_TARGET=10.13 -DDS_STORE_SCRIPT=ON
sudo cmake --build . --target package --parallel $(sysctl -n hw.logicalcpu) sudo cmake --build . --parallel $(sysctl -n hw.logicalcpu)
cp qv2ray-*.dmg ../ sudo cmake --install .
- name: macOS - Get package name sudo appdmg ../assets/package_dmg.json ../Qv2ray.dmg
id: get_package
run: echo ::set-output name=NAME::$(basename qv2ray-*.dmg)
# -------------------------------------------------------- # --------------------------------------------------------
- name: Windows - ${{ matrix.qt_version }} - Generate Dependencies and Build - name: Windows - ${{ matrix.qt_version }} - Generate Dependencies and Build
shell: bash shell: bash
@ -177,15 +179,15 @@ jobs:
if: matrix.platform == 'macos-latest' if: matrix.platform == 'macos-latest'
uses: actions/upload-artifact@master uses: actions/upload-artifact@master
with: with:
name: ${{ steps.get_package.outputs.NAME }} name: Qv2ray-${{ github.sha }}.macOS-${{ matrix.arch }}.qt${{ matrix.qt_version }}.dmg
path: ${{ steps.get_package.outputs.NAME }} path: Qv2ray.dmg
- name: macOS - ${{ matrix.qt_version }} - Upload binaries to release - name: macOS - ${{ matrix.qt_version }} - Upload binaries to release
uses: svenstaro/upload-release-action@v1-release uses: svenstaro/upload-release-action@v1-release
if: github.event_name == 'release' && matrix.platform == 'macos-latest' && matrix.qt_version == '5.14.2' if: github.event_name == 'release' && matrix.platform == 'macos-latest' && matrix.qt_version == '5.14.2'
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ steps.get_package.outputs.NAME }} file: Qv2ray.dmg
asset_name: ${{ steps.get_package.outputs.NAME }} asset_name: Qv2ray-${{ steps.get_version.outputs.VERSION }}.macOS-${{ matrix.arch }}.dmg
tag: ${{ github.ref }} tag: ${{ github.ref }}
overwrite: true overwrite: true
# -------------------------------------------------------- # --------------------------------------------------------

6
.gitmodules vendored
View File

@ -10,12 +10,12 @@
[submodule "libs/libqvb"] [submodule "libs/libqvb"]
path = libs/libqvb path = libs/libqvb
url = https://github.com/Qv2ray/QvRPCBridge url = https://github.com/Qv2ray/QvRPCBridge
[submodule "3rdparty/cpp-httplib"]
path = 3rdparty/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "libs/puresource"] [submodule "libs/puresource"]
path = libs/puresource path = libs/puresource
url = https://github.com/Qv2ray/PureSource/ url = https://github.com/Qv2ray/PureSource/
[submodule "3rdparty/zxing-cpp"] [submodule "3rdparty/zxing-cpp"]
path = 3rdparty/zxing-cpp path = 3rdparty/zxing-cpp
url = https://github.com/nu-book/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/

View File

@ -1,17 +1,6 @@
language: shell language: shell
os: linux os: linux
dist: bionic dist: bionic
arch:
- amd64
- arm64
git:
depth: false
branches:
only:
- dev
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
env: env:
global: global:
@ -23,30 +12,22 @@ addons:
- name: snapcraft - name: snapcraft
channel: stable channel: stable
confinement: classic confinement: classic
- name: lxd
before_install: channel: stable
- 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
script: 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: after_failure:
- sudo journalctl -u snapd - sudo journalctl -u snapd
deploy: deploy:
- provider: snap - provider: launchpad
snap: qv2ray_*.snap slug: "~ymshenyu/qv2ray/+git/trunk"
channel: edge oauth_token: $LAUNCHPAD_OAUTH_TOKEN
skip_cleanup: true oauth_token_secret: $LAUNCHPAD_OAUTH_TOKEN_SECRET
on: on:
branch: dev all_branches: true
- provider: snap
snap: qv2ray_*.snap
channel: beta
skip_cleanup: true
on:
branch: /^v\d+\.\d+(\.\d+)?(-\S*)?$/

@ -1 +1 @@
Subproject commit f3f17e9a04e3db67e4a717fd2984754fd4555c24 Subproject commit db07dd4ffcbfdd62431584d499928e45b5864f40

@ -1 +1 @@
Subproject commit 4abe20afbfa5695ac7a9bce1298943b645aeffe9 Subproject commit 4baf2e74f64c9a6ce36d456491bb41d0f2ae999e

@ -1 +0,0 @@
Subproject commit e1acb949e74c663dc9dedc04e41d8bb0dfafb7c7

View File

@ -146,7 +146,11 @@ if(QV2RAY_DISABLE_AUTO_UPDATE)
add_definitions(-DDISABLE_AUTO_UPDATE) add_definitions(-DDISABLE_AUTO_UPDATE)
endif() endif()
set(QVPLUGIN_INTERFACE_INCLUDE_DIR "src/components/plugins/interface")
include(src/components/plugins/interface/QvPluginInterface.cmake)
set(QV2RAY_SOURCES set(QV2RAY_SOURCES
${QVPLUGIN_INTERFACE_HEADERS}
3rdparty/libsemver/version.cpp 3rdparty/libsemver/version.cpp
src/base/Qv2rayLog.cpp src/base/Qv2rayLog.cpp
src/common/CommandArgs.cpp src/common/CommandArgs.cpp
@ -161,11 +165,10 @@ set(QV2RAY_SOURCES
src/components/geosite/QvGeositeReader.cpp src/components/geosite/QvGeositeReader.cpp
src/components/latency/win/ICMPPinger.cpp src/components/latency/win/ICMPPinger.cpp
src/components/latency/QvTCPing.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.cpp
src/components/plugins/toolbar/QvToolbar_linux.cpp src/components/plugins/toolbar/QvToolbar_linux.cpp
src/components/plugins/toolbar/QvToolbar_win.cpp src/components/plugins/toolbar/QvToolbar_win.cpp
src/components/plugins/QvPluginHost.cpp
src/components/proxy/QvProxyConfigurator.cpp src/components/proxy/QvProxyConfigurator.cpp
src/components/route/RouteSchemeIO.cpp src/components/route/RouteSchemeIO.cpp
src/components/speedchart/speedplotview.cpp src/components/speedchart/speedplotview.cpp
@ -180,9 +183,10 @@ set(QV2RAY_SOURCES
src/core/connection/Serialization_vmess.cpp src/core/connection/Serialization_vmess.cpp
src/core/CoreUtils.cpp src/core/CoreUtils.cpp
src/core/handler/ConfigHandler.cpp src/core/handler/ConfigHandler.cpp
src/core/handler/V2rayInstanceHandler.cpp src/core/handler/KernelInstanceHandler.cpp
src/core/kernel/APIBackend.cpp src/core/kernel/APIBackend.cpp
src/core/kernel/KernelInteractions.cpp src/core/kernel/V2rayKernelInteractions.cpp
src/core/kernel/PluginKernelInteractions.cpp
src/core/kernel/QvKernelABIChecker.cpp src/core/kernel/QvKernelABIChecker.cpp
src/core/settings/SettingsBackend.cpp src/core/settings/SettingsBackend.cpp
src/core/settings/SettingsUpgrade.cpp src/core/settings/SettingsUpgrade.cpp
@ -205,6 +209,7 @@ set(QV2RAY_SOURCES
src/ui/w_MainWindow.cpp src/ui/w_MainWindow.cpp
src/ui/w_MainWindow_extra.cpp src/ui/w_MainWindow_extra.cpp
src/ui/w_PreferencesWindow.cpp src/ui/w_PreferencesWindow.cpp
src/ui/w_PluginManager.cpp
src/ui/w_ScreenShot_Core.cpp src/ui/w_ScreenShot_Core.cpp
src/ui/w_SubscriptionManager.cpp src/ui/w_SubscriptionManager.cpp
# ui files # ui files
@ -220,6 +225,7 @@ set(QV2RAY_SOURCES
src/ui/widgets/RouteSettingsMatrix.ui src/ui/widgets/RouteSettingsMatrix.ui
src/ui/w_MainWindow.ui src/ui/w_MainWindow.ui
src/ui/w_PreferencesWindow.ui src/ui/w_PreferencesWindow.ui
src/ui/w_PluginManager.ui
src/ui/w_ScreenShot_Core.ui src/ui/w_ScreenShot_Core.ui
# headers # headers
3rdparty/libsemver/version.hpp 3rdparty/libsemver/version.hpp
@ -246,8 +252,8 @@ set(QV2RAY_SOURCES
src/components/geosite/QvGeositeReader.hpp src/components/geosite/QvGeositeReader.hpp
src/components/latency/win/ICMPPinger.hpp src/components/latency/win/ICMPPinger.hpp
src/components/latency/QvTCPing.hpp src/components/latency/QvTCPing.hpp
src/components/pac/QvPACHandler.hpp
src/components/plugins/toolbar/QvToolbar.hpp src/components/plugins/toolbar/QvToolbar.hpp
src/components/plugins/QvPluginHost.hpp
src/components/proxy/QvProxyConfigurator.hpp src/components/proxy/QvProxyConfigurator.hpp
src/components/route/RouteSchemeIO.hpp src/components/route/RouteSchemeIO.hpp
src/components/route/presets/RouteScheme_V2rayN.hpp src/components/route/presets/RouteScheme_V2rayN.hpp
@ -260,8 +266,10 @@ set(QV2RAY_SOURCES
src/core/CoreSafeTypes.hpp src/core/CoreSafeTypes.hpp
src/core/CoreUtils.hpp src/core/CoreUtils.hpp
src/core/handler/ConfigHandler.hpp src/core/handler/ConfigHandler.hpp
src/core/handler/KernelInstanceHandler.hpp
src/core/kernel/APIBackend.hpp src/core/kernel/APIBackend.hpp
src/core/kernel/KernelInteractions.hpp src/core/kernel/V2rayKernelInteractions.hpp
src/core/kernel/PluginKernelInteractions.hpp
src/core/kernel/QvKernelABIChecker.hpp src/core/kernel/QvKernelABIChecker.hpp
src/core/settings/SettingsBackend.hpp src/core/settings/SettingsBackend.hpp
src/ui/editors/w_InboundEditor.hpp src/ui/editors/w_InboundEditor.hpp
@ -281,6 +289,7 @@ set(QV2RAY_SOURCES
src/ui/w_ImportConfig.hpp src/ui/w_ImportConfig.hpp
src/ui/w_MainWindow.hpp src/ui/w_MainWindow.hpp
src/ui/w_PreferencesWindow.hpp src/ui/w_PreferencesWindow.hpp
src/ui/w_PluginManager.hpp
src/ui/w_ScreenShot_Core.hpp src/ui/w_ScreenShot_Core.hpp
src/ui/w_SubscriptionManager.hpp src/ui/w_SubscriptionManager.hpp
assets/qv2ray.rc assets/qv2ray.rc
@ -356,6 +365,7 @@ if(APPLE)
) )
set(MACOSX_ICON "${CMAKE_SOURCE_DIR}/assets/icons/qv2ray.icns") 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} set_source_files_properties(${QM_FILES}
PROPERTIES PROPERTIES
MACOSX_PACKAGE_LOCATION Resources/lang MACOSX_PACKAGE_LOCATION Resources/lang
@ -368,6 +378,7 @@ if(APPLE)
set_target_properties(${PROJECT_NAME} set_target_properties(${PROJECT_NAME}
PROPERTIES PROPERTIES
MACOSX_BUNDLE TRUE MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${MACOSX_PLIST}
MACOSX_BUNDLE_BUNDLE_NAME "Qv2ray" MACOSX_BUNDLE_BUNDLE_NAME "Qv2ray"
MACOSX_BUNDLE_BUNDLE_VERSION ${QV2RAY_VERSION_STRING} MACOSX_BUNDLE_BUNDLE_VERSION ${QV2RAY_VERSION_STRING}
MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2019-2020 Qv2ray Development Group" MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2019-2020 Qv2ray Development Group"

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

BIN
assets/DS_Store Normal file

Binary file not shown.

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleGetInfoString</key>
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleLongVersionString</key>
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CSResourcesFileMapped</key>
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<string>True</string>
</dict>
</plist>

View File

@ -0,0 +1,238 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="48"
height="48"
viewBox="0 0 12.699999 12.7"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="Applogo_Frameless_Plain.svg"
inkscape:export-xdpi="512"
inkscape:export-ydpi="512">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient908">
<stop
style="stop-color:#6e7678;stop-opacity:1"
offset="0"
id="stop904" />
<stop
style="stop-color:#2e3235;stop-opacity:1"
offset="1"
id="stop906" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient847">
<stop
style="stop-color:#c1fe6f;stop-opacity:1"
offset="0"
id="stop843" />
<stop
style="stop-color:#60fe6f;stop-opacity:1"
offset="1"
id="stop845" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient5057">
<stop
style="stop-color:#3c3c3c;stop-opacity:1;"
offset="0"
id="stop5053" />
<stop
style="stop-color:#282728;stop-opacity:0.6574803"
offset="1"
id="stop5055" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4759">
<stop
style="stop-color:#2eec71;stop-opacity:1"
offset="0"
id="stop4755" />
<stop
style="stop-color:#1cdc9a;stop-opacity:1"
offset="1"
id="stop4757" />
</linearGradient>
<linearGradient
id="linearGradient4713"
inkscape:collect="always">
<stop
id="stop4709"
offset="0"
style="stop-color:#3de256;stop-opacity:1" />
<stop
id="stop4711"
offset="1"
style="stop-color:#2fbeba;stop-opacity:1" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4713"
id="linearGradient4707"
x1="89.965836"
y1="19.458199"
x2="108.00179"
y2="39.934193"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(38.574145,1.3181723)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient5057"
id="linearGradient5059"
x1="92.227142"
y1="48.940449"
x2="140.26065"
y2="94.022179"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient4759"
id="linearGradient953"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.34597512,0.06144722,-0.06655638,0.31941658,-25.1488,274.41205)"
x1="85.229973"
y1="28.293249"
x2="113.08728"
y2="35.515835" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient847"
id="linearGradient955"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.15666471,0,0,0.15666471,-5.1793298,256.3406)"
x1="39.630962"
y1="196.65211"
x2="56.496174"
y2="208.57474" />
<clipPath
clipPathUnits="userSpaceOnUse"
id="clipPath922">
<rect
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625"
id="rect924"
width="12.7"
height="12.170834"
x="9.5367426e-08"
y="284.56458"
ry="1.0638391" />
</clipPath>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient908"
id="linearGradient910"
x1="12.7"
y1="284.29999"
x2="12.7"
y2="297"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.87500008,0,0,0.87505491,0.81442048,36.315602)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="7.9900606"
inkscape:cx="26.213373"
inkscape:cy="25.087073"
inkscape:document-units="mm"
inkscape:current-layer="g889"
showgrid="false"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1"
inkscape:snap-object-midpoints="false"
inkscape:snap-smooth-nodes="false"
showguides="true"
inkscape:guide-bbox="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:snap-midpoints="false"
units="px"
inkscape:snap-nodes="false"
inkscape:snap-others="false"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:object-nodes="false"
inkscape:snap-center="false"
inkscape:snap-text-baseline="false"
inkscape:snap-global="false"
inkscape:snap-grids="false"
inkscape:snap-to-guides="false">
<inkscape:grid
type="xygrid"
id="grid885" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-284.29999)">
<g
id="g889"
transform="matrix(0.77674917,0,0,0.77993751,1.4662318,64.012038)">
<g
id="g44"
transform="matrix(1.1963196,0,0,1.1963196,-1.3111729,-57.928707)">
<path
sodipodi:nodetypes="cscccc"
inkscape:connector-curvature="0"
id="path879-7"
d="m 4.8977933,290.46043 c 0,0 -0.3822449,0.7135 -0.5365115,0.93777 -0.154295,0.22431 -0.5217135,0.4236 -0.5217135,0.4236 l 0.6747961,-0.0816 1.7063455,-0.35106 c 0,0 -0.7913874,-1.07043 -1.3229166,-0.92869 z"
style="fill:#27b181;fill-opacity:1;stroke:none;stroke-width:0.02480469px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#1cd28e;fill-opacity:1;stroke:none;stroke-width:0.08956835px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 4.1456075,290.71139 c 0,0 1.1173813,-2.75102 3.3019109,-0.59828 2.1845208,2.15274 3.6949266,5.47345 3.6949266,5.47345 0,0 -2.9944646,-4.26031 -6.9968375,-4.87517 z"
id="path4715-0"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cscc" />
<path
style="fill:url(#linearGradient953);fill-opacity:1;stroke:none;stroke-width:0.08956835px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 2.2137987,290.23705 c 0,0 -0.1508518,-2.94653 2.7747518,-1.81527 2.9256022,1.13127 6.6242245,4.28351 6.6242245,4.28351 0,0 -4.81123,-3.07932 -9.3989763,-2.46824 z"
id="path4715-6-93"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient955);fill-opacity:1;stroke:none;stroke-width:0.08956835px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 1.2940265,289.53029 c 0,0 -1.32291659,-2.64583 1.9849644,-2.35592 3.1595803,0.27691 7.8046181,2.09134 7.8046181,2.09134 0,0 -5.6043094,-1.52438 -9.7895825,0.26458 z"
id="path4715-6-9-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cscc" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.0 KiB

View File

@ -0,0 +1,310 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="tray-connected.svg"
inkscape:export-xdpi="558.54999"
inkscape:export-ydpi="558.54999"
inkscape:export-filename="/media/Storage/Projects/Qv2ray/assets/icons/ui_dark/design/tray-connected.svg.png">
<defs
id="defs2">
<linearGradient
id="linearGradient841"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop839" />
</linearGradient>
<linearGradient
id="linearGradient5057"
inkscape:collect="always">
<stop
id="stop5053"
offset="0"
style="stop-color:#3c3c3c;stop-opacity:1;" />
<stop
id="stop5055"
offset="1"
style="stop-color:#282728;stop-opacity:0.6574803" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4713">
<stop
style="stop-color:#3de256;stop-opacity:1"
offset="0"
id="stop4709" />
<stop
style="stop-color:#2fbeba;stop-opacity:1"
offset="1"
id="stop4711" />
</linearGradient>
<linearGradient
gradientTransform="translate(38.574145,1.3181723)"
gradientUnits="userSpaceOnUse"
y2="39.934193"
x2="108.00179"
y1="19.458199"
x1="89.965836"
id="linearGradient4707"
xlink:href="#linearGradient4713"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="94.022179"
x2="140.26065"
y1="48.940449"
x1="92.227142"
id="linearGradient5059"
xlink:href="#linearGradient5057"
inkscape:collect="always" />
<clipPath
id="clipPath922"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<clipPath
id="clipPath922-6"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924-2"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1510">
<feFlood
flood-opacity="0.976471"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1500" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1502" />
<feGaussianBlur
in="composite1"
stdDeviation="0.1"
result="blur"
id="feGaussianBlur1504" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1506" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1508" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1859">
<feFlood
flood-opacity="0.266667"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1849" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1851" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1853" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1855" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1857" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="-5.2209634"
inkscape:cy="5.0686426"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-bottom="5"
fit-margin-right="5"
inkscape:snap-global="true"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-grids="false"
inkscape:snap-object-midpoints="true"
showguides="true"
inkscape:guide-bbox="true"
scale-x="1"
guidecolor="#00ffff"
guideopacity="0.49803922"
guidehicolor="#ff5184"
guidehiopacity="0.62745098"
inkscape:snap-nodes="true"
inkscape:snap-others="false">
<inkscape:grid
type="xygrid"
id="grid815"
originx="0"
originy="0"
color="#3f513e"
opacity="0.04313725"
empcolor="#3f3742"
empopacity="0.16470588"
empspacing="5"
dotted="false" />
<sodipodi:guide
position="28.284675,2.8909227"
orientation="1,0"
id="guide924"
inkscape:locked="false" />
<sodipodi:guide
position="26.141,6.3542733"
orientation="1,0"
id="guide932"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,255,236)" />
<sodipodi:guide
position="0.79375,4.4979166"
orientation="1,0"
id="guide1884"
inkscape:locked="false" />
<sodipodi:guide
position="3.96875,5.0270833"
orientation="0,1"
id="guide1886"
inkscape:locked="false" />
<sodipodi:guide
position="5.0270833,0.26458333"
orientation="1,0"
id="guide1888"
inkscape:locked="false" />
<sodipodi:guide
position="4.4979166,0.79375"
orientation="0,1"
id="guide1890"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-1.9173392,-290.74464)">
<path
id="path1962"
transform="matrix(0.26458333,0,0,0.26458333,1.9173392,290.74464)"
style="fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 7,11 6.2929688,11.707031 8.5859375,14 H 18 V 13 H 9 Z M 6.2929688,11.707031 5,13 H 4 v 1 H 5.4140625 L 7,12.414062 Z M 3,3 V 18.999988 H 19 V 3 Z M 3.9999999,3.9999875 H 18 V 18 H 3.9999999 Z" />
<rect
style="opacity:0.5;fill:none;fill-opacity:1;stroke:none;stroke-width:0.04583338;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect844"
width="5.8208332"
height="5.8208447"
x="1.9173392"
y="290.74463"
inkscape:export-xdpi="64"
inkscape:export-ydpi="64" />
<path
style="fill:#ececec;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 4.5631726,292.06756 -0.2645834,0.26459 v 0.52916 l 0.2645834,-0.26458 v 0.26458 0.52917 l 0.5291666,0.79375 0.5291667,1.32291 v -0.79374 l 0.2645833,-0.26459 -0.2645833,-0.52916 v -0.79375 l -0.2645833,-0.52917 z"
id="path1934"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
<g
id="g868" />
<g
id="g873"
transform="translate(0.24804686,0.57052501)" />
<circle
style="fill:#999999;stroke-width:0.18761367;fill-opacity:1"
id="path43"
cx="6.2250867"
cy="295.03583"
r="0.96738303" />
<ellipse
cy="295.03583"
cx="6.2250867"
id="path43-3"
style="fill:#2edf46;fill-opacity:1;stroke-width:0.15462632"
rx="0.79291928"
ry="0.80168903" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="tray-systemproxy.svg"
inkscape:export-xdpi="558.54999"
inkscape:export-ydpi="558.54999">
<defs
id="defs2">
<linearGradient
id="linearGradient841"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop839" />
</linearGradient>
<linearGradient
id="linearGradient5057"
inkscape:collect="always">
<stop
id="stop5053"
offset="0"
style="stop-color:#3c3c3c;stop-opacity:1;" />
<stop
id="stop5055"
offset="1"
style="stop-color:#282728;stop-opacity:0.6574803" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4713">
<stop
style="stop-color:#3de256;stop-opacity:1"
offset="0"
id="stop4709" />
<stop
style="stop-color:#2fbeba;stop-opacity:1"
offset="1"
id="stop4711" />
</linearGradient>
<linearGradient
gradientTransform="translate(38.574145,1.3181723)"
gradientUnits="userSpaceOnUse"
y2="39.934193"
x2="108.00179"
y1="19.458199"
x1="89.965836"
id="linearGradient4707"
xlink:href="#linearGradient4713"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="94.022179"
x2="140.26065"
y1="48.940449"
x1="92.227142"
id="linearGradient5059"
xlink:href="#linearGradient5057"
inkscape:collect="always" />
<clipPath
id="clipPath922"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<clipPath
id="clipPath922-6"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924-2"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1510">
<feFlood
flood-opacity="0.976471"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1500" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1502" />
<feGaussianBlur
in="composite1"
stdDeviation="0.1"
result="blur"
id="feGaussianBlur1504" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1506" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1508" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1859">
<feFlood
flood-opacity="0.266667"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1849" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1851" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1853" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1855" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1857" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="5.778172"
inkscape:cy="7.0536028"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-bottom="5"
fit-margin-right="5"
inkscape:snap-global="true"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-grids="false"
inkscape:snap-object-midpoints="true"
showguides="true"
inkscape:guide-bbox="true"
scale-x="1"
guidecolor="#00ffff"
guideopacity="0.49803922"
guidehicolor="#ff5184"
guidehiopacity="0.62745098"
inkscape:snap-nodes="true"
inkscape:snap-others="false">
<inkscape:grid
type="xygrid"
id="grid815"
originx="0"
originy="0"
color="#3f513e"
opacity="0.04313725"
empcolor="#3f3742"
empopacity="0.16470588"
empspacing="5"
dotted="false" />
<sodipodi:guide
position="28.284675,2.8909227"
orientation="1,0"
id="guide924"
inkscape:locked="false" />
<sodipodi:guide
position="26.141,6.3542733"
orientation="1,0"
id="guide932"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,255,236)" />
<sodipodi:guide
position="0.79375,4.4979166"
orientation="1,0"
id="guide1884"
inkscape:locked="false" />
<sodipodi:guide
position="3.96875,5.0270833"
orientation="0,1"
id="guide1886"
inkscape:locked="false" />
<sodipodi:guide
position="5.0270833,0.26458333"
orientation="1,0"
id="guide1888"
inkscape:locked="false" />
<sodipodi:guide
position="4.4979166,0.79375"
orientation="0,1"
id="guide1890"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-1.9173392,-290.74464)">
<path
id="path1962"
transform="matrix(0.26458333,0,0,0.26458333,1.9173392,290.74464)"
style="fill:#e6e6e6;fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 7,11 6.2929688,11.707031 8.5859375,14 H 18 V 13 H 9 Z M 6.2929688,11.707031 5,13 H 4 v 1 H 5.4140625 L 7,12.414062 Z M 3,3 V 18.999988 H 19 V 3 Z M 3.9999999,3.9999875 H 18 V 18 H 3.9999999 Z" />
<rect
style="opacity:0.5;fill:none;fill-opacity:1;stroke:none;stroke-width:0.04583338;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect844"
width="5.8208332"
height="5.8208447"
x="1.9173392"
y="290.74463"
inkscape:export-xdpi="64"
inkscape:export-ydpi="64" />
<path
style="fill:#ececec;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 4.5631726,292.06756 -0.2645834,0.26459 v 0.52916 l 0.2645834,-0.26458 v 0.26458 0.52917 l 0.5291666,0.79375 0.5291667,1.32291 v -0.79374 l 0.2645833,-0.26459 -0.2645833,-0.52916 v -0.79375 l -0.2645833,-0.52917 z"
id="path1934"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
<g
id="g868" />
<g
id="g873"
transform="translate(0.24804686,0.57052501)" />
<circle
style="fill:#dfdfdf;stroke-width:0.18761367;fill-opacity:1"
id="path43"
cx="6.2250867"
cy="295.03583"
r="0.96738303" />
<ellipse
cy="295.03583"
cx="6.2250867"
id="path43-3"
style="fill:#4ddbf3;fill-opacity:1;stroke-width:0.15462632"
rx="0.79291928"
ry="0.80168903" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="tray-connected.svg"
inkscape:export-xdpi="558.54999"
inkscape:export-ydpi="558.54999">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient876">
<stop
style="stop-color:#31363b;stop-opacity:0.58823532"
offset="0"
id="stop872" />
<stop
style="stop-color:#31363b;stop-opacity:0;"
offset="1"
id="stop874" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient866">
<stop
style="stop-color:#31363b;stop-opacity:0.58823532"
offset="0"
id="stop862" />
<stop
style="stop-color:#31363b;stop-opacity:0;"
offset="1"
id="stop864" />
</linearGradient>
<linearGradient
id="linearGradient841"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop839" />
</linearGradient>
<linearGradient
id="linearGradient5057"
inkscape:collect="always">
<stop
id="stop5053"
offset="0"
style="stop-color:#3c3c3c;stop-opacity:1;" />
<stop
id="stop5055"
offset="1"
style="stop-color:#282728;stop-opacity:0.6574803" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4713">
<stop
style="stop-color:#3de256;stop-opacity:1"
offset="0"
id="stop4709" />
<stop
style="stop-color:#2fbeba;stop-opacity:1"
offset="1"
id="stop4711" />
</linearGradient>
<linearGradient
gradientTransform="translate(38.574145,1.3181723)"
gradientUnits="userSpaceOnUse"
y2="39.934193"
x2="108.00179"
y1="19.458199"
x1="89.965836"
id="linearGradient4707"
xlink:href="#linearGradient4713"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="94.022179"
x2="140.26065"
y1="48.940449"
x1="92.227142"
id="linearGradient5059"
xlink:href="#linearGradient5057"
inkscape:collect="always" />
<clipPath
id="clipPath922"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<clipPath
id="clipPath922-6"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924-2"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1510">
<feFlood
flood-opacity="0.976471"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1500" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1502" />
<feGaussianBlur
in="composite1"
stdDeviation="0.1"
result="blur"
id="feGaussianBlur1504" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1506" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1508" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1859">
<feFlood
flood-opacity="0.266667"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1849" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1851" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1853" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1855" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1857" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient866"
id="linearGradient868"
x1="4.2985892"
y1="292.06757"
x2="7.7381635"
y2="295.50714"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient876"
id="linearGradient878"
x1="5.6215057"
y1="294.44879"
x2="6.6798391"
y2="295.50714"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="4.561514"
inkscape:cy="3.2123107"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-bottom="5"
fit-margin-right="5"
inkscape:snap-global="true"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-grids="false"
inkscape:snap-object-midpoints="true"
showguides="true"
inkscape:guide-bbox="true"
scale-x="1"
guidecolor="#00ffff"
guideopacity="0.49803922"
guidehicolor="#ff5184"
guidehiopacity="0.62745098"
inkscape:snap-nodes="true"
inkscape:snap-others="false">
<inkscape:grid
type="xygrid"
id="grid815"
originx="0"
originy="0"
color="#3f513e"
opacity="0.04313725"
empcolor="#3f3742"
empopacity="0.16470588"
empspacing="5"
dotted="false" />
<sodipodi:guide
position="11.244792,0.79375"
orientation="1,0"
id="guide924"
inkscape:locked="false" />
<sodipodi:guide
position="-1.6867187,5.0270833"
orientation="1,0"
id="guide932"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,255,236)" />
<sodipodi:guide
position="0.79375,4.4979166"
orientation="1,0"
id="guide1884"
inkscape:locked="false" />
<sodipodi:guide
position="3.96875,5.0270833"
orientation="0,1"
id="guide1886"
inkscape:locked="false" />
<sodipodi:guide
position="5.0270833,0.26458333"
orientation="1,0"
id="guide1888"
inkscape:locked="false" />
<sodipodi:guide
position="4.4979166,0.79375"
orientation="0,1"
id="guide1890"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-1.9173392,-290.74464)">
<path
style="fill:url(#linearGradient878);stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.65"
d="m 5.6215058,295.50714 v -0.79375 l 0.2645833,-0.26458 0.79375,0.79375 v 0.26458 z"
id="path870"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient868);stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.65"
d="m 4.2985892,292.86131 2.3812499,2.38125 v -1.32292 l -1.3229166,-1.32292 -0.79375,-0.52916 -0.2645833,0.26458 z"
id="path860"
inkscape:connector-curvature="0" />
<path
id="path1962"
transform="matrix(0.26458333,0,0,0.26458333,1.9173392,290.74464)"
style="fill:#31363b;fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 7,11 6.2929688,11.707031 8.5859375,14 H 18 V 13 H 9 Z M 6.2929688,11.707031 5,13 H 4 v 1 H 5.4140625 L 7,12.414062 Z M 3,3 V 18.999988 H 19 V 3 Z M 3.9999999,3.9999875 H 18 V 18 H 3.9999999 Z" />
<rect
style="opacity:0.5;fill:none;fill-opacity:1;stroke:none;stroke-width:0.04583338;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect844"
width="5.8208332"
height="5.8208447"
x="1.9173392"
y="290.74463"
inkscape:export-xdpi="64"
inkscape:export-ydpi="64" />
<path
style="fill:#31363b;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 4.5631726,292.06756 -0.2645834,0.26459 v 0.52916 l 0.2645834,-0.26458 v 0.26458 0.52917 l 0.5291666,0.79375 0.5291667,1.32291 v -0.79374 l 0.2645833,-0.26459 -0.2645833,-0.52916 v -0.79375 l -0.2645833,-0.52917 z"
id="path1934"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
<circle
style="fill:#595959;fill-opacity:1;stroke-width:0.18761367"
id="path43"
cx="6.2266774"
cy="295.0452"
r="0.96738309" />
<ellipse
cy="295.0452"
cx="6.2266774"
id="path43-3"
style="fill:#2cee14;fill-opacity:1;stroke-width:0.15462632"
rx="0.79291928"
ry="0.80168903" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,359 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="22"
height="22"
viewBox="0 0 5.8208333 5.8208333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="tray-systemproxy.svg"
inkscape:export-xdpi="558.54999"
inkscape:export-ydpi="558.54999">
<defs
id="defs2">
<linearGradient
inkscape:collect="always"
id="linearGradient876">
<stop
style="stop-color:#31363b;stop-opacity:0.58823532"
offset="0"
id="stop872" />
<stop
style="stop-color:#31363b;stop-opacity:0;"
offset="1"
id="stop874" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient866">
<stop
style="stop-color:#31363b;stop-opacity:0.58823532"
offset="0"
id="stop862" />
<stop
style="stop-color:#31363b;stop-opacity:0;"
offset="1"
id="stop864" />
</linearGradient>
<linearGradient
id="linearGradient841"
osb:paint="solid">
<stop
style="stop-color:#000000;stop-opacity:1;"
offset="0"
id="stop839" />
</linearGradient>
<linearGradient
id="linearGradient5057"
inkscape:collect="always">
<stop
id="stop5053"
offset="0"
style="stop-color:#3c3c3c;stop-opacity:1;" />
<stop
id="stop5055"
offset="1"
style="stop-color:#282728;stop-opacity:0.6574803" />
</linearGradient>
<linearGradient
inkscape:collect="always"
id="linearGradient4713">
<stop
style="stop-color:#3de256;stop-opacity:1"
offset="0"
id="stop4709" />
<stop
style="stop-color:#2fbeba;stop-opacity:1"
offset="1"
id="stop4711" />
</linearGradient>
<linearGradient
gradientTransform="translate(38.574145,1.3181723)"
gradientUnits="userSpaceOnUse"
y2="39.934193"
x2="108.00179"
y1="19.458199"
x1="89.965836"
id="linearGradient4707"
xlink:href="#linearGradient4713"
inkscape:collect="always" />
<linearGradient
gradientUnits="userSpaceOnUse"
y2="94.022179"
x2="140.26065"
y1="48.940449"
x1="92.227142"
id="linearGradient5059"
xlink:href="#linearGradient5057"
inkscape:collect="always" />
<clipPath
id="clipPath922"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<clipPath
id="clipPath922-6"
clipPathUnits="userSpaceOnUse">
<rect
ry="1.0638391"
y="284.56458"
x="9.5367426e-08"
height="12.170834"
width="12.7"
id="rect924-2"
style="fill:none;fill-opacity:1;stroke:#23d829;stroke-width:0.044;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.72265625" />
</clipPath>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1510">
<feFlood
flood-opacity="0.976471"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1500" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1502" />
<feGaussianBlur
in="composite1"
stdDeviation="0.1"
result="blur"
id="feGaussianBlur1504" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1506" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1508" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter1859">
<feFlood
flood-opacity="0.266667"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1849" />
<feComposite
in="flood"
in2="SourceGraphic"
operator="in"
result="composite1"
id="feComposite1851" />
<feGaussianBlur
in="composite1"
stdDeviation="0.2"
result="blur"
id="feGaussianBlur1853" />
<feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1855" />
<feComposite
in="offset"
in2="SourceGraphic"
operator="out"
result="composite2"
id="feComposite1857" />
</filter>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient866"
id="linearGradient868"
x1="4.2985892"
y1="292.06757"
x2="7.7381635"
y2="295.50714"
gradientUnits="userSpaceOnUse" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient876"
id="linearGradient878"
x1="5.6215057"
y1="294.44879"
x2="6.6798391"
y2="295.50714"
gradientUnits="userSpaceOnUse" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32"
inkscape:cx="9.920889"
inkscape:cy="8.4623107"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1028"
inkscape:window-x="0"
inkscape:window-y="26"
inkscape:window-maximized="1"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
fit-margin-top="5"
fit-margin-left="5"
fit-margin-bottom="5"
fit-margin-right="5"
inkscape:snap-global="true"
inkscape:object-paths="false"
inkscape:snap-intersection-paths="false"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-grids="false"
inkscape:snap-object-midpoints="true"
showguides="true"
inkscape:guide-bbox="true"
scale-x="1"
guidecolor="#00ffff"
guideopacity="0.49803922"
guidehicolor="#ff5184"
guidehiopacity="0.62745098"
inkscape:snap-nodes="true"
inkscape:snap-others="false">
<inkscape:grid
type="xygrid"
id="grid815"
originx="0"
originy="0"
color="#3f513e"
opacity="0.04313725"
empcolor="#3f3742"
empopacity="0.16470588"
empspacing="5"
dotted="false" />
<sodipodi:guide
position="11.244792,0.79375"
orientation="1,0"
id="guide924"
inkscape:locked="false" />
<sodipodi:guide
position="-1.6867187,5.0270833"
orientation="1,0"
id="guide932"
inkscape:locked="false"
inkscape:label=""
inkscape:color="rgb(0,255,236)" />
<sodipodi:guide
position="0.79375,4.4979166"
orientation="1,0"
id="guide1884"
inkscape:locked="false" />
<sodipodi:guide
position="3.96875,5.0270833"
orientation="0,1"
id="guide1886"
inkscape:locked="false" />
<sodipodi:guide
position="5.0270833,0.26458333"
orientation="1,0"
id="guide1888"
inkscape:locked="false" />
<sodipodi:guide
position="4.4979166,0.79375"
orientation="0,1"
id="guide1890"
inkscape:locked="false" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-1.9173392,-290.74464)">
<path
style="fill:url(#linearGradient878);stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.65"
d="m 5.6215058,295.50714 v -0.79375 l 0.2645833,-0.26458 0.79375,0.79375 v 0.26458 z"
id="path870"
inkscape:connector-curvature="0" />
<path
style="fill:url(#linearGradient868);stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;opacity:0.65"
d="m 4.2985892,292.86131 2.3812499,2.38125 v -1.32292 l -1.3229166,-1.32292 -0.79375,-0.52916 -0.2645833,0.26458 z"
id="path860"
inkscape:connector-curvature="0" />
<path
id="path1962"
transform="matrix(0.26458333,0,0,0.26458333,1.9173392,290.74464)"
style="fill:#31363b;fill-opacity:1;stroke:none;stroke-width:0.99999994px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="M 7,11 6.2929688,11.707031 8.5859375,14 H 18 V 13 H 9 Z M 6.2929688,11.707031 5,13 H 4 v 1 H 5.4140625 L 7,12.414062 Z M 3,3 V 18.999988 H 19 V 3 Z M 3.9999999,3.9999875 H 18 V 18 H 3.9999999 Z" />
<rect
style="opacity:0.5;fill:none;fill-opacity:1;stroke:none;stroke-width:0.04583338;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect844"
width="5.8208332"
height="5.8208447"
x="1.9173392"
y="290.74463"
inkscape:export-xdpi="64"
inkscape:export-ydpi="64" />
<path
style="fill:#31363b;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 4.5631726,292.06756 -0.2645834,0.26459 v 0.52916 l 0.2645834,-0.26458 v 0.26458 0.52917 l 0.5291666,0.79375 0.5291667,1.32291 v -0.79374 l 0.2645833,-0.26459 -0.2645833,-0.52916 v -0.79375 l -0.2645833,-0.52917 z"
id="path1934"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccccccccc" />
<circle
style="fill:#595959;fill-opacity:1;stroke-width:0.18761367"
id="path43"
cx="6.2266774"
cy="295.0452"
r="0.96738309" />
<ellipse
cy="295.0452"
cx="6.2266774"
id="path43-3"
style="fill:#00c3ff;fill-opacity:1;stroke-width:0.15462632"
rx="0.79291928"
ry="0.80168903"
inkscape:export-filename="/media/Storage/Projects/Qv2ray/assets/icons/ui_light/tray-systemproxy.png"
inkscape:export-xdpi="2050.1499"
inkscape:export-ydpi="2050.1499" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -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" }
]
}

46
azure-pipelines.yml Normal file
View File

@ -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

57
cmake/CMakeDMGSetup.scpt Normal file
View File

@ -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

View File

@ -61,6 +61,14 @@ endif()
if(APPLE) if(APPLE)
set(CPACK_GENERATOR "DragNDrop") 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() endif()
include(CPack) include(CPack)

10
debian/changelog vendored
View File

@ -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 <ymshenyu@gmail.com> Sat, 11 Apr 2020 23:12:20 +0800
qv2ray (2.4.1-1) unstable; urgency=medium qv2ray (2.4.1-1) unstable; urgency=medium
* add: add new semver checker * add: add new semver checker

View File

@ -1 +1 @@
5150 5264

View File

@ -1 +1 @@
2.4.1 2.5.0

View File

@ -1 +1 @@
-pre1

View File

@ -32,5 +32,9 @@
<file>assets/icons/ui_light/locate.png</file> <file>assets/icons/ui_light/locate.png</file>
<file>assets/icons/ui_dark/sort.png</file> <file>assets/icons/ui_dark/sort.png</file>
<file>assets/icons/ui_light/sort.png</file> <file>assets/icons/ui_light/sort.png</file>
<file>assets/icons/ui_dark/tray-connected.png</file>
<file>assets/icons/ui_dark/tray-systemproxy.png</file>
<file>assets/icons/ui_light/tray-connected.png</file>
<file>assets/icons/ui_light/tray-systemproxy.png</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@ -83,6 +83,7 @@ parts:
sed -i 's|^Icon=.*|Icon=/usr/share/icons/hicolor/256x256/apps/qv2ray.png|g' assets/qv2ray.desktop sed -i 's|^Icon=.*|Icon=/usr/share/icons/hicolor/256x256/apps/qv2ray.png|g' assets/qv2ray.desktop
after: after:
- desktop-qt5 - desktop-qt5
- ppa
desktop-qt5: desktop-qt5:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
@ -108,8 +109,21 @@ parts:
- locales-all - locales-all
- xdg-user-dirs - xdg-user-dirs
- fcitx-frontend-qt5 - fcitx-frontend-qt5
after:
- ppa
qt5-gtk-platform: qt5-gtk-platform:
plugin: nil plugin: nil
stage-packages: stage-packages:
- qt5-gtk-platformtheme - 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

View File

@ -46,6 +46,7 @@ using namespace Qv2ray::base::objects::transfer;
#define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf") #define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf")
#define QV2RAY_CONNECTIONS_DIR (QV2RAY_CONFIG_DIR + "connections/") #define QV2RAY_CONNECTIONS_DIR (QV2RAY_CONFIG_DIR + "connections/")
#define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/") #define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/")
#define QV2RAY_PLUGIN_SETTINGS_DIR (QV2RAY_CONFIG_DIR + "plugin_settings/")
// Get GFWList and PAC file path. // Get GFWList and PAC file path.
#define QV2RAY_RULES_DIR (QV2RAY_CONFIG_DIR + "rules/") #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 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/")) ((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) #define QSTRN(num) QString::number(num)
@ -127,4 +136,27 @@ namespace Qv2ray
isExiting = true; isExiting = true;
QApplication::quit(); 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 } // namespace Qv2ray

View File

@ -41,6 +41,7 @@ const inline QString MODULE_FILEIO = "COMMON-FILEIO";
// //
const inline QString MODULE_PROXY = "COMPONENT-PROXY"; const inline QString MODULE_PROXY = "COMPONENT-PROXY";
const inline QString MODULE_UPDATE = "COMPONENT-UPDATE"; 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"; const inline QString MODULE_CORE_HANDLER = "QV2RAY-CORE";

View File

@ -257,13 +257,14 @@ namespace Qv2ray::base::objects
{ {
QString serverName; QString serverName;
bool allowInsecure; bool allowInsecure;
bool allowInsecureCiphers;
QList<QString> alpn; QList<QString> alpn;
QList<CertificateObject> certificates; QList<CertificateObject> certificates;
bool disableSystemRoot; 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 } // namespace transfer
// //

View File

@ -41,18 +41,6 @@ namespace Qv2ray::base::config
XTOSTRUCT(O(Pages)) 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 struct Qv2rayForwardProxyConfig
{ {
bool enableForwardProxy; bool enableForwardProxy;
@ -73,7 +61,6 @@ namespace Qv2ray::base::config
{ {
QString listenip; QString listenip;
bool setSystemProxy; bool setSystemProxy;
Qv2rayPACConfig pacConfig;
// SOCKS // SOCKS
bool useSocks; bool useSocks;
@ -89,26 +76,27 @@ namespace Qv2ray::base::config
objects::AccountObject httpAccount; objects::AccountObject httpAccount;
Qv2rayInboundsConfig() 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() 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, XTOSTRUCT(O(setSystemProxy, listenip, useSocks, useHTTP, socks_port, socks_useAuth, socksAccount, socksUDP, socksLocalIP, http_port,
http_port, http_useAuth, httpAccount)) http_useAuth, httpAccount))
}; };
struct Qv2rayUIConfig struct Qv2rayUIConfig
{ {
QString theme; QString theme;
QString language; QString language;
bool quietMode;
bool useDarkTheme; bool useDarkTheme;
bool useDarkTrayIcon; bool useDarkTrayIcon;
int maximumLogLines; int maximumLogLines;
Qv2rayUIConfig() : theme("Fusion"), language("en_US"), useDarkTheme(false), useDarkTrayIcon(true), maximumLogLines(500) 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 struct Qv2rayRouteConfig_Impl
@ -141,6 +129,15 @@ namespace Qv2ray::base::config
XTOSTRUCT(O(domainStrategy, domains, ips)) XTOSTRUCT(O(domainStrategy, domains, ips))
}; };
struct Qv2rayPluginConfig
{
QMap<QString, bool> pluginStates;
bool v2rayIntegration;
int portAllocationStart;
Qv2rayPluginConfig() : pluginStates(), v2rayIntegration(true), portAllocationStart(15000){};
XTOSTRUCT(O(pluginStates, v2rayIntegration))
};
struct Qv2rayConnectionConfig struct Qv2rayConnectionConfig
{ {
bool bypassCN; bool bypassCN;
@ -218,6 +215,25 @@ namespace Qv2ray::base::config
XTOSTRUCT(O(ignoredVersion, updateChannel)) 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 struct Qv2rayConfig
{ {
int config_version; int config_version;
@ -234,19 +250,53 @@ namespace Qv2ray::base::config
// //
Qv2rayUIConfig uiConfig; Qv2rayUIConfig uiConfig;
Qv2rayAPIConfig apiConfig; Qv2rayAPIConfig apiConfig;
Qv2rayPluginConfig pluginConfig;
Qv2rayKernelConfig kernelConfig; Qv2rayKernelConfig kernelConfig;
Qv2rayUpdateConfig updateConfig; Qv2rayUpdateConfig updateConfig;
Qv2rayNetworkConfig networkConfig;
Qv2rayToolBarConfig toolBarConfig; Qv2rayToolBarConfig toolBarConfig;
Qv2rayInboundsConfig inboundConfig; Qv2rayInboundsConfig inboundConfig;
Qv2rayAdvancedConfig advancedConfig;
Qv2rayConnectionConfig connectionConfig; Qv2rayConnectionConfig connectionConfig;
Qv2rayConfig() Qv2rayConfig()
: config_version(QV2RAY_CONFIG_VERSION), tProxySupport(false), logLevel(), autoStartId("null"), groups(), subscriptions(), : config_version(QV2RAY_CONFIG_VERSION), //
connections(), uiConfig(), apiConfig(), kernelConfig(), updateConfig(), toolBarConfig(), inboundConfig(), connectionConfig() 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, XTOSTRUCT(O(config_version, //
autoStartId, inboundConfig, connectionConfig, toolBarConfig, apiConfig)) tProxySupport, //
logLevel, //
uiConfig, //
pluginConfig, //
updateConfig, //
kernelConfig, //
networkConfig, //
groups, //
connections, //
subscriptions, //
autoStartId, //
inboundConfig, //
connectionConfig, //
toolBarConfig, //
advancedConfig, //
apiConfig //
))
}; };
} // namespace Qv2ray::base::config } // namespace Qv2ray::base::config

View File

@ -16,6 +16,8 @@ namespace Qv2ray
bool enableToolbarPlguin; bool enableToolbarPlguin;
/// Disable Qt scale factors support. /// Disable Qt scale factors support.
bool noScaleFactors; bool noScaleFactors;
/// Disable all plugin features.
bool noPlugins;
}; };
} // namespace base } // namespace base
inline base::QvStartupOptions StartupOption = base::QvStartupOptions(); inline base::QvStartupOptions StartupOption = base::QvStartupOptions();

View File

@ -9,6 +9,7 @@ namespace Qv2ray::common
runAsRootOption("I-just-wanna-run-with-root", tr("Explicitly run Qv2ray as root.")), // runAsRootOption("I-just-wanna-run-with-root", tr("Explicitly run Qv2ray as root.")), //
debugOption("debug", tr("Enable Debug Output")), // debugOption("debug", tr("Enable Debug Output")), //
noScaleFactorOption("noScaleFactor", tr("Disable manually set QT_SCALE_FACTOR")), // noScaleFactorOption("noScaleFactor", tr("Disable manually set QT_SCALE_FACTOR")), //
noPluginsOption("noPlugin", tr("Disable plugin feature")), //
withToolbarOption("withToolbarPlugin", tr("Enable Qv2ray network toolbar plugin")), // withToolbarOption("withToolbarPlugin", tr("Enable Qv2ray network toolbar plugin")), //
// //
helpOption("FAKE"), versionOption("FAKE") helpOption("FAKE"), versionOption("FAKE")
@ -20,6 +21,7 @@ namespace Qv2ray::common
parser.addOption(runAsRootOption); parser.addOption(runAsRootOption);
parser.addOption(debugOption); parser.addOption(debugOption);
parser.addOption(noScaleFactorOption); parser.addOption(noScaleFactorOption);
parser.addOption(noPluginsOption);
parser.addOption(withToolbarOption); parser.addOption(withToolbarOption);
helpOption = parser.addHelpOption(); helpOption = parser.addHelpOption();
versionOption = parser.addVersionOption(); versionOption = parser.addVersionOption();
@ -63,6 +65,12 @@ namespace Qv2ray::common
StartupOption.noScaleFactors = true; StartupOption.noScaleFactors = true;
} }
if (parser.isSet(noPluginsOption))
{
DEBUG(MODULE_INIT, "noPluginOption is set.")
StartupOption.noPlugins = true;
}
if (parser.isSet(withToolbarOption)) if (parser.isSet(withToolbarOption))
{ {
DEBUG(MODULE_INIT, "withToolbarOption is set.") DEBUG(MODULE_INIT, "withToolbarOption is set.")

View File

@ -28,6 +28,7 @@ namespace Qv2ray::common
QCommandLineOption runAsRootOption; QCommandLineOption runAsRootOption;
QCommandLineOption debugOption; QCommandLineOption debugOption;
QCommandLineOption noScaleFactorOption; QCommandLineOption noScaleFactorOption;
QCommandLineOption noPluginsOption;
QCommandLineOption withToolbarOption; QCommandLineOption withToolbarOption;
QCommandLineOption helpOption; QCommandLineOption helpOption;
QCommandLineOption versionOption; QCommandLineOption versionOption;

View File

@ -16,112 +16,80 @@ namespace Qv2ray::common
accessManager.disconnect(); 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) void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value)
{ {
DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value) DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value)
request.setRawHeader(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) if (useProxy)
{ {
auto proxy = QNetworkProxyFactory::systemProxyForQuery(); auto p = GlobalConfig.networkConfig.useCustomProxy ?
accessManager.setProxy(proxy.first()); QNetworkProxy{
LOG(MODULE_NETWORK, "Sync get is using system proxy settings") GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, //
GlobalConfig.networkConfig.address, //
quint16(GlobalConfig.networkConfig.port) //
} :
QNetworkProxyFactory::systemProxyForQuery().first();
accessManager.setProxy(p);
} }
else else
{ {
DEBUG(MODULE_NETWORK, "Get without proxy.")
accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy)); 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::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); 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); reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p);
// //
QEventLoop loop; QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec(); loop.exec();
//
// Data or timeout? // Data or timeout?
auto data = reply->readAll(); auto data = reply->readAll();
return data; return data;
} }
void QvHttpRequestHelper::get(const QString &url) void QvHttpRequestHelper::AsyncGet(const QString &url)
{ {
this->setUrl(url); request.setUrl({ url });
// request.setRawHeader("Content-Type", if (GlobalConfig.networkConfig.useCustomProxy)
// "application/x-www-form-urlencoded"); {
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); reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p); 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() void QvHttpRequestHelper::onRequestFinished_p()
{ {
if (reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool()) if (reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool())
@ -134,15 +102,15 @@ namespace Qv2ray::common
QString error = QMetaEnum::fromType<QNetworkReply::NetworkError>().key(reply->error()); QString error = QMetaEnum::fromType<QNetworkReply::NetworkError>().key(reply->error());
LOG(MODULE_NETWORK, "Network request error string: " + error) LOG(MODULE_NETWORK, "Network request error string: " + error)
QByteArray empty; QByteArray empty;
emit httpRequestFinished(empty); emit OnRequestFinished(empty);
} }
else 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") DEBUG(MODULE_NETWORK, "A request is now ready read")
this->data += reply->readAll(); this->data += reply->readAll();

View File

@ -31,26 +31,18 @@ namespace Qv2ray::common
public: public:
explicit QvHttpRequestHelper(QObject *parent = nullptr); explicit QvHttpRequestHelper(QObject *parent = nullptr);
~QvHttpRequestHelper(); ~QvHttpRequestHelper();
bool setUrl(const QString &url);
void setHeader(const QByteArray &key, const QByteArray &value);
// get // get
QByteArray syncget(const QString &url, bool useProxy); void AsyncGet(const QString &url);
void get(const QString &url); QByteArray Get(const QString &url, bool useProxy);
//// 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);
signals: signals:
void httpRequestFinished(QByteArray &data); void OnRequestFinished(QByteArray &data);
private slots: private slots:
void onRequestFinished_p(); void onRequestFinished_p();
void onReadyRead(); void onReadyRead_p();
private: private:
void setHeader(const QByteArray &key, const QByteArray &value);
QByteArray data; QByteArray data;
QUrl url; QUrl url;
QNetworkReply *reply; QNetworkReply *reply;

View File

@ -7,14 +7,16 @@
#include "TextUtfEncoding.h" #include "TextUtfEncoding.h"
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
namespace Qv2ray::components namespace Qv2ray::common
{ {
using namespace ZXing; using namespace ZXing;
QString DecodeQRCode(const QImage &source) QString DecodeQRCode(const QImage &source)
{ {
if (source.isNull())
return "";
QImage img = source.copy(); QImage img = source.copy();
auto result = const auto result =
ReadBarcode(img.width(), img.height(), img.bits(), img.width() * 4, 4, 0, 1, 2, { BarcodeFormatFromString("") }, true, true); ReadBarcode(img.width(), img.height(), img.bits(), img.width() * 4, 4, 0, 1, 2, { ZXing::BarcodeFormat::QR_CODE }, true, true);
if (result.isValid()) if (result.isValid())
{ {
@ -34,17 +36,15 @@ namespace Qv2ray::components
int eccLevel = 1; int eccLevel = 1;
try try
{ {
auto barcodeFormat = BarcodeFormatFromString("QR_CODE"); MultiFormatWriter writer(ZXing::BarcodeFormat::QR_CODE);
MultiFormatWriter writer(barcodeFormat);
writer.setMargin(1); writer.setMargin(1);
writer.setEccLevel(eccLevel); writer.setEccLevel(eccLevel);
auto bitmap = writer.encode(content.toStdWString(), size.width(), size.height()); const auto bitmap = writer.encode(content.toStdWString(), size.width(), size.height());
auto BM = bitmap.toByteMatrix(); const auto BM = bitmap.toByteMatrix();
// //
const QRgb black = qRgba(0, 0, 0, 255); const auto black = qRgba(0, 0, 0, 255);
const QRgb white = qRgba(255, 255, 255, 255); const auto white = qRgba(255, 255, 255, 255);
// //
auto image = QImage(bitmap.width(), bitmap.width(), QImage::Format_ARGB32); auto image = QImage(bitmap.width(), bitmap.width(), QImage::Format_ARGB32);
image.fill(white); image.fill(white);
@ -67,4 +67,4 @@ namespace Qv2ray::components
return {}; return {};
} }
} }
} // namespace Qv2ray::components } // namespace Qv2ray::common

View File

@ -2,9 +2,9 @@
#include <QImage> #include <QImage>
#include <QString> #include <QString>
namespace Qv2ray::components namespace Qv2ray::common
{ {
QString DecodeQRCode(const QImage &img); QString DecodeQRCode(const QImage &img);
QImage EncodeQRCode(const QString &content, const QSize &size); QImage EncodeQRCode(const QString &content, const QSize &size);
} // namespace Qv2ray::components } // namespace Qv2ray::common
using namespace Qv2ray::components; using namespace Qv2ray::common;

View File

@ -17,33 +17,14 @@ using namespace Qv2ray::base;
QStringList getLanguageSearchPaths() QStringList getLanguageSearchPaths()
{ {
// Configuration Path // Configuration Path
QStringList list; QStringList list = Qv2rayAssetsPaths("lang");
list << QV2RAY_CONFIG_DIR + "lang";
//
#ifdef EMBED_TRANSLATIONS #ifdef EMBED_TRANSLATIONS
// If the translations have been embedded. // If the translations have been embedded.
list << QString(":/translations/"); list << QString(":/translations/");
#endif #endif
//
//
#ifdef QV2RAY_TRANSLATION_PATH #ifdef QV2RAY_TRANSLATION_PATH
// Platform-specific dir, if specified. // Platform-specific dir, if specified.
list << QString(QV2RAY_TRANSLATION_PATH); 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 #endif
return list; return list;
}; };

View File

@ -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

View File

@ -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<ushort>(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

View File

@ -1,41 +0,0 @@
#pragma once
#include <QObject>
#include <QThread>
#include <memory>
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;

View File

@ -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 <QPluginLoader>
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<Qv2rayInterface *>(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<Qv2rayInterface *>(_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<Qv2rayInterface *>(_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<QvPluginEditor *> QvPluginHost::GetOutboundEditorWidgets() const
{
QList<QvPluginEditor *> 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<QString, QPair<QString, QJsonObject>> QvPluginHost::TryDeserializeShareLink(const QString &sharelink, //
QString *prefix, //
QString *errMessage, //
QString *newGroupName, //
bool *status) const
{
Q_UNUSED(newGroupName)
QMultiHash<QString, QPair<QString, QJsonObject>> 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<QString, std::shared_ptr<QvPluginKernel>> QvPluginHost::GetPluginKernels() const
{
QMap<QString, std::shared_ptr<QvPluginKernel>> 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

View File

@ -0,0 +1,99 @@
#pragma once
#include "components/plugins/interface/QvPluginInterface.hpp"
#include <QMap>
#include <QObject>
#include <memory>
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<QWidget> 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<QString, std::shared_ptr<QvPluginKernel>> GetPluginKernels() const;
//
const QMultiHash<QString, QPair<QString, QJsonObject>> 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<QvPluginEditor *> 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<QString, QvPluginInfo> 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;

@ -0,0 +1 @@
Subproject commit d37c7ea9459956dc459610e98b821d4a790cb6e8

View File

@ -89,14 +89,14 @@ namespace Qv2ray::components::plugins
case 104: case 104:
{ {
// Current Connection Name // Current Connection Name
CL.Message = GetDisplayName(ConnectionManager->CurrentConnection()); CL.Message = GetDisplayName(KernelInstance->CurrentConnection());
break; break;
} }
case 105: case 105:
{ {
// Current Connection Status // Current Connection Status
CL.Message = ConnectionManager->CurrentConnection() == NullConnectionId ? QObject::tr("Not connected") : CL.Message = KernelInstance->CurrentConnection() == NullConnectionId ? QObject::tr("Not connected") :
QObject::tr("Connected"); QObject::tr("Connected");
break; break;
} }
@ -132,14 +132,14 @@ namespace Qv2ray::components::plugins
case 301: case 301:
{ {
// Total Upload // Total Upload
CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount(ConnectionManager->CurrentConnection()))); CL.Message = FormatBytes(get<0>(GetConnectionUsageAmount(KernelInstance->CurrentConnection())));
break; break;
} }
case 302: case 302:
{ {
// Total download // Total download
CL.Message = FormatBytes(get<1>(GetConnectionUsageAmount(ConnectionManager->CurrentConnection()))); CL.Message = FormatBytes(get<1>(GetConnectionUsageAmount(KernelInstance->CurrentConnection())));
break; break;
} }
@ -160,7 +160,7 @@ namespace Qv2ray::components::plugins
case 305: case 305:
{ {
// Connection latency // Connection latency
CL.Message = QSTRN(GetConnectionLatency(ConnectionManager->CurrentConnection())) + " ms"; CL.Message = QSTRN(GetConnectionLatency(KernelInstance->CurrentConnection())) + " ms";
break; break;
} }
default: default:

View File

@ -43,7 +43,7 @@ namespace Qv2ray::components::plugins::Toolbar
} }
catch (...) catch (...)
{ {
LOG(MODULE_PLUGIN, "Closing a broken socket.") LOG(MODULE_PLUGINHOST, "Closing a broken socket.")
} }
} }
void DataMessageQThread() void DataMessageQThread()
@ -66,8 +66,8 @@ namespace Qv2ray::components::plugins::Toolbar
while (!isExiting) while (!isExiting)
{ {
bool result = server->waitForNewConnection(5000, &timeOut); bool result = server->waitForNewConnection(5000, &timeOut);
DEBUG(MODULE_PLUGIN, "Plugin thread listening failed: " + server->errorString()) DEBUG(MODULE_PLUGINHOST, "Plugin thread listening failed: " + server->errorString())
DEBUG(MODULE_PLUGIN, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false")) DEBUG(MODULE_PLUGINHOST, "waitForNewConnection: " + QString(result ? "true" : "false") + ", " + QString(timeOut ? "true" : "false"))
} }
server->close(); server->close();
@ -85,7 +85,7 @@ namespace Qv2ray::components::plugins::Toolbar
if (linuxWorkerThread->isRunning()) if (linuxWorkerThread->isRunning())
{ {
LOG(MODULE_PLUGIN, "Waiting for linuxWorkerThread to stop.") LOG(MODULE_PLUGINHOST, "Waiting for linuxWorkerThread to stop.")
linuxWorkerThread->wait(); linuxWorkerThread->wait();
} }

View File

@ -28,7 +28,7 @@ namespace Qv2ray::components::plugins::Toolbar
if (hThread == nullptr) if (hThread == nullptr)
{ {
LOG(MODULE_PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError()))
return; return;
} }
else else
@ -52,7 +52,7 @@ namespace Qv2ray::components::plugins::Toolbar
if (hPipe == INVALID_HANDLE_VALUE) if (hPipe == INVALID_HANDLE_VALUE)
{ {
LOG(MODULE_PLUGIN, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "CreateNamedPipe failed, GLE=" + QSTRN(GetLastError()))
return static_cast<DWORD>(-1); return static_cast<DWORD>(-1);
} }
@ -60,12 +60,12 @@ namespace Qv2ray::components::plugins::Toolbar
if (fConnected) 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); ThreadHandle = CreateThread(nullptr, 0, InstanceThread, hPipe, 0, &dwThreadId);
if (ThreadHandle == nullptr) if (ThreadHandle == nullptr)
{ {
LOG(MODULE_PLUGIN, "CreateThread failed, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "CreateThread failed, GLE=" + QSTRN(GetLastError()))
return static_cast<DWORD>(-1); return static_cast<DWORD>(-1);
} }
else else
@ -93,11 +93,11 @@ namespace Qv2ray::components::plugins::Toolbar
{ {
if (GetLastError() == ERROR_BROKEN_PIPE) if (GetLastError() == ERROR_BROKEN_PIPE)
{ {
LOG(MODULE_PLUGIN, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "InstanceThread: client disconnected, GLE=" + QSTRN(GetLastError()))
} }
else else
{ {
LOG(MODULE_PLUGIN, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "InstanceThread ReadFile failed, GLE=" + QSTRN(GetLastError()))
} }
break; break;
@ -120,7 +120,7 @@ namespace Qv2ray::components::plugins::Toolbar
if (!fSuccess || cbReplyBytes != cbWritten) if (!fSuccess || cbReplyBytes != cbWritten)
{ {
LOG(MODULE_PLUGIN, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError())) LOG(MODULE_PLUGINHOST, "InstanceThread WriteFile failed, GLE=" + QSTRN(GetLastError()))
break; break;
} }
} }

View File

@ -1,6 +1,7 @@
#include "QvProxyConfigurator.hpp" #include "QvProxyConfigurator.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "components/plugins/QvPluginHost.hpp"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include <WinInet.h> #include <WinInet.h>
#include <Windows.h> #include <Windows.h>
@ -188,24 +189,18 @@ namespace Qv2ray::components::proxy
} }
#endif #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") LOG(MODULE_PROXY, "Setting up System Proxy")
bool hasHTTP = (httpPort != 0); bool hasHTTP = (httpPort != 0);
bool hasSOCKS = (socksPort != 0); bool hasSOCKS = (socksPort != 0);
if (!(hasHTTP || hasSOCKS || usePAC)) if (!(hasHTTP || hasSOCKS))
{ {
LOG(MODULE_PROXY, "Nothing?") LOG(MODULE_PROXY, "Nothing?")
return; return;
} }
if (usePAC)
{
LOG(MODULE_PROXY, "Qv2ray will set system proxy to use PAC file")
}
else
{
if (hasHTTP) if (hasHTTP)
{ {
LOG(MODULE_PROXY, "Qv2ray will set system proxy to use HTTP") LOG(MODULE_PROXY, "Qv2ray will set system proxy to use HTTP")
@ -215,19 +210,9 @@ namespace Qv2ray::components::proxy
{ {
LOG(MODULE_PROXY, "Qv2ray will set system proxy to use SOCKS") LOG(MODULE_PROXY, "Qv2ray will set system proxy to use SOCKS")
} }
}
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
QString __a; QString __a = (hasHTTP ? "http://" : "socks5://") + address + ":" + QSTRN(hasHTTP ? httpPort : socksPort);
if (usePAC)
{
__a = address;
}
else
{
__a = (hasHTTP ? "http://" : "socks5://") + address + ":" + QSTRN(httpPort);
}
LOG(MODULE_PROXY, "Windows proxy string: " + __a) LOG(MODULE_PROXY, "Windows proxy string: " + __a)
auto proxyStrW = new WCHAR[__a.length() + 1]; auto proxyStrW = new WCHAR[__a.length() + 1];
@ -235,7 +220,7 @@ namespace Qv2ray::components::proxy
// //
__QueryProxyOptions(); __QueryProxyOptions();
if (!__SetProxyOptions(proxyStrW, usePAC)) if (!__SetProxyOptions(proxyStrW, false))
{ {
LOG(MODULE_PROXY, "Failed to set proxy.") LOG(MODULE_PROXY, "Failed to set proxy.")
} }
@ -243,28 +228,13 @@ namespace Qv2ray::components::proxy
__QueryProxyOptions(); __QueryProxyOptions();
#elif defined(Q_OS_LINUX) #elif defined(Q_OS_LINUX)
QStringList actions; QStringList actions;
auto proxyMode = usePAC ? "auto" : "manual"; auto proxyMode = "manual";
actions << QString("gsettings set org.gnome.system.proxy mode '%1'").arg(proxyMode); actions << QString("gsettings set org.gnome.system.proxy mode '%1'").arg(proxyMode);
bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE"; bool isKDE = qEnvironmentVariable("XDG_SESSION_DESKTOP") == "KDE";
if (isKDE) if (isKDE)
{ {
LOG(MODULE_PROXY, "KDE detected") LOG(MODULE_PROXY, "KDE detected")
} }
//
if (usePAC)
{
actions << QString("gsettings set org.gnome.system.proxy autoconfig-url '%1'").arg(address);
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);
}
}
else
{
if (isKDE) if (isKDE)
{ {
actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + actions << QString("kwriteconfig5 --file " + QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) +
@ -304,7 +274,6 @@ namespace Qv2ray::components::proxy
.arg(QSTRN(socksPort)); .arg(QSTRN(socksPort));
} }
} }
}
// note: do not use std::all_of / any_of / none_of, // note: do not use std::all_of / any_of / none_of,
// because those are short-circuit and cannot guarantee atomicity. // because those are short-circuit and cannot guarantee atomicity.
@ -326,13 +295,6 @@ namespace Qv2ray::components::proxy
{ {
LOG(MODULE_PROXY, "Setting proxy for interface: " + service) LOG(MODULE_PROXY, "Setting proxy for interface: " + service)
if (usePAC)
{
QProcess::execute("/usr/sbin/networksetup -setautoproxystate " + service + " on");
QProcess::execute("/usr/sbin/networksetup -setautoproxyurl " + service + " " + address);
}
else
{
if (hasHTTP) if (hasHTTP)
{ {
QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " on"); QProcess::execute("/usr/sbin/networksetup -setwebproxystate " + service + " on");
@ -347,9 +309,17 @@ namespace Qv2ray::components::proxy
QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort)); QProcess::execute("/usr/sbin/networksetup -setsocksfirewallproxy " + service + " " + address + " " + QSTRN(socksPort));
} }
} }
}
#endif #endif
//
// Trigger plugin events
QMap<Events::SystemProxy::SystemProxyType, int> 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() void ClearSystemProxy()
@ -403,5 +373,9 @@ namespace Qv2ray::components::proxy
} }
#endif #endif
//
// Trigger plugin events
PluginHost->Send_SystemProxyEvent(
Events::SystemProxy::EventObject{ {}, Events::SystemProxy::SystemProxyStateType::SystemProxyState_ClearProxy });
} }
} // namespace Qv2ray::components::proxy } // namespace Qv2ray::components::proxy

View File

@ -5,7 +5,7 @@
namespace Qv2ray::components::proxy namespace Qv2ray::components::proxy
{ {
void ClearSystemProxy(); 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 } // namespace Qv2ray::components::proxy
using namespace Qv2ray::components; using namespace Qv2ray::components;

View File

@ -142,9 +142,8 @@ namespace Qv2ray::components::route::presets::v2rayN
const inline QList<QString> DomainsBlock{ "geosite:category-ads-all" }; const inline QList<QString> DomainsBlock{ "geosite:category-ads-all" };
const inline QList<QString> DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam", const inline QList<QString> DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam",
"geosite:telegram", "geosite:tumblr", "geosite:speedtest", "geosite:bbc", "geosite:telegram", "geosite:tumblr", "domain:naver.com", "geosite:bbc",
"domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org", "domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org" };
"domain:naver.com" };
const inline QList<QString> IPsProxy{ const inline QList<QString> 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", "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",

View File

@ -19,7 +19,7 @@ namespace Qv2ray::components
QvUpdateChecker::QvUpdateChecker(QObject *parent) : QObject(parent) QvUpdateChecker::QvUpdateChecker(QObject *parent) : QObject(parent)
{ {
requestHelper = new QvHttpRequestHelper(this); requestHelper = new QvHttpRequestHelper(this);
connect(requestHelper, &QvHttpRequestHelper::httpRequestFinished, this, &QvUpdateChecker::VersionUpdate); connect(requestHelper, &QvHttpRequestHelper::OnRequestFinished, this, &QvUpdateChecker::VersionUpdate);
} }
QvUpdateChecker::~QvUpdateChecker() QvUpdateChecker::~QvUpdateChecker()
{ {
@ -29,7 +29,7 @@ namespace Qv2ray::components
#ifndef DISABLE_AUTO_UPDATE #ifndef DISABLE_AUTO_UPDATE
auto updateChannel = GlobalConfig.updateConfig.updateChannel; auto updateChannel = GlobalConfig.updateConfig.updateChannel;
LOG(MODULE_NETWORK, "Start checking update for channel ID: " + QSTRN(updateChannel)) LOG(MODULE_NETWORK, "Start checking update for channel ID: " + QSTRN(updateChannel))
requestHelper->get(UpdateChannelLink[updateChannel]); requestHelper->AsyncGet(UpdateChannelLink[updateChannel]);
#endif #endif
} }
void QvUpdateChecker::VersionUpdate(QByteArray &data) void QvUpdateChecker::VersionUpdate(QByteArray &data)

View File

@ -56,7 +56,11 @@ namespace Qv2ray::core
} }
else 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; return ConnectionManager->GetConnectionMetaObject(id).groupId;
} }
const QMap<QString, int> GetConfigInboundPorts(const CONFIGROOT &root)
{
if (!root.contains("inbounds"))
{
return {};
}
QMap<QString, int> 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<QString, int> GetConfigInboundPorts(const ConnectionId &id)
{
return GetConfigInboundPorts(ConnectionManager->GetConnectionRoot(id));
}
} // namespace Qv2ray::core } // namespace Qv2ray::core

View File

@ -43,9 +43,11 @@ namespace Qv2ray::core
const QString GetDisplayName(const ConnectionId &id, int limit = -1); const QString GetDisplayName(const ConnectionId &id, int limit = -1);
const QString GetDisplayName(const GroupId &id, int limit = -1); const QString GetDisplayName(const GroupId &id, int limit = -1);
// //
const GroupId GetConnectionGroupId(const ConnectionId &id); const GroupId GetConnectionGroupId(const ConnectionId &id);
// //
const QMap<QString, int> GetConfigInboundPorts(const CONFIGROOT &root);
const QMap<QString, int> GetConfigInboundPorts(const ConnectionId &id);
//
} // namespace Qv2ray::core } // namespace Qv2ray::core
using namespace Qv2ray::core; using namespace Qv2ray::core;

View File

@ -308,7 +308,7 @@ namespace Qv2ray::core::connection
if (!root.contains("inbounds") || root.value("inbounds").toArray().empty()) if (!root.contains("inbounds") || root.value("inbounds").toArray().empty())
{ {
INBOUNDS inboundsList; INBOUNDS inboundsList;
QJsonObject sniffingObject{ { "enabled", false } };
// HTTP Inbound // HTTP Inbound
if (GlobalConfig.inboundConfig.useHTTP) if (GlobalConfig.inboundConfig.useHTTP)
{ {
@ -317,6 +317,7 @@ namespace Qv2ray::core::connection
httpInBoundObject.insert("port", GlobalConfig.inboundConfig.http_port); httpInBoundObject.insert("port", GlobalConfig.inboundConfig.http_port);
httpInBoundObject.insert("protocol", "http"); httpInBoundObject.insert("protocol", "http");
httpInBoundObject.insert("tag", "http_IN"); httpInBoundObject.insert("tag", "http_IN");
httpInBoundObject.insert("sniffing", sniffingObject);
if (GlobalConfig.inboundConfig.http_useAuth) if (GlobalConfig.inboundConfig.http_useAuth)
{ {
@ -335,6 +336,7 @@ namespace Qv2ray::core::connection
socksInBoundObject.insert("port", GlobalConfig.inboundConfig.socks_port); socksInBoundObject.insert("port", GlobalConfig.inboundConfig.socks_port);
socksInBoundObject.insert("protocol", "socks"); socksInBoundObject.insert("protocol", "socks");
socksInBoundObject.insert("tag", "socks_IN"); socksInBoundObject.insert("tag", "socks_IN");
socksInBoundObject.insert("sniffing", sniffingObject);
auto socksInSettings = GenerateSocksIN(GlobalConfig.inboundConfig.socks_useAuth ? "password" : "noauth", auto socksInSettings = GenerateSocksIN(GlobalConfig.inboundConfig.socks_useAuth ? "password" : "noauth",
QList<AccountObject>() << GlobalConfig.inboundConfig.socksAccount, QList<AccountObject>() << GlobalConfig.inboundConfig.socksAccount,
GlobalConfig.inboundConfig.socksUDP, GlobalConfig.inboundConfig.socksLocalIP); GlobalConfig.inboundConfig.socksUDP, GlobalConfig.inboundConfig.socksLocalIP);

View File

@ -2,6 +2,7 @@
#include "Generation.hpp" #include "Generation.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "components/plugins/QvPluginHost.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
@ -11,29 +12,61 @@ namespace Qv2ray::core::connection
{ {
QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName) QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *prefix, QString *errMessage, QString *newGroupName)
{ {
QMultiHash<QString, CONFIGROOT> config; QMultiHash<QString, CONFIGROOT> connectionConf;
if (link.startsWith("vmess://")) if (link.startsWith("vmess://"))
{ {
auto conf = ConvertConfigFromVMessString(link, prefix, errMessage); 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://")) else if (link.startsWith("ss://"))
{ {
auto conf = ConvertConfigFromSSString(link, prefix, errMessage); auto conf = ConvertConfigFromSSString(link, prefix, errMessage);
config.insert(*prefix, conf); connectionConf.insert(*prefix, conf);
} }
else if (link.startsWith("ssd://")) else if (link.startsWith("ssd://"))
{ {
QStringList errMessageList; QStringList errMessageList;
config = ConvertConfigFromSSDString(link, newGroupName, &errMessageList); connectionConf = ConvertConfigFromSSDString(link, newGroupName, &errMessageList);
*errMessage = errMessageList.join(NEWLINE); *errMessage = errMessageList.join(NEWLINE);
} }
else else
{
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."); *errMessage = QObject::tr("Unsupported share link format.");
} }
}
return config; return connectionConf;
} }
const QString ConvertConfigToString(const ConnectionId &id, bool isSip002) const QString ConvertConfigToString(const ConnectionId &id, bool isSip002)
@ -45,51 +78,51 @@ namespace Qv2ray::core::connection
return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER; return QV2RAY_SERIALIZATION_COMPLEX_CONFIG_PLACEHOLDER;
} }
auto server = ConnectionManager->GetConnectionRoot(id); 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()); const auto outbound = OUTBOUND(server["outbounds"].toArray().first().toObject());
auto type = outbound["protocol"].toString(); const auto type = outbound["protocol"].toString();
const auto &settings = outbound["settings"].toObject();
QString sharelink = ""; QString sharelink = "";
if (type == "vmess") if (type == "vmess")
{ {
auto vmessServer = auto vmessServer = StructFromJsonString<VMessServerObject>(JsonToString(settings["vnext"].toArray().first().toObject()));
StructFromJsonString<VMessServerObject>(JsonToString(outbound["settings"].toObject()["vnext"].toArray().first().toObject()));
auto transport = StructFromJsonString<StreamSettingsObject>(JsonToString(outbound["streamSettings"].toObject())); auto transport = StructFromJsonString<StreamSettingsObject>(JsonToString(outbound["streamSettings"].toObject()));
sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias); sharelink = vmess::ConvertConfigToVMessString(transport, vmessServer, alias);
} }
else if (type == "shadowsocks") else if (type == "shadowsocks")
{ {
auto ssServer = StructFromJsonString<ShadowSocksServerObject>( auto ssServer = StructFromJsonString<ShadowSocksServerObject>(JsonToString(settings["servers"].toArray().first().toObject()));
JsonToString(outbound["settings"].toObject()["servers"].toArray().first().toObject()));
sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002); sharelink = ss::ConvertConfigToSSString(ssServer, alias, isSip002);
} }
else else
{ {
if (!type.isEmpty()) if (type.isEmpty())
{ {
// DEBUG(MODULE_CONNECTION, "WARNING: Unsupported outbound type: " + type) DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.")
} }
else else
{ {
DEBUG(MODULE_CONNECTION, "WARNING: Empty outbound type.") bool ok = false;
sharelink = PluginHost->TrySerializeShareLink(type, settings, alias, groupName, &ok);
Q_UNUSED(ok)
} }
} }
return sharelink; return sharelink;
} }
QString DecodeSubscriptionString(QByteArray arr) QString DecodeSubscriptionString(const QByteArray &arr)
{ {
// String may start with: vmess:// and ss:// // String may start with: vmess:// and ss://
// We only process vmess:// here // We only process vmess:// here
// Some subscription providers may use plain vmess:// saperated by // Some subscription providers may use plain vmess:// saperated by
// lines But others may use base64 of above. // lines But others may use base64 of above.
auto result = QString::fromUtf8(arr).trimmed(); auto result = QString::fromUtf8(arr).trimmed();
return result.startsWith("vmess://") ? result : Base64Decode(result); return result.contains("://") ? result : Base64Decode(result);
} }
} // namespace Serialization } // namespace Serialization
} // namespace Qv2ray::core::connection } // namespace Qv2ray::core::connection

View File

@ -15,11 +15,11 @@ namespace Qv2ray::core::connection
inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)"); inline auto QV2RAY_SSD_DEFAULT_NAME_PATTERN = QObject::tr("%1 - %2 (rate %3)");
// //
// General // General
QString DecodeSubscriptionString(QByteArray arr); QString DecodeSubscriptionString(const QByteArray &arr);
QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage, QMultiHash<QString, CONFIGROOT> ConvertConfigFromString(const QString &link, QString *aliasPrefix, QString *errMessage,
QString *newGroupName = nullptr); QString *newGroupName = nullptr);
const QString ConvertConfigToString(const ConnectionId &id, bool isSip002 = false); 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 // VMess URI Protocol
namespace vmess namespace vmess

View File

@ -2,7 +2,6 @@
#include "Serialization.hpp" #include "Serialization.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/handler/ConfigHandler.hpp"
namespace Qv2ray::core::connection namespace Qv2ray::core::connection
{ {

View File

@ -83,7 +83,7 @@ namespace Qv2ray::core::connection::Serialization
} }
// decode base64 // 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()); const auto decodedJSON = QByteArray::fromBase64(ssdURIBody.toUtf8());
if (decodedJSON.length() == 0) if (decodedJSON.length() == 0)
@ -161,13 +161,12 @@ namespace Qv2ray::core::connection::Serialization
{ {
ssObject.port = port; 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; ssObject.port = currPort;
} }
else else
{ {
*logList << QObject::tr("Invalid port encountered. using fallback value.");
ssObject.port = port; ssObject.port = port;
} }
@ -186,7 +185,6 @@ namespace Qv2ray::core::connection::Serialization
} }
else else
{ {
*logList << QObject::tr("Invalid name encountered. using fallback value.");
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port); nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
} }
@ -201,7 +199,7 @@ namespace Qv2ray::core::connection::Serialization
} }
else if (!serverObject["ratio"].isUndefined()) 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. // format the total name of the node.
@ -209,10 +207,8 @@ namespace Qv2ray::core::connection::Serialization
// appending to the total list // appending to the total list
CONFIGROOT root; CONFIGROOT root;
OUTBOUNDS outbounds; OUTBOUNDS outbounds;
outbounds.append( outbounds.append(GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT({ ssObject }), {}));
GenerateOutboundEntry("shadowsocks", GenerateShadowSocksOUT(QList<ShadowSocksServerObject>{ ssObject }), QJsonObject()));
JADD(outbounds) JADD(outbounds)
serverList.insertMulti(totalName, root); serverList.insertMulti(totalName, root);
} }

View File

@ -1,6 +1,7 @@
#include "ConfigHandler.hpp" #include "ConfigHandler.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "components/plugins/QvPluginHost.hpp"
#include "core/connection/Serialization.hpp" #include "core/connection/Serialization.hpp"
#include "core/settings/SettingsBackend.hpp" #include "core/settings/SettingsBackend.hpp"
@ -12,7 +13,7 @@ namespace Qv2ray::core::handlers
DEBUG(MODULE_CORE_HANDLER, "ConnectionHandler Constructor.") DEBUG(MODULE_CORE_HANDLER, "ConnectionHandler Constructor.")
// Do we need to check how many of them are loaded? // 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++) for (auto i = 0; i < GlobalConfig.connections.count(); i++)
{ {
auto const &id = ConnectionId(GlobalConfig.connections.keys().at(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].displayName = tr("Default Group");
groups[DefaultGroupId].isSubscription = false; groups[DefaultGroupId].isSubscription = false;
// //
vCoreInstance = new V2rayKernelInstance(); kernelHandler = new KernelInstanceHandler(this);
connect(vCoreInstance, &V2rayKernelInstance::OnProcessErrored, this, &QvConfigHandler::OnVCoreCrashed); connect(kernelHandler, &KernelInstanceHandler::OnCrashed, this, &QvConfigHandler::OnKernelCrashed_p);
connect(vCoreInstance, &V2rayKernelInstance::OnNewStatsDataArrived, this, &QvConfigHandler::OnStatsDataArrived); connect(kernelHandler, &KernelInstanceHandler::OnStatsDataAvailable, this, &QvConfigHandler::OnStatsDataArrived_p);
// Directly connected to a signal. connect(kernelHandler, &KernelInstanceHandler::OnKernelLogAvailable, this, &QvConfigHandler::OnKernelLogAvailable);
connect(vCoreInstance, &V2rayKernelInstance::OnProcessOutputReadyRead, this, &QvConfigHandler::OnVCoreLogAvailable); connect(kernelHandler, &KernelInstanceHandler::OnConnected, this, &QvConfigHandler::OnConnected);
connect(kernelHandler, &KernelInstanceHandler::OnDisconnected, this, &QvConfigHandler::OnDisconnected);
// //
tcpingHelper = new QvTCPingHelper(5, this); tcpingHelper = new QvTCPingHelper(5, this);
httpHelper = new QvHttpRequestHelper(this); httpHelper = new QvHttpRequestHelper(this);
connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived); connect(tcpingHelper, &QvTCPingHelper::OnLatencyTestCompleted, this, &QvConfigHandler::OnLatencyDataArrived_p);
// //
// Save per 2 minutes. // Save per 1 minutes.
saveTimerId = startTimer(2 * 60 * 1000); saveTimerId = startTimer(1 * 60 * 1000);
// Do not ping all... // Do not ping all...
// pingAllTimerId = startTimer(5 * 60 * 1000);
pingConnectionTimerId = startTimer(60 * 1000); pingConnectionTimerId = startTimer(60 * 1000);
} }
@ -138,9 +139,10 @@ namespace Qv2ray::core::handlers
} }
else if (event->timerId() == pingConnectionTimerId) 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; return NullGroupId;
} }
void QvConfigHandler::ClearGroupUsage(const GroupId &id)
const optional<QString> QvConfigHandler::ClearConnectionUsage(const ConnectionId &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].upLinkData = 0;
connections[id].downLinkData = 0; connections[id].downLinkData = 0;
emit OnStatsAvailable(id, 0, 0, 0, 0); emit OnStatsAvailable(id, 0, 0, 0, 0);
return {}; PluginHost->Send_ConnectionStatsEvent({ GetDisplayName(id), 0, 0, 0, 0 });
return;
} }
const optional<QString> QvConfigHandler::RenameConnection(const ConnectionId &id, const QString &newName) const optional<QString> QvConfigHandler::RenameConnection(const ConnectionId &id, const QString &newName)
{ {
CheckConnectionExistance(id); CheckConnectionExistance(id);
OnConnectionRenamed(id, connections[id].displayName, newName); OnConnectionRenamed(id, connections[id].displayName, newName);
PluginHost->Send_ConnectionEvent({ newName, connections[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed });
connections[id].displayName = newName; connections[id].displayName = newName;
CHSaveConfigData_p(); CHSaveConfigData_p();
return {}; return {};
@ -232,6 +242,7 @@ namespace Qv2ray::core::handlers
QFile connectionFile((groups[groupId].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + groupId.toString() + "/" + QFile connectionFile((groups[groupId].isSubscription ? QV2RAY_SUBSCRIPTION_DIR : QV2RAY_CONNECTIONS_DIR) + groupId.toString() + "/" +
id.toString() + QV2RAY_CONFIG_FILE_EXTENSION); id.toString() + QV2RAY_CONFIG_FILE_EXTENSION);
// //
PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Deleted });
connections.remove(id); connections.remove(id);
groups[groupId].connections.removeAll(id); groups[groupId].connections.removeAll(id);
// //
@ -279,6 +290,8 @@ namespace Qv2ray::core::handlers
groups[newGroupId].connections.append(id); groups[newGroupId].connections.append(id);
connections[id].groupId = newGroupId; connections[id].groupId = newGroupId;
// //
PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Updated });
//
emit OnConnectionGroupChanged(id, oldgid, newGroupId); emit OnConnectionGroupChanged(id, oldgid, newGroupId);
// //
return {}; return {};
@ -308,6 +321,8 @@ namespace Qv2ray::core::handlers
QDir(QV2RAY_CONNECTIONS_DIR + id.toString()).removeRecursively(); QDir(QV2RAY_CONNECTIONS_DIR + id.toString()).removeRecursively();
} }
// //
PluginHost->Send_ConnectionEvent({ groups[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Deleted });
//
groups.remove(id); groups.remove(id);
CHSaveConfigData_p(); CHSaveConfigData_p();
emit OnGroupDeleted(id, list); emit OnGroupDeleted(id, list);
@ -321,55 +336,41 @@ namespace Qv2ray::core::handlers
const optional<QString> QvConfigHandler::StartConnection(const ConnectionId &id) const optional<QString> QvConfigHandler::StartConnection(const ConnectionId &id)
{ {
CheckConnectionExistance(id); CheckConnectionExistance(id);
connections[id].lastConnected = system_clock::to_time_t(system_clock::now());
if (currentConnectionId != NullConnectionId)
{
StopConnection();
}
CONFIGROOT root = GetConnectionRoot(id); CONFIGROOT root = GetConnectionRoot(id);
return CHStartConnection_p(id, root); return kernelHandler->StartConnection(id, root);
} }
void QvConfigHandler::RestartConnection() // const ConnectionId &id void QvConfigHandler::RestartConnection() // const ConnectionId &id
{ {
auto conn = currentConnectionId; kernelHandler->RestartConnection();
if (conn != NullConnectionId)
{
StopConnection();
StartConnection(conn);
}
} }
void QvConfigHandler::StopConnection() // const ConnectionId &id void QvConfigHandler::StopConnection() // const ConnectionId &id
{ {
// Currently just simply stop it. kernelHandler->StopConnection();
//_UNUSED(id)
// if (currentConnectionId == id) {
//}
CHStopConnection_p();
CHSaveConfigData_p(); CHSaveConfigData_p();
} }
bool QvConfigHandler::IsConnected(const ConnectionId &id) const bool QvConfigHandler::IsConnected(const ConnectionId &id) const
{ {
CheckConnectionExistanceEx(id, false); return kernelHandler->isConnected(id);
return currentConnectionId == 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() QvConfigHandler::~QvConfigHandler()
{ {
LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor") LOG(MODULE_CORE_HANDLER, "Triggering save settings from destructor")
CHSaveConfigData_p(); delete kernelHandler;
if (vCoreInstance->KernelStarted)
{
vCoreInstance->StopConnection();
LOG(MODULE_CORE_HANDLER, "Stopped connection from destructor.")
}
delete vCoreInstance;
delete httpHelper; delete httpHelper;
CHSaveConfigData_p();
} }
const CONFIGROOT QvConfigHandler::GetConnectionRoot(const ConnectionId &id) const const CONFIGROOT QvConfigHandler::GetConnectionRoot(const ConnectionId &id) const
@ -378,7 +379,7 @@ namespace Qv2ray::core::handlers
return connectionRootCache.value(id); return connectionRootCache.value(id);
} }
void QvConfigHandler::OnLatencyDataArrived(const QvTCPingResultObject &result) void QvConfigHandler::OnLatencyDataArrived_p(const QvTCPingResultObject &result)
{ {
CheckConnectionExistanceEx(result.connectionId, nothing); CheckConnectionExistanceEx(result.connectionId, nothing);
connections[result.connectionId].latency = result.avg; connections[result.connectionId].latency = result.avg;
@ -399,7 +400,8 @@ namespace Qv2ray::core::handlers
connectionRootCache[id] = root; connectionRootCache[id] = root;
// //
emit OnConnectionModified(id); emit OnConnectionModified(id);
if (!skipRestart && id == currentConnectionId) PluginHost->Send_ConnectionEvent({ connections[id].displayName, "", Events::ConnectionEntry::ConnectionEvent_Updated });
if (!skipRestart && kernelHandler->isConnected(id))
{ {
emit RestartConnection(); emit RestartConnection();
} }
@ -412,6 +414,7 @@ namespace Qv2ray::core::handlers
groups[id].displayName = displayName; groups[id].displayName = displayName;
groups[id].isSubscription = isSubscription; groups[id].isSubscription = isSubscription;
groups[id].importDate = system_clock::to_time_t(system_clock::now()); groups[id].importDate = system_clock::to_time_t(system_clock::now());
PluginHost->Send_ConnectionEvent({ displayName, "", Events::ConnectionEntry::ConnectionEvent_Created });
emit OnGroupCreated(id, displayName); emit OnGroupCreated(id, displayName);
CHSaveConfigData_p(); CHSaveConfigData_p();
return id; return id;
@ -425,6 +428,7 @@ namespace Qv2ray::core::handlers
return tr("Group does not exist"); return tr("Group does not exist");
} }
OnGroupRenamed(id, groups[id].displayName, newName); OnGroupRenamed(id, groups[id].displayName, newName);
PluginHost->Send_ConnectionEvent({ newName, groups[id].displayName, Events::ConnectionEntry::ConnectionEvent_Renamed });
groups[id].displayName = newName; groups[id].displayName = newName;
return {}; return {};
} }
@ -468,7 +472,7 @@ namespace Qv2ray::core::handlers
return false; return false;
} }
isHttpRequestInProgress = true; isHttpRequestInProgress = true;
auto data = httpHelper->syncget(groups[id].address, useSystemProxy); auto data = httpHelper->Get(groups[id].address, useSystemProxy);
isHttpRequestInProgress = false; isHttpRequestInProgress = false;
return CHUpdateSubscription_p(id, data); return CHUpdateSubscription_p(id, data);
} }
@ -583,7 +587,17 @@ namespace Qv2ray::core::handlers
return hasErrorOccured; 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) LOG(MODULE_CORE_HANDLER, "Creating new connection: " + displayName)
ConnectionId newId(GenerateUuid()); ConnectionId newId(GenerateUuid());
@ -592,8 +606,12 @@ namespace Qv2ray::core::handlers
connections[newId].importDate = system_clock::to_time_t(system_clock::now()); connections[newId].importDate = system_clock::to_time_t(system_clock::now());
connections[newId].displayName = displayName; connections[newId].displayName = displayName;
emit OnConnectionCreated(newId, displayName); emit OnConnectionCreated(newId, displayName);
PluginHost->Send_ConnectionEvent({ displayName, "", Events::ConnectionEntry::ConnectionEvent_Created });
UpdateConnection(newId, root); UpdateConnection(newId, root);
if (!skipSaveConfig)
{
CHSaveConfigData_p(); CHSaveConfigData_p();
}
return newId; return newId;
} }

View File

@ -6,7 +6,7 @@
#include "core/CoreSafeTypes.hpp" #include "core/CoreSafeTypes.hpp"
#include "core/CoreUtils.hpp" #include "core/CoreUtils.hpp"
#include "core/connection/ConnectionIO.hpp" #include "core/connection/ConnectionIO.hpp"
#include "core/kernel/KernelInteractions.hpp" #include "core/handler/KernelInstanceHandler.hpp"
#define CheckIdExistance(type, id, val) \ #define CheckIdExistance(type, id, val) \
if (!type.contains(id)) \ if (!type.contains(id)) \
@ -21,7 +21,6 @@
#define CheckConnectionExistance(id) CheckConnectionExistanceEx(id, tr("Connection does not exist")) #define CheckConnectionExistance(id) CheckConnectionExistanceEx(id, tr("Connection does not exist"))
namespace Qv2ray::core::handlers namespace Qv2ray::core::handlers
{ {
//
class QvConfigHandler : public QObject class QvConfigHandler : public QObject
{ {
Q_OBJECT Q_OBJECT
@ -30,11 +29,6 @@ namespace Qv2ray::core::handlers
~QvConfigHandler(); ~QvConfigHandler();
public slots: public slots:
//
inline const ConnectionId CurrentConnection() const
{
return currentConnectionId;
}
inline const QList<ConnectionId> Connections() const inline const QList<ConnectionId> Connections() const
{ {
return connections.keys(); return connections.keys();
@ -80,11 +74,13 @@ namespace Qv2ray::core::handlers
// //
// Connection Operations. // Connection Operations.
bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false); bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false);
const optional<QString> ClearConnectionUsage(const ConnectionId &id); void ClearGroupUsage(const GroupId &id);
void ClearConnectionUsage(const ConnectionId &id);
const optional<QString> DeleteConnection(const ConnectionId &id); const optional<QString> DeleteConnection(const ConnectionId &id);
const optional<QString> RenameConnection(const ConnectionId &id, const QString &newName); const optional<QString> RenameConnection(const ConnectionId &id, const QString &newName);
const optional<QString> MoveConnectionGroup(const ConnectionId &id, const GroupId &newGroupId); const optional<QString> 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 // Get Conncetion Property
const CONFIGROOT GetConnectionRoot(const ConnectionId &id) const; const CONFIGROOT GetConnectionRoot(const ConnectionId &id) const;
@ -107,10 +103,7 @@ namespace Qv2ray::core::handlers
const tuple<QString, int64_t, float> GetSubscriptionData(const GroupId &id) const; const tuple<QString, int64_t, float> GetSubscriptionData(const GroupId &id) const;
signals: signals:
void OnCrashed(); void OnKernelLogAvailable(const ConnectionId &id, const QString &log);
void OnConnected(const ConnectionId &id);
void OnDisconnected(const ConnectionId &id);
void OnVCoreLogAvailable(const ConnectionId &id, const QString &log);
void OnStatsAvailable(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD); 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); void OnConnectionCreated(const ConnectionId &id, const QString &displayName);
@ -127,20 +120,20 @@ namespace Qv2ray::core::handlers
void OnGroupDeleted(const GroupId &id, const QList<ConnectionId> &connections); void OnGroupDeleted(const GroupId &id, const QList<ConnectionId> &connections);
// //
void OnSubscriptionUpdateFinished(const GroupId &id); 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: private slots:
void OnStatsDataArrived(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed); void OnKernelCrashed_p(const ConnectionId &id, const QString &errMessage);
void OnVCoreCrashed(const ConnectionId &id); void OnLatencyDataArrived_p(const QvTCPingResultObject &data);
void OnLatencyDataArrived(const QvTCPingResultObject &data); void OnStatsDataArrived_p(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed);
protected: protected:
void timerEvent(QTimerEvent *event) override; void timerEvent(QTimerEvent *event) override;
private: private:
void CHSaveConfigData_p(); void CHSaveConfigData_p();
//
optional<QString> CHStartConnection_p(const ConnectionId &id, const CONFIGROOT &root);
void CHStopConnection_p();
bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData); bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData);
private: private:
@ -155,13 +148,7 @@ namespace Qv2ray::core::handlers
QvHttpRequestHelper *httpHelper; QvHttpRequestHelper *httpHelper;
bool isHttpRequestInProgress = false; bool isHttpRequestInProgress = false;
QvTCPingHelper *tcpingHelper; QvTCPingHelper *tcpingHelper;
// We only support one cuncurrent connection currently. KernelInstanceHandler *kernelHandler;
#ifdef QV2RAY_MULTIPlE_ONNECTION
QHash<ConnectionId, *V2rayKernelInstance> kernelInstances;
#else
ConnectionId currentConnectionId = NullConnectionId;
V2rayKernelInstance *vCoreInstance = nullptr;
#endif
}; };
inline ::Qv2ray::core::handlers::QvConfigHandler *ConnectionManager = nullptr; inline ::Qv2ray::core::handlers::QvConfigHandler *ConnectionManager = nullptr;

View File

@ -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<QString> 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<std::tuple<QString, int, QString>> 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<std::tuple<QString, QString, QString>> 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<QString, int> 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<QString, int> 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

View File

@ -0,0 +1,56 @@
#pragma once
#include "components/plugins/QvPluginHost.hpp"
#include "core/CoreSafeTypes.hpp"
#include "core/kernel/V2rayKernelInteractions.hpp"
#include <QObject>
#include <optional>
namespace Qv2ray::core::handlers
{
class KernelInstanceHandler : public QObject
{
Q_OBJECT
public:
explicit KernelInstanceHandler(QObject *parent = nullptr);
~KernelInstanceHandler();
std::optional<QString> 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<QString, int> 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<QString, std::shared_ptr<QvPluginKernel>> kernels;
QMap<QString, QvPluginKernel *> activeKernels;
QMap<QString, int> inboundPorts;
CONFIGROOT root;
V2rayKernelInstance *vCoreInstance = nullptr;
ConnectionId currentConnectionId = NullConnectionId;
ConnectionId lastConnectionId = NullConnectionId;
};
inline const KernelInstanceHandler *KernelInstance;
} // namespace Qv2ray::core::handlers

View File

@ -1,49 +0,0 @@
#include "ConfigHandler.hpp"
#include "core/connection/Generation.hpp"
optional<QString> 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();
}

View File

@ -91,7 +91,7 @@ namespace Qv2ray::core::kernel
qint64 value_up = 0; qint64 value_up = 0;
qint64 value_down = 0; qint64 value_down = 0;
for (auto tag : inboundTags) for (const auto &tag : inboundTags)
{ {
value_up += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink"); value_up += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink");
value_down += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink"); value_down += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink");
@ -105,7 +105,6 @@ namespace Qv2ray::core::kernel
if (running) if (running)
{ {
apiFailedCounter = 0;
emit OnDataReady(value_up, value_down); 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()) + ")") LOG(MODULE_VCORE, "API call returns: " + QSTRN(status.error_code()) + " (" + QString::fromStdString(status.error_message()) + ")")
apiFailedCounter++; apiFailedCounter++;
} }
else
{
apiFailedCounter = 0;
}
qint64 data = response.stat().value(); qint64 data = response.stat().value();
#else #else

View File

@ -0,0 +1 @@
#include "PluginKernelInteractions.hpp"

View File

@ -0,0 +1,6 @@
#pragma once
#include "components/plugins/QvPluginHost.hpp"
namespace Qv2ray::core::kernel
{
}

View File

@ -4,13 +4,14 @@
namespace Qv2ray::core::kernel::abi namespace Qv2ray::core::kernel::abi
{ {
[[nodiscard]] QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType) QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType)
{ {
switch (hostType) switch (hostType)
{ {
case ABI_WIN32: [[fallthrough]]; case ABI_WIN32:
case ABI_MACH_O: [[fallthrough]]; case ABI_MACH_O:
case ABI_ELF_AARCH64: [[fallthrough]]; case ABI_ELF_AARCH64:
case ABI_ELF_ARM:
case ABI_ELF_X86: return targetType == hostType ? ABI_PERFECT : ABI_NOPE; 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_X86_64: return targetType == hostType ? ABI_PERFECT : targetType == ABI_ELF_X86 ? ABI_MAYBE : ABI_NOPE;
case ABI_ELF_OTHER: return targetType == hostType ? ABI_PERFECT : ABI_MAYBE; case ABI_ELF_OTHER: return targetType == hostType ? ABI_PERFECT : ABI_MAYBE;
@ -18,7 +19,7 @@ namespace Qv2ray::core::kernel::abi
} }
} }
[[nodiscard]] std::pair<std::optional<QvKernelABIType>, std::optional<QString>> deduceKernelABI(const QString &pathCoreExecutable) std::pair<std::optional<QvKernelABIType>, std::optional<QString>> deduceKernelABI(const QString &pathCoreExecutable)
{ {
QFile file(pathCoreExecutable); QFile file(pathCoreExecutable);
if (!file.exists()) if (!file.exists())
@ -43,6 +44,8 @@ namespace Qv2ray::core::kernel::abi
return { QvKernelABIType::ABI_ELF_X86, std::nullopt }; return { QvKernelABIType::ABI_ELF_X86, std::nullopt };
else if (elfInstruction == 0xB700u) else if (elfInstruction == 0xB700u)
return { QvKernelABIType::ABI_ELF_AARCH64, std::nullopt }; return { QvKernelABIType::ABI_ELF_AARCH64, std::nullopt };
else if (elfInstruction == 0x2800u)
return { QvKernelABIType::ABI_ELF_ARM, std::nullopt };
else else
return { QvKernelABIType::ABI_ELF_OTHER, std::nullopt }; 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) }; 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) 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: return QObject::tr("ELF x86 executable");
case ABI_ELF_X86_64: return QObject::tr("ELF amd64 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_AARCH64: return QObject::tr("ELF arm64 executable");
case ABI_ELF_ARM: return QObject::tr("ELF arm executable");
case ABI_ELF_OTHER: return QObject::tr("other ELF executable"); case ABI_ELF_OTHER: return QObject::tr("other ELF executable");
default: return QObject::tr("unknown abi"); default: return QObject::tr("unknown abi");
} }

View File

@ -17,6 +17,7 @@ namespace Qv2ray::core::kernel
ABI_ELF_X86, ABI_ELF_X86,
ABI_ELF_X86_64, ABI_ELF_X86_64,
ABI_ELF_AARCH64, ABI_ELF_AARCH64,
ABI_ELF_ARM,
ABI_ELF_OTHER, ABI_ELF_OTHER,
}; };
@ -38,10 +39,14 @@ namespace Qv2ray::core::kernel
QvKernelABIType::ABI_WIN32; QvKernelABIType::ABI_WIN32;
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_64) #elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_64)
QvKernelABIType::ABI_ELF_AARCH64; QvKernelABIType::ABI_ELF_AARCH64;
#elif defined(Q_OS_LINUX) && defined(Q_PROCESSOR_ARM_V7)
QvKernelABIType::ABI_ELF_ARM;
#else
#error "unknown architecture"
#endif #endif
[[nodiscard]] std::pair<std::optional<QvKernelABIType>, std::optional<QString>> deduceKernelABI(const QString &pathCoreExecutable); std::pair<std::optional<QvKernelABIType>, std::optional<QString>> deduceKernelABI(const QString &pathCoreExecutable);
[[nodiscard]] QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType); QvKernelABICompatibility checkCompatibility(QvKernelABIType hostType, QvKernelABIType targetType);
[[nodiscard]] QString abiToString(QvKernelABIType abi); QString abiToString(QvKernelABIType abi);
} // namespace abi } // namespace abi
} // namespace Qv2ray::core::kernel } // namespace Qv2ray::core::kernel

View File

@ -1,4 +1,4 @@
#include "KernelInteractions.hpp" #include "V2rayKernelInteractions.hpp"
#include "APIBackend.hpp" #include "APIBackend.hpp"
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
@ -31,33 +31,42 @@ namespace Qv2ray::core::kernel
} }
coreFile.close(); coreFile.close();
// Get Core ABI. // Get Core ABI.
auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath); auto [abi, err] = kernel::abi::deduceKernelABI(vCorePath);
if (err) if (err)
{ {
LOG(MODULE_VCORE, "Core ABI deduction failed: " + err.value()) LOG(MODULE_VCORE, "Core ABI deduction failed: " + ACCESS_OPTIONAL_VALUE(err))
*message = err.value(); *message = ACCESS_OPTIONAL_VALUE(err);
return false; 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 // Get Compiled ABI
auto compiledABI = kernel::abi::COMPILED_ABI_TYPE; auto compiledABI = kernel::abi::COMPILED_ABI_TYPE;
LOG(MODULE_VCORE, "Host ABI: " + kernel::abi::abiToString(compiledABI)) LOG(MODULE_VCORE, "Host ABI: " + kernel::abi::abiToString(compiledABI))
// Check ABI Compatibility. // Check ABI Compatibility.
switch (kernel::abi::checkCompatibility(compiledABI, abi.value())) switch (kernel::abi::checkCompatibility(compiledABI, ACCESS_OPTIONAL_VALUE(abi)))
{ {
case kernel::abi::ABI_NOPE: case kernel::abi::ABI_NOPE:
{
LOG(MODULE_VCORE, "Host is incompatible with core") LOG(MODULE_VCORE, "Host is incompatible with core")
*message = tr("V2Ray core is incompatible with your platform.\r\n" // *message = tr("V2Ray core is incompatible with your platform.\r\n" //
"Expected core ABI is %1, but got actual %2.\r\n" // "Expected core ABI is %1, but got actual %2.\r\n" //
"Maybe you have downloaded the wrong core?") "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; 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 else
{ {
QvMessageBoxWarn(nullptr, tr("Cannot start V2ray"), QvMessageBoxWarn(nullptr, tr("Cannot start V2ray"), //
tr("V2ray core settings is incorrect.") + NEWLINE + NEWLINE + tr("The error is: ") + NEWLINE + v2rayCheckResult); tr("V2ray core settings is incorrect.") + NEWLINE + NEWLINE + //
tr("The error is: ") + NEWLINE + v2rayCheckResult);
return false; return false;
} }
} }
V2rayKernelInstance::V2rayKernelInstance() V2rayKernelInstance::V2rayKernelInstance(QObject *parent) : QObject(parent)
{ {
vProcess = new QProcess(); vProcess = new QProcess();
connect(vProcess, &QProcess::readyReadStandardOutput, this, connect(vProcess, &QProcess::readyReadStandardOutput, this,
[&]() { emit OnProcessOutputReadyRead(id, vProcess->readAllStandardOutput().trimmed()); }); [&]() { emit OnProcessOutputReadyRead(vProcess->readAllStandardOutput().trimmed()); });
connect(vProcess, &QProcess::stateChanged, [&](QProcess::ProcessState state) { connect(vProcess, &QProcess::stateChanged, [&](QProcess::ProcessState state) {
DEBUG(MODULE_VCORE, "V2ray kernel process status changed: " + QVariant::fromValue(state).toString()) 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.") LOG(MODULE_VCORE, "V2ray kernel crashed.")
StopConnection(); StopConnection();
emit OnProcessErrored(id); emit OnProcessErrored("V2ray kernel crashed.");
} }
}); });
apiWorker = new APIWorker(); apiWorker = new APIWorker();
@ -183,7 +193,7 @@ namespace Qv2ray::core::kernel
KernelStarted = false; KernelStarted = false;
} }
optional<QString> V2rayKernelInstance::StartConnection(const ConnectionId &id, const CONFIGROOT &root) optional<QString> V2rayKernelInstance::StartConnection(const CONFIGROOT &root)
{ {
if (KernelStarted) if (KernelStarted)
{ {
@ -206,8 +216,6 @@ namespace Qv2ray::core::kernel
vProcess->waitForStarted(); vProcess->waitForStarted();
DEBUG(MODULE_VCORE, "V2ray core started.") DEBUG(MODULE_VCORE, "V2ray core started.")
KernelStarted = true; KernelStarted = true;
// Set Connection ID
this->id = id;
QStringList inboundTags; QStringList inboundTags;
for (auto item : root["inbounds"].toArray()) for (auto item : root["inbounds"].toArray())
@ -285,6 +293,6 @@ namespace Qv2ray::core::kernel
void V2rayKernelInstance::onAPIDataReady(const quint64 speedUp, const quint64 speedDown) void V2rayKernelInstance::onAPIDataReady(const quint64 speedUp, const quint64 speedDown)
{ {
emit OnNewStatsDataArrived(id, speedUp, speedDown); emit OnNewStatsDataArrived(speedUp, speedDown);
} }
} // namespace Qv2ray::core::kernel } // namespace Qv2ray::core::kernel

View File

@ -12,7 +12,7 @@ namespace Qv2ray::core::kernel
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit V2rayKernelInstance(); explicit V2rayKernelInstance(QObject *parent = nullptr);
~V2rayKernelInstance() override; ~V2rayKernelInstance() override;
// //
// Speed // Speed
@ -21,7 +21,7 @@ namespace Qv2ray::core::kernel
qulonglong getAllSpeedUp(); qulonglong getAllSpeedUp();
qulonglong getAllSpeedDown(); qulonglong getAllSpeedDown();
// //
optional<QString> StartConnection(const ConnectionId &id, const CONFIGROOT &root); optional<QString> StartConnection(const CONFIGROOT &root);
void StopConnection(); void StopConnection();
bool KernelStarted = false; bool KernelStarted = false;
// //
@ -29,19 +29,17 @@ namespace Qv2ray::core::kernel
static bool ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message); static bool ValidateKernel(const QString &vCorePath, const QString &vAssetsPath, QString *message);
signals: signals:
void OnProcessErrored(const ConnectionId &id); void OnProcessErrored(const QString &errMessage);
void OnProcessOutputReadyRead(const ConnectionId &id, const QString &output); void OnProcessOutputReadyRead(const QString &output);
void OnNewStatsDataArrived(const ConnectionId &id, const quint64 speedUp, const quint64 speedDown); void OnNewStatsDataArrived(const quint64 speedUp, const quint64 speedDown);
public slots: private slots:
void onAPIDataReady(const quint64 speedUp, const quint64 speedDown); void onAPIDataReady(const quint64 speedUp, const quint64 speedDown);
private: private:
APIWorker *apiWorker; APIWorker *apiWorker;
QProcess *vProcess; QProcess *vProcess;
bool apiEnabled; bool apiEnabled;
//
ConnectionId id = NullConnectionId;
}; };
} // namespace Qv2ray::core::kernel } // namespace Qv2ray::core::kernel

View File

@ -4,6 +4,7 @@
#include "common/QvTranslator.hpp" #include "common/QvTranslator.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
#include "core/settings/SettingsBackend.hpp" #include "core/settings/SettingsBackend.hpp"
#include "src/components/plugins/QvPluginHost.hpp"
#include "ui/w_MainWindow.hpp" #include "ui/w_MainWindow.hpp"
#include <QApplication> #include <QApplication>
@ -139,7 +140,7 @@ bool initialiseQv2ray()
Qv2rayConfig conf; Qv2rayConfig conf;
conf.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH)); conf.kernelConfig.KernelPath(QString(QV2RAY_DEFAULT_VCORE_PATH));
conf.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH)); conf.kernelConfig.AssetsPath(QString(QV2RAY_DEFAULT_VASSETS_PATH));
conf.logLevel = 2; conf.logLevel = 3;
conf.uiConfig.language = QLocale::system().name(); conf.uiConfig.language = QLocale::system().name();
// //
// Save initial config. // Save initial config.
@ -229,7 +230,7 @@ int main(int argc, char *argv[])
#endif #endif
if (StartupOption.noScaleFactors) 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")) LOG(MODULE_UI, "Original QT_SCALE_FACTOR was: " + qEnvironmentVariable("QT_SCALE_FACTOR"))
qputenv("QT_SCALE_FACTOR", "1"); qputenv("QT_SCALE_FACTOR", "1");
} }
@ -237,6 +238,9 @@ int main(int argc, char *argv[])
{ {
LOG(MODULE_INIT, "High DPI scaling is enabled.") LOG(MODULE_INIT, "High DPI scaling is enabled.")
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 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 _qApp(argc, argv, false,
SingleApplication::User | SingleApplication::ExcludeAppPath | SingleApplication::ExcludeAppVersion); SingleApplication::User | SingleApplication::ExcludeAppPath | SingleApplication::ExcludeAppVersion);
@ -404,11 +408,8 @@ int main(int argc, char *argv[])
#endif #endif
//_qApp.setAttribute(Qt::AA_DontUseNativeMenuBar); //_qApp.setAttribute(Qt::AA_DontUseNativeMenuBar);
// Initialise Connection Handler // Initialise Connection Handler
PluginHost = new QvPluginHost();
ConnectionManager = new QvConfigHandler(); 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 // Show MainWindow
MainWindow w; MainWindow w;
QObject::connect(&_qApp, &SingleApplication::instanceStarted, [&]() { QObject::connect(&_qApp, &SingleApplication::instanceStarted, [&]() {
@ -423,6 +424,7 @@ int main(int argc, char *argv[])
#endif #endif
auto rcode = _qApp.exec(); auto rcode = _qApp.exec();
delete ConnectionManager; delete ConnectionManager;
delete PluginHost;
LOG(MODULE_INIT, "Quitting normally") LOG(MODULE_INIT, "Quitting normally")
return rcode; return rcode;
#ifndef QT_DEBUG #ifndef QT_DEBUG

View File

@ -9,78 +9,53 @@
#include <QIntValidator> #include <QIntValidator>
#include <iostream> #include <iostream>
OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), Tag(""), Mux(), vmess(), shadowsocks() OutboundEditor::OutboundEditor(QWidget *parent) : QDialog(parent), tag(OUTBOUND_TAG_PROXY)
{ {
QvMessageBusConnect(OutboundEditor); QvMessageBusConnect(OutboundEditor);
setupUi(this); setupUi(this);
// //
ssWidget = new StreamSettingsWidget(this); outboundType = "vmess";
transportFrame->addWidget(ssWidget);
// //
shadowsocks = ShadowSocksServerObject(); streamSettingsWidget = new StreamSettingsWidget(this);
socks = SocksServerObject(); streamSettingsWidget->SetStreamObject({});
vmess = VMessServerObject(); transportFrame->addWidget(streamSettingsWidget);
socks.users.push_back(SocksServerObject::UserObject());
vmess.users.push_back(VMessServerObject::UserObject());
// //
auto stream = StreamSettingsObject(); socks.users.push_back({});
ssWidget->SetStreamObject(stream); vmess.users.push_back({});
// //
OutboundType = "vmess"; auto pluginEditorWidgetsInfo = PluginHost->GetOutboundEditorWidgets();
Tag = OUTBOUND_TAG_PROXY; for (const auto &plugin : pluginEditorWidgetsInfo)
useFProxy = false; {
ReloadGUI(); for (const auto &_d : plugin->OutboundCapabilities())
Result = GenerateConnectionJson(); {
outBoundTypeCombo->addItem(_d.displayName, _d.protocol);
auto index = outboundTypeStackView->addWidget(plugin);
pluginWidgets.insert(index, { _d, plugin });
}
}
//
outboundType = "vmess";
useForwardProxy = false;
} }
QvMessageBusSlotImpl(OutboundEditor) QvMessageBusSlotImpl(OutboundEditor)
{ {
switch (msg) 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; originalConfig = 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<StreamSettingsObject>(JsonToString(outboundEntry["streamSettings"].toObject())));
if (OutboundType == "vmess")
{
vmess =
StructFromJsonString<VMessServerObject>(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<ShadowSocksServerObject>(
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<SocksServerObject>(JsonToString(outboundEntry["settings"].toObject()["servers"].toArray().first().toObject()));
vmess.address = socks.address;
vmess.port = socks.port;
shadowsocks.address = socks.address;
shadowsocks.port = socks.port;
}
ReloadGUI(); ReloadGUI();
Result = GenerateConnectionJson();
} }
OutboundEditor::~OutboundEditor() OutboundEditor::~OutboundEditor()
@ -90,124 +65,187 @@ OutboundEditor::~OutboundEditor()
OUTBOUND OutboundEditor::OpenEditor() OUTBOUND OutboundEditor::OpenEditor()
{ {
int resultCode = this->exec(); int resultCode = this->exec();
return resultCode == QDialog::Accepted ? Result : Original; return resultCode == QDialog::Accepted ? resultConfig : originalConfig;
} }
QString OutboundEditor::GetFriendlyName() QString OutboundEditor::GetFriendlyName()
{ {
auto host = ipLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_"); auto host = ipLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_");
auto port = portLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_"); auto port = portLineEdit->text().replace(":", "-").replace("/", "_").replace("\\", "_");
auto type = OutboundType; auto type = outboundType;
QString name = Tag.isEmpty() ? host + "-[" + port + "]-" + type : Tag; QString name = tag.isEmpty() ? host + "-[" + port + "]-" + type : tag;
return name; return name;
} }
OUTBOUND OutboundEditor::GenerateConnectionJson() OUTBOUND OutboundEditor::GenerateConnectionJson()
{ {
OUTBOUNDSETTING settings; 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": [] } // VMess is only a ServerObject, and we need an array { "vnext": [] }
QJsonArray vnext; QJsonArray vnext;
vmess.address = address;
vmess.port = port;
vnext.append(GetRootObject(vmess)); vnext.append(GetRootObject(vmess));
settings.insert("vnext", vnext); settings.insert("vnext", vnext);
} }
else if (OutboundType == "shadowsocks") else if (outboundType == "shadowsocks")
{ {
streaming = QJsonObject(); streaming = QJsonObject();
LOG(MODULE_CONNECTION, "Shadowsocks outbound does not need StreamSettings.") LOG(MODULE_CONNECTION, "Shadowsocks outbound does not need StreamSettings.")
QJsonArray servers; QJsonArray servers;
shadowsocks.address = address;
shadowsocks.port = port;
servers.append(GetRootObject(shadowsocks)); servers.append(GetRootObject(shadowsocks));
settings["servers"] = servers; 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()) if (!socks.users.isEmpty() && socks.users.first().user.isEmpty() && socks.users.first().pass.isEmpty())
{ {
LOG(MODULE_UI, "Removed empty user form SOCKS settings") LOG(MODULE_UI, "Removed empty user form SOCKS settings")
socks.users.clear(); socks.users.clear();
} }
socks.address = address;
socks.port = port;
streaming = QJsonObject(); streaming = QJsonObject();
LOG(MODULE_CONNECTION, "Socks outbound does not need StreamSettings.") LOG(MODULE_CONNECTION, "Socks outbound does not need StreamSettings.")
QJsonArray servers; QJsonArray servers;
servers.append(GetRootObject(socks)); servers.append(GetRootObject(socks));
settings["servers"] = servers; 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); auto root = GenerateOutboundEntry(outboundType, settings, streaming, muxConfig, "0.0.0.0", tag);
root[QV2RAY_USE_FPROXY_KEY] = useFProxy; root[QV2RAY_USE_FPROXY_KEY] = useForwardProxy;
return root; return root;
} }
void OutboundEditor::ReloadGUI() 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<StreamSettingsObject>(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); outBoundTypeCombo->setCurrentIndex(0);
ipLineEdit->setText(vmess.address); vmess = StructFromJsonString<VMessServerObject>(JsonToString(settings["vnext"].toArray().first().toObject()));
portLineEdit->setText(QSTRN(vmess.port)); if (vmess.users.empty())
{
vmess.users.push_back({});
}
address = vmess.address;
port = vmess.port;
idLineEdit->setText(vmess.users.front().id); idLineEdit->setText(vmess.users.front().id);
alterLineEdit->setValue(vmess.users.front().alterId); alterLineEdit->setValue(vmess.users.front().alterId);
securityCombo->setCurrentText(vmess.users.front().security); securityCombo->setCurrentText(vmess.users.front().security);
} }
else if (OutboundType == "shadowsocks") else if (outboundType == "shadowsocks")
{ {
outBoundTypeCombo->setCurrentIndex(1); outBoundTypeCombo->setCurrentIndex(1);
shadowsocks = StructFromJsonString<ShadowSocksServerObject>(JsonToString(settings["servers"].toArray().first().toObject()));
address = shadowsocks.address;
port = shadowsocks.port;
// ShadowSocks Configs // ShadowSocks Configs
ipLineEdit->setText(shadowsocks.address);
portLineEdit->setText(QSTRN(shadowsocks.port));
ss_emailTxt->setText(shadowsocks.email); ss_emailTxt->setText(shadowsocks.email);
ss_levelSpin->setValue(shadowsocks.level); ss_levelSpin->setValue(shadowsocks.level);
ss_otaCheckBox->setChecked(shadowsocks.ota); ss_otaCheckBox->setChecked(shadowsocks.ota);
ss_passwordTxt->setText(shadowsocks.password); ss_passwordTxt->setText(shadowsocks.password);
ss_encryptionMethod->setCurrentText(shadowsocks.method); ss_encryptionMethod->setCurrentText(shadowsocks.method);
} }
else if (OutboundType == "socks") else if (outboundType == "socks")
{ {
outBoundTypeCombo->setCurrentIndex(2); outBoundTypeCombo->setCurrentIndex(2);
ipLineEdit->setText(socks.address); socks = StructFromJsonString<SocksServerObject>(JsonToString(settings["servers"].toArray().first().toObject()));
portLineEdit->setText(QSTRN(socks.port)); address = socks.address;
port = socks.port;
if (socks.users.empty()) if (socks.users.empty())
socks.users.push_back(SocksServerObject::UserObject()); {
socks.users.push_back({});
}
socks_PasswordTxt->setText(socks.users.front().pass); socks_PasswordTxt->setText(socks.users.front().pass);
socks_UserNameTxt->setText(socks.users.front().user); socks_UserNameTxt->setText(socks.users.front().user);
} }
else
useFPCB->setChecked(useFProxy); {
muxEnabledCB->setChecked(Mux["enabled"].toBool()); bool processed = false;
muxConcurrencyTxt->setValue(Mux["concurrency"].toInt()); 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() void OutboundEditor::on_buttonBox_accepted()
{ {
Result = GenerateConnectionJson(); resultConfig = GenerateConnectionJson();
} }
void OutboundEditor::on_ipLineEdit_textEdited(const QString &arg1) void OutboundEditor::on_ipLineEdit_textEdited(const QString &arg1)
{ {
vmess.address = arg1; address = arg1;
shadowsocks.address = arg1;
socks.address = arg1;
} }
void OutboundEditor::on_portLineEdit_textEdited(const QString &arg1) void OutboundEditor::on_portLineEdit_textEdited(const QString &arg1)
{ {
if (arg1 != "") port = arg1.toInt();
{
vmess.port = arg1.toInt();
shadowsocks.port = arg1.toInt();
socks.port = arg1.toInt();
}
} }
void OutboundEditor::on_idLineEdit_textEdited(const QString &arg1) void OutboundEditor::on_idLineEdit_textEdited(const QString &arg1)
{ {
if (vmess.users.empty()) if (vmess.users.empty())
vmess.users.push_back(VMessServerObject::UserObject()); vmess.users.push_back({});
vmess.users.front().id = arg1; 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) void OutboundEditor::on_securityCombo_currentIndexChanged(const QString &arg1)
{ {
if (vmess.users.empty()) if (vmess.users.empty())
vmess.users.push_back(VMessServerObject::UserObject()); vmess.users.push_back({});
vmess.users.front().security = arg1; vmess.users.front().security = arg1;
} }
void OutboundEditor::on_tagTxt_textEdited(const QString &arg1) void OutboundEditor::on_tagTxt_textEdited(const QString &arg1)
{ {
Tag = arg1; tag = arg1;
} }
void OutboundEditor::on_muxEnabledCB_stateChanged(int 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) void OutboundEditor::on_muxConcurrencyTxt_valueChanged(int arg1)
{ {
Mux["concurrency"] = arg1; muxConfig["concurrency"] = arg1;
} }
void OutboundEditor::on_alterLineEdit_valueChanged(int arg1) void OutboundEditor::on_alterLineEdit_valueChanged(int arg1)
{ {
if (vmess.users.empty()) if (vmess.users.empty())
vmess.users.push_back(VMessServerObject::UserObject()); vmess.users.push_back({});
vmess.users.front().alterId = arg1; vmess.users.front().alterId = arg1;
} }
void OutboundEditor::on_useFPCB_stateChanged(int arg1) void OutboundEditor::on_useFPCB_stateChanged(int arg1)
{ {
useFProxy = arg1 == Qt::Checked; useForwardProxy = arg1 == Qt::Checked;
} }
void OutboundEditor::on_outBoundTypeCombo_currentIndexChanged(int index) void OutboundEditor::on_outBoundTypeCombo_currentIndexChanged(int index)
{ {
// 0, 1, 2 as built-in vmess, ss, socks
outboundTypeStackView->setCurrentIndex(index); 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) 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) void OutboundEditor::on_socks_UserNameTxt_textEdited(const QString &arg1)
{ {
if (socks.users.isEmpty()) if (socks.users.isEmpty())
socks.users.push_back(SocksServerObject::UserObject()); socks.users.push_back({});
socks.users.front().user = arg1; socks.users.front().user = arg1;
} }
void OutboundEditor::on_socks_PasswordTxt_textEdited(const QString &arg1) void OutboundEditor::on_socks_PasswordTxt_textEdited(const QString &arg1)
{ {
if (socks.users.isEmpty()) if (socks.users.isEmpty())
socks.users.push_back(SocksServerObject::UserObject()); socks.users.push_back({});
socks.users.front().pass = arg1; socks.users.front().pass = arg1;
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include "base/Qv2rayBase.hpp" #include "base/Qv2rayBase.hpp"
#include "components/plugins/QvPluginHost.hpp"
#include "ui/messaging/QvMessageBus.hpp" #include "ui/messaging/QvMessageBus.hpp"
#include "ui/widgets/StreamSettingsWidget.hpp" #include "ui/widgets/StreamSettingsWidget.hpp"
#include "ui_w_OutboundEditor.h" #include "ui_w_OutboundEditor.h"
@ -13,68 +14,55 @@ class OutboundEditor
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit OutboundEditor(QWidget *parent = nullptr); explicit OutboundEditor(const OUTBOUND &outboundEntry, QWidget *parent = nullptr);
explicit OutboundEditor(OUTBOUND outboundEntry, QWidget *parent = nullptr);
~OutboundEditor(); ~OutboundEditor();
OUTBOUND OpenEditor(); OUTBOUND OpenEditor();
QString GetFriendlyName(); QString GetFriendlyName();
private: private:
explicit OutboundEditor(QWidget *parent = nullptr);
QvMessageBusSlotDecl; QvMessageBusSlotDecl;
signals: signals:
void s_reload_config(bool need_restart); void s_reload_config(bool need_restart);
private slots: private slots:
void on_buttonBox_accepted(); void on_buttonBox_accepted();
void on_ipLineEdit_textEdited(const QString &arg1); void on_ipLineEdit_textEdited(const QString &arg1);
void on_portLineEdit_textEdited(const QString &arg1); void on_portLineEdit_textEdited(const QString &arg1);
void on_idLineEdit_textEdited(const QString &arg1); void on_idLineEdit_textEdited(const QString &arg1);
void on_tagTxt_textEdited(const QString &arg1); void on_tagTxt_textEdited(const QString &arg1);
void on_muxEnabledCB_stateChanged(int arg1); void on_muxEnabledCB_stateChanged(int arg1);
void on_muxConcurrencyTxt_valueChanged(int arg1); void on_muxConcurrencyTxt_valueChanged(int arg1);
void on_alterLineEdit_valueChanged(int arg1); void on_alterLineEdit_valueChanged(int arg1);
void on_useFPCB_stateChanged(int arg1); void on_useFPCB_stateChanged(int arg1);
void on_outBoundTypeCombo_currentIndexChanged(int index); void on_outBoundTypeCombo_currentIndexChanged(int index);
void on_ss_emailTxt_textEdited(const QString &arg1); void on_ss_emailTxt_textEdited(const QString &arg1);
void on_ss_passwordTxt_textEdited(const QString &arg1); void on_ss_passwordTxt_textEdited(const QString &arg1);
void on_ss_encryptionMethod_currentIndexChanged(const QString &arg1); void on_ss_encryptionMethod_currentIndexChanged(const QString &arg1);
void on_ss_levelSpin_valueChanged(int arg1); void on_ss_levelSpin_valueChanged(int arg1);
void on_ss_otaCheckBox_stateChanged(int arg1); void on_ss_otaCheckBox_stateChanged(int arg1);
void on_socks_UserNameTxt_textEdited(const QString &arg1); void on_socks_UserNameTxt_textEdited(const QString &arg1);
void on_socks_PasswordTxt_textEdited(const QString &arg1); void on_socks_PasswordTxt_textEdited(const QString &arg1);
void on_securityCombo_currentIndexChanged(const QString &arg1); void on_securityCombo_currentIndexChanged(const QString &arg1);
private: private:
QString Tag; QString tag;
void ReloadGUI(); void ReloadGUI();
bool useFProxy; bool useForwardProxy;
OUTBOUND GenerateConnectionJson(); OUTBOUND GenerateConnectionJson();
OUTBOUND Original; OUTBOUND originalConfig;
OUTBOUND Result; OUTBOUND resultConfig;
QJsonObject Mux; QJsonObject muxConfig;
// //
// Connection Configs // Connection Configs
QString OutboundType; QString outboundType;
QString address;
int port;
// //
VMessServerObject vmess; VMessServerObject vmess;
ShadowSocksServerObject shadowsocks; ShadowSocksServerObject shadowsocks;
SocksServerObject socks; SocksServerObject socks;
// //
StreamSettingsWidget *ssWidget; StreamSettingsWidget *streamSettingsWidget;
//
QMap<int, QPair<QvPluginOutboundProtocolObject, QvPluginEditor *>> pluginWidgets;
}; };

View File

@ -5,7 +5,7 @@
#include "core/connection/ConnectionIO.hpp" #include "core/connection/ConnectionIO.hpp"
#include "core/connection/Serialization.hpp" #include "core/connection/Serialization.hpp"
#include "core/handler/ConfigHandler.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_JsonEditor.hpp"
#include "ui/editors/w_OutboundEditor.hpp" #include "ui/editors/w_OutboundEditor.hpp"
#include "ui/editors/w_RoutesEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp"
@ -37,7 +37,10 @@ QvMessageBusSlotImpl(ImportConfigWindow)
{ {
switch (msg) switch (msg)
{ {
MBShowDefaultImpl MBHideDefaultImpl MBRetranslateDefaultImpl MBUpdateColorSchemeDefaultImpl MBShowDefaultImpl;
MBHideDefaultImpl;
MBRetranslateDefaultImpl;
MBUpdateColorSchemeDefaultImpl;
} }
} }
@ -53,7 +56,7 @@ QMultiHash<QString, CONFIGROOT> ImportConfigWindow::SelectConnection(bool outbou
routeEditBtn->setEnabled(!outboundsOnly); routeEditBtn->setEnabled(!outboundsOnly);
this->exec(); this->exec();
QMultiHash<QString, CONFIGROOT> conn; QMultiHash<QString, CONFIGROOT> conn;
for (const auto connEntry : connections.values()) for (const auto &connEntry : connections.values())
{ {
conn += connEntry; conn += connEntry;
} }
@ -64,11 +67,11 @@ int ImportConfigWindow::ImportConnection()
{ {
this->exec(); this->exec();
int count = 0; int count = 0;
for (const auto groupName : connections.keys()) for (const auto &groupName : connections.keys())
{ {
GroupId groupId = groupName.isEmpty() ? DefaultGroupId : ConnectionManager->CreateGroup(groupName, false); GroupId groupId = groupName.isEmpty() ? DefaultGroupId : ConnectionManager->CreateGroup(groupName, false);
const auto groupObject = connections[groupName]; const auto groupObject = connections[groupName];
for (const auto connConf : groupObject) for (const auto &connConf : groupObject)
{ {
auto connName = groupObject.key(connConf); auto connName = groupObject.key(connConf);
@ -77,7 +80,7 @@ int ImportConfigWindow::ImportConnection()
{ {
connName = protocol + "/" + host + ":" + QSTRN(port) + "-" + GenerateRandomString(5); 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 else
{ {
for (auto conf : config) for (const auto &conf : config)
{ {
AddToGroup(newGroupName, config.key(conf), conf); AddToGroup(newGroupName, config.key(conf), conf);
} }
@ -173,7 +176,7 @@ void ImportConfigWindow::on_beginImportBtn_clicked()
if (!linkErrors.isEmpty()) if (!linkErrors.isEmpty())
{ {
for (auto item : linkErrors) for (const auto &item : linkErrors)
{ {
vmessConnectionStringTxt->appendPlainText(linkErrors.key(item)); vmessConnectionStringTxt->appendPlainText(linkErrors.key(item));
errorsList->addItem(item); errorsList->addItem(item);
@ -213,6 +216,8 @@ void ImportConfigWindow::on_selectImageBtn_clicked()
imageFileEdit->setText(dir); imageFileEdit->setText(dir);
// //
QFile file(dir); QFile file(dir);
if (!file.exists())
return;
file.open(QFile::OpenModeFlag::ReadOnly); file.open(QFile::OpenModeFlag::ReadOnly);
auto buf = file.readAll(); auto buf = file.readAll();
file.close(); file.close();
@ -258,7 +263,7 @@ void ImportConfigWindow::on_errorsList_currentItemChanged(QListWidgetItem *curre
void ImportConfigWindow::on_connectionEditBtn_clicked() void ImportConfigWindow::on_connectionEditBtn_clicked()
{ {
OutboundEditor w(this); OutboundEditor w(OUTBOUND(), this);
auto outboundEntry = w.OpenEditor(); auto outboundEntry = w.OpenEditor();
bool isChanged = w.result() == QDialog::Accepted; bool isChanged = w.result() == QDialog::Accepted;
QString alias = w.GetFriendlyName(); QString alias = w.GetFriendlyName();
@ -283,7 +288,7 @@ void ImportConfigWindow::on_cancelImportBtn_clicked()
void ImportConfigWindow::on_subscriptionButton_clicked() void ImportConfigWindow::on_subscriptionButton_clicked()
{ {
hide(); hide();
SubscriptionEditor w; SubscriptionEditor w(this);
w.exec(); w.exec();
auto importToComplex = !keepImportedInboundCheckBox->isEnabled(); auto importToComplex = !keepImportedInboundCheckBox->isEnabled();
connections.clear(); connections.clear();

View File

@ -1,6 +1,6 @@
#include "w_MainWindow.hpp" #include "w_MainWindow.hpp"
#include "components/pac/QvPACHandler.hpp" #include "components/plugins/QvPluginHost.hpp"
#include "components/plugins/toolbar/QvToolbar.hpp" #include "components/plugins/toolbar/QvToolbar.hpp"
#include "components/proxy/QvProxyConfigurator.hpp" #include "components/proxy/QvProxyConfigurator.hpp"
#include "components/update/UpdateChecker.hpp" #include "components/update/UpdateChecker.hpp"
@ -9,6 +9,7 @@
#include "ui/editors/w_OutboundEditor.hpp" #include "ui/editors/w_OutboundEditor.hpp"
#include "ui/editors/w_RoutesEditor.hpp" #include "ui/editors/w_RoutesEditor.hpp"
#include "ui/w_ImportConfig.hpp" #include "ui/w_ImportConfig.hpp"
#include "ui/w_PluginManager.hpp"
#include "ui/w_PreferencesWindow.hpp" #include "ui/w_PreferencesWindow.hpp"
#include "ui/w_SubscriptionManager.hpp" #include "ui/w_SubscriptionManager.hpp"
#include "ui/widgets/ConnectionInfoWidget.hpp" #include "ui/widgets/ConnectionInfoWidget.hpp"
@ -50,11 +51,11 @@ QvMessageBusSlotImpl(MainWindow)
void MainWindow::UpdateColorScheme() 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")); importConfigButton->setIcon(QICON_R("import.png"));
updownImageBox->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_UI_COLORSCHEME_ROOT + "netspeed_arrow.png)"); updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)");
// //
tray_action_ShowHide->setIcon(this->windowIcon()); tray_action_ShowHide->setIcon(this->windowIcon());
action_RCM_Start->setIcon(QICON_R("connect.png")); action_RCM_Start->setIcon(QICON_R("connect.png"));
@ -131,17 +132,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
UpdateColorScheme(); UpdateColorScheme();
// //
// //
connect(ConnectionManager, &QvConfigHandler::OnCrashed, [&] { connect(ConnectionManager, &QvConfigHandler::OnKernelCrashed, [&](const ConnectionId &, const QString &reason) {
this->show(); this->show();
QvMessageBoxWarn(this, tr("V2ray vcore terminated."), QvMessageBoxWarn(this, tr("Kernel terminated."),
tr("V2ray vcore terminated unexpectedly.") + NEWLINE + NEWLINE + tr("The kernel terminated unexpectedly:") + NEWLINE + reason + NEWLINE + NEWLINE +
tr("To solve the problem, read the V2ray log in the log text browser.")); tr("To solve the problem, read the kernel log in the log text browser."));
}); });
// //
connect(ConnectionManager, &QvConfigHandler::OnConnected, this, &MainWindow::OnConnected); connect(ConnectionManager, &QvConfigHandler::OnConnected, this, &MainWindow::OnConnected);
connect(ConnectionManager, &QvConfigHandler::OnDisconnected, this, &MainWindow::OnDisconnected); connect(ConnectionManager, &QvConfigHandler::OnDisconnected, this, &MainWindow::OnDisconnected);
connect(ConnectionManager, &QvConfigHandler::OnStatsAvailable, this, &MainWindow::OnStatsAvailable); 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::OnConnectionDeleted, this, &MainWindow::OnConnectionDeleted);
connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &MainWindow::OnConnectionCreated); connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &MainWindow::OnConnectionCreated);
@ -229,7 +230,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connectionListRCM_Menu->addAction(action_RCM_Delete); connectionListRCM_Menu->addAction(action_RCM_Delete);
connect(action_RCM_Start, &QAction::triggered, this, &MainWindow::on_action_StartThis_triggered); 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_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_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_EditJson, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsJson_triggered);
connect(action_RCM_EditComplex, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsComplex_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(); 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) void MainWindow::keyReleaseEvent(QKeyEvent *e)
@ -383,11 +390,6 @@ void MainWindow::on_action_StartThis_triggered()
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
if (GlobalConfig.inboundConfig.pacConfig.enablePAC && pacServer != nullptr && pacServer->isRunning())
{
// Wait for PAC server to finish.
pacServer->wait();
}
hTray.hide(); hTray.hide();
} }
@ -478,12 +480,18 @@ void MainWindow::on_connectionListWidget_customContextMenuRequested(const QPoint
auto item = connectionListWidget->itemAt(connectionListWidget->mapFromGlobal(_pos)); auto item = connectionListWidget->itemAt(connectionListWidget->mapFromGlobal(_pos));
if (item != nullptr) if (item != nullptr)
{ {
if (GetItemWidget(item)->IsConnection()) 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); connectionListRCM_Menu->popup(_pos);
} }
} }
}
void MainWindow::on_action_RCM_DeleteThese_triggered() void MainWindow::on_action_RCM_DeleteThese_triggered()
{ {
@ -492,10 +500,17 @@ void MainWindow::on_action_RCM_DeleteThese_triggered()
for (auto item : connectionListWidget->selectedItems()) for (auto item : connectionListWidget->selectedItems())
{ {
auto widget = GetItemWidget(item); auto widget = GetItemWidget(item);
if (widget)
{
if (widget->IsConnection()) if (widget->IsConnection())
{ {
connlist.append(get<1>(widget->Identifier())); connlist.append(get<1>(widget->Identifier()));
} }
else
{
connlist.append(ConnectionManager->GetGroupMetaObject(get<0>(widget->Identifier())).connections);
}
}
} }
LOG(MODULE_UI, "Selected " + QSTRN(connlist.count()) + " items") LOG(MODULE_UI, "Selected " + QSTRN(connlist.count()) + " items")
@ -568,13 +583,17 @@ void MainWindow::on_connectionListWidget_itemDoubleClicked(QTreeWidgetItem *item
void MainWindow::OnDisconnected(const ConnectionId &id) void MainWindow::OnDisconnected(const ConnectionId &id)
{ {
Q_UNUSED(id) Q_UNUSED(id)
hTray.setIcon(Q_TRAYICON("tray.png"));
tray_action_Start->setEnabled(true); tray_action_Start->setEnabled(true);
tray_action_Stop->setEnabled(false); tray_action_Stop->setEnabled(false);
tray_action_Restart->setEnabled(false); tray_action_Restart->setEnabled(false);
tray_SystemProxyMenu->setEnabled(false); tray_SystemProxyMenu->setEnabled(false);
lastConnectedId = id; lastConnectedId = id;
locateBtn->setEnabled(false); locateBtn->setEnabled(false);
if (!GlobalConfig.uiConfig.quietMode)
{
this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon()); this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon());
}
hTray.setToolTip(TRAY_TOOLTIP_PREFIX); hTray.setToolTip(TRAY_TOOLTIP_PREFIX);
netspeedLabel->setText("0.00 B/s" NEWLINE "0.00 B/s"); netspeedLabel->setText("0.00 B/s" NEWLINE "0.00 B/s");
dataamountLabel->setText("0.00 B" NEWLINE "0.00 B"); dataamountLabel->setText("0.00 B" NEWLINE "0.00 B");
@ -583,16 +602,12 @@ void MainWindow::OnDisconnected(const ConnectionId &id)
{ {
ClearSystemProxy(); ClearSystemProxy();
} }
if (GlobalConfig.inboundConfig.pacConfig.enablePAC)
{
pacServer->stopServer();
}
} }
void MainWindow::OnConnected(const ConnectionId &id) void MainWindow::OnConnected(const ConnectionId &id)
{ {
Q_UNUSED(id) Q_UNUSED(id)
hTray.setIcon(Q_TRAYICON("tray-connected.png"));
tray_action_Start->setEnabled(false); tray_action_Start->setEnabled(false);
tray_action_Stop->setEnabled(true); tray_action_Stop->setEnabled(true);
tray_action_Restart->setEnabled(true); tray_action_Restart->setEnabled(true);
@ -601,70 +616,14 @@ void MainWindow::OnConnected(const ConnectionId &id)
locateBtn->setEnabled(true); locateBtn->setEnabled(true);
on_clearlogButton_clicked(); on_clearlogButton_clicked();
auto name = GetDisplayName(id); auto name = GetDisplayName(id);
if (!GlobalConfig.uiConfig.quietMode)
{
this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon()); this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon());
}
hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name); hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name);
connetionStatusLabel->setText(tr("Connected: ") + name); connetionStatusLabel->setText(tr("Connected: ") + name);
// //
ConnectionManager->StartLatencyTest(id); 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) if (GlobalConfig.inboundConfig.setSystemProxy)
{ {
MWSetSystemProxy(); MWSetSystemProxy();
@ -860,7 +819,7 @@ void MainWindow::OnGroupDeleted(const GroupId &id, const QList<ConnectionId> &co
void MainWindow::on_locateBtn_clicked() void MainWindow::on_locateBtn_clicked()
{ {
auto id = ConnectionManager->CurrentConnection(); auto id = KernelInstance->CurrentConnection();
if (id != NullConnectionId) if (id != NullConnectionId)
{ {
connectionListWidget->setCurrentItem(connectionNodes.value(id).get()); connectionListWidget->setCurrentItem(connectionNodes.value(id).get());
@ -964,7 +923,10 @@ void MainWindow::on_action_RCM_SetAutoConnection_triggered()
auto widget = GetItemWidget(current); auto widget = GetItemWidget(current);
auto &conn = get<1>(widget->Identifier()); auto &conn = get<1>(widget->Identifier());
GlobalConfig.autoStartId = conn.toString(); GlobalConfig.autoStartId = conn.toString();
if (!GlobalConfig.uiConfig.quietMode)
{
hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn))); hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn)));
}
SaveGlobalSettings(); SaveGlobalSettings();
} }
} }
@ -975,6 +937,17 @@ void MainWindow::on_action_RCM_ClearUsage_triggered()
if (current != nullptr) if (current != nullptr)
{ {
auto widget = GetItemWidget(current); auto widget = GetItemWidget(current);
if (widget)
{
if (widget->IsConnection())
ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier())); ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier()));
else
ConnectionManager->ClearGroupUsage(get<0>(widget->Identifier()));
} }
} }
}
void MainWindow::on_pluginsBtn_clicked()
{
PluginManageWindow(this).exec();
}

View File

@ -2,7 +2,6 @@
#include "common/HTTPRequestHelper.hpp" #include "common/HTTPRequestHelper.hpp"
#include "common/LogHighlighter.hpp" #include "common/LogHighlighter.hpp"
#include "components/pac/QvPACHandler.hpp"
#include "components/speedchart/speedwidget.hpp" #include "components/speedchart/speedwidget.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
#include "ui/messaging/QvMessageBus.hpp" #include "ui/messaging/QvMessageBus.hpp"
@ -61,6 +60,8 @@ class MainWindow
void on_connectionListWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous); void on_connectionListWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
void on_masterLogBrowser_textChanged(); void on_masterLogBrowser_textChanged();
void on_pluginsBtn_clicked();
private: private:
void on_actionExit_triggered(); void on_actionExit_triggered();
void on_action_StartThis_triggered(); void on_action_StartThis_triggered();
@ -108,7 +109,6 @@ class MainWindow
// Charts // Charts
SpeedWidget *speedChartWidget; SpeedWidget *speedChartWidget;
QSystemTrayIcon hTray; QSystemTrayIcon hTray;
PACServer *pacServer;
SyntaxHighlighter *vCoreLogHighlighter; SyntaxHighlighter *vCoreLogHighlighter;
ConnectionInfoWidget *infoWidget; ConnectionInfoWidget *infoWidget;
// //

View File

@ -22,7 +22,7 @@
<widget class="QWidget" name="centralWidget"> <widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0"> <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,1,0">
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0"> <layout class="QHBoxLayout" name="horizontalLayout" stretch="0,0,0,0,0">
<property name="spacing"> <property name="spacing">
<number>5</number> <number>5</number>
</property> </property>
@ -33,6 +33,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QPushButton" name="pluginsBtn">
<property name="text">
<string>Plugins</string>
</property>
</widget>
</item>
<item> <item>
<widget class="QPushButton" name="preferencesBtn"> <widget class="QPushButton" name="preferencesBtn">
<property name="text"> <property name="text">
@ -553,7 +560,6 @@
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<tabstops> <tabstops>
<tabstop>subsButton</tabstop> <tabstop>subsButton</tabstop>
<tabstop>preferencesBtn</tabstop>
<tabstop>connectionListWidget</tabstop> <tabstop>connectionListWidget</tabstop>
<tabstop>importConfigButton</tabstop> <tabstop>importConfigButton</tabstop>
</tabstops> </tabstops>

View File

@ -4,102 +4,66 @@
void MainWindow::MWSetSystemProxy() void MainWindow::MWSetSystemProxy()
{ {
bool usePAC = GlobalConfig.inboundConfig.pacConfig.enablePAC; auto inboundPorts = KernelInstance->InboundPorts();
bool pacUseSocks = GlobalConfig.inboundConfig.pacConfig.useSocksProxy; bool httpEnabled = inboundPorts.contains("http");
bool httpEnabled = GlobalConfig.inboundConfig.useHTTP; bool socksEnabled = inboundPorts.contains("socks");
bool socksEnabled = GlobalConfig.inboundConfig.useSocks; auto httpPort = inboundPorts["http"];
// auto socksPort = inboundPorts["socks"];
bool isComplex = IsComplexConfig(ConnectionManager->CurrentConnection());
if (!isComplex)
{
// Is simple config and we will try to set system proxy.
LOG(MODULE_UI, "Preparing to set system proxy")
//
QString proxyAddress; QString proxyAddress;
bool canSetSystemProxy = true;
if (usePAC)
{
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) if (httpEnabled || socksEnabled)
{ {
// Not use PAC, System proxy should use HTTP or SOCKS proxyAddress = "127.0.0.1";
LOG(MODULE_PROXY, "Setting up system proxy.") SetSystemProxy(proxyAddress, httpPort, socksPort);
// A 'proxy host' should be a host WITHOUT `http://` uri scheme hTray.setIcon(Q_TRAYICON("tray-systemproxy.png"));
proxyAddress = "localhost"; if (!GlobalConfig.uiConfig.quietMode)
}
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.")); hTray.showMessage("Qv2ray", tr("System proxy configured."));
} }
} }
else 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() void MainWindow::MWClearSystemProxy()
{ {
ClearSystemProxy(); ClearSystemProxy();
hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png"));
if (!GlobalConfig.uiConfig.quietMode)
{
hTray.showMessage("Qv2ray", tr("System proxy removed.")); hTray.showMessage("Qv2ray", tr("System proxy removed."));
} }
}
void MainWindow::CheckSubscriptionsUpdate() void MainWindow::CheckSubscriptionsUpdate()
{ {
QStringList updateList; QStringList updateList;
auto subscriptions = ConnectionManager->Subscriptions(); 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); // The update is ignored.
auto renewTime = lastRenewDate.addSecs(into.updateInterval * 86400); if (info.updateInterval == 0)
continue;
//
const auto &lastRenewDate = QDateTime::fromTime_t(info.lastUpdated);
const auto &renewTime = lastRenewDate.addSecs(info.updateInterval * 86400);
LOG(MODULE_SUBSCRIPTION, // LOG(MODULE_SUBSCRIPTION, //
"Subscription \"" + entry.toString() + "\": " + // "Subscription \"" + info.displayName + "\": " + //
NEWLINE + " --> Last renewal time: " + lastRenewDate.toString() + // NEWLINE + " --> Last renewal time: " + lastRenewDate.toString() + //
NEWLINE + " --> Renew interval: " + QSTRN(into.updateInterval) + // NEWLINE + " --> Renew interval: " + QSTRN(info.updateInterval) + //
NEWLINE + " --> Ideal renew time: " + renewTime.toString()) // NEWLINE + " --> Ideal renew time: " + renewTime.toString()) //
if (renewTime <= QDateTime::currentDateTime()) if (renewTime <= QDateTime::currentDateTime())
{ {
LOG(MODULE_SUBSCRIPTION, "Subscription: " + entry.toString() + " needs to be updated.") LOG(MODULE_SUBSCRIPTION, "Subscription: " + info.displayName + " needs to be updated.")
updateList.append(entry.toString()); updateList.append(info.displayName);
} }
} }

132
src/ui/w_PluginManager.cpp Normal file
View File

@ -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 &current = 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));
}

View File

@ -0,0 +1,33 @@
#pragma once
#include "ui_w_PluginManager.h"
#include <QDialog>
#include <memory>
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<QWidget> settingsWidget;
bool isLoading = true;
};

356
src/ui/w_PluginManager.ui Normal file
View File

@ -0,0 +1,356 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>w_PluginManager</class>
<widget class="QDialog" name="w_PluginManager">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>757</width>
<height>518</height>
</rect>
</property>
<property name="windowTitle">
<string>Plugin Manager</string>
</property>
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Plugins</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QListWidget" name="pluginListWidget"/>
</item>
<item row="2" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="openPluginFolder">
<property name="text">
<string>Open Local Plugin Folder</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolButton">
<property name="toolTip">
<string>Online help about plugins</string>
</property>
<property name="text">
<string>?</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="3">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tabWidgetPage1">
<attribute name="title">
<string>Plugin Metadata</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="1,0">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Name</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="pluginNameLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Author</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="pluginAuthorLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Description</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="pluginDescriptionLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Library Path</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="pluginLibPathLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>State</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLabel" name="pluginStateLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Capability</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLabel" name="pluginHookTypeLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Special Type</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="pluginTypeLabel">
<property name="cursor">
<cursorShape>IBeamCursor</cursorShape>
</property>
<property name="text">
<string/>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="pluginIconLabel">
<property name="minimumSize">
<size>
<width>64</width>
<height>64</height>
</size>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>Plugin Settings</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0">
<item>
<layout class="QGridLayout" name="pluginSettingsLayout">
<item row="0" column="0">
<widget class="QLabel" name="pluginUnloadLabel">
<property name="text">
<string>Plugin Not Loaded</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="pluginEditSettingsJsonBtn">
<property name="text">
<string>Manually Edit Settings</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>w_PluginManager</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>w_PluginManager</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -4,10 +4,11 @@
#include "common/QvHelpers.hpp" #include "common/QvHelpers.hpp"
#include "common/QvTranslator.hpp" #include "common/QvTranslator.hpp"
#include "components/autolaunch/QvAutoLaunch.hpp" #include "components/autolaunch/QvAutoLaunch.hpp"
#include "components/plugins/interface/QvPluginInterface.hpp"
#include "components/plugins/toolbar/QvToolbar.hpp" #include "components/plugins/toolbar/QvToolbar.hpp"
#include "core/connection/ConnectionIO.hpp" #include "core/connection/ConnectionIO.hpp"
#include "core/handler/ConfigHandler.hpp" #include "core/handler/ConfigHandler.hpp"
#include "core/kernel/KernelInteractions.hpp" #include "core/kernel/V2rayKernelInteractions.hpp"
#include "core/settings/SettingsBackend.hpp" #include "core/settings/SettingsBackend.hpp"
#include "ui/widgets/RouteSettingsMatrix.hpp" #include "ui/widgets/RouteSettingsMatrix.hpp"
@ -69,6 +70,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
qvBuildInfo->setText(QV2RAY_BUILD_INFO); qvBuildInfo->setText(QV2RAY_BUILD_INFO);
qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO); qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO);
qvBuildTime->setText(__DATE__ " " __TIME__); qvBuildTime->setText(__DATE__ " " __TIME__);
qvPluginInterfaceVersionLabel->setText(tr("Version: %1").arg(QSTRN(QV2RAY_PLUGIN_INTERFACE_VERSION)));
// //
// Deep copy // Deep copy
CurrentConfig = GlobalConfig; CurrentConfig = GlobalConfig;
@ -87,14 +89,6 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
// //
// //
listenIPTxt->setText(CurrentConfig.inboundConfig.listenip); 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; bool have_http = CurrentConfig.inboundConfig.useHTTP;
httpGroupBox->setChecked(have_http); httpGroupBox->setChecked(have_http);
@ -134,12 +128,26 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
// //
localDNSCb->setChecked(CurrentConfig.connectionConfig.withLocalDNS); 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(); DNSListTxt->clear();
for (auto dnsStr : CurrentConfig.connectionConfig.dnsList) for (auto dnsStr : CurrentConfig.connectionConfig.dnsList)
{ {
auto str = dnsStr.trimmed(); auto str = dnsStr.trimmed();
if (!str.isEmpty()) if (!str.isEmpty())
{ {
DNSListTxt->appendPlainText(str); DNSListTxt->appendPlainText(str);
@ -150,11 +158,11 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
updateSettingsGroupBox->setEnabled(false); updateSettingsGroupBox->setEnabled(false);
updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor.")); updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor."));
#endif #endif
//
updateChannelCombo->setCurrentIndex(CurrentConfig.updateConfig.updateChannel); updateChannelCombo->setCurrentIndex(CurrentConfig.updateConfig.updateChannel);
cancelIgnoreVersionBtn->setEnabled(!CurrentConfig.updateConfig.ignoredVersion.isEmpty()); cancelIgnoreVersionBtn->setEnabled(!CurrentConfig.updateConfig.ignoredVersion.isEmpty());
ignoredNextVersion->setText(CurrentConfig.updateConfig.ignoredVersion); ignoredNextVersion->setText(CurrentConfig.updateConfig.ignoredVersion);
//
for (auto i = 0; i < CurrentConfig.toolBarConfig.Pages.size(); i++) for (auto i = 0; i < CurrentConfig.toolBarConfig.Pages.size(); i++)
{ {
nsBarPagesList->addItem(tr("Page") + QSTRN(i + 1) + ": " + QSTRN(CurrentConfig.toolBarConfig.Pages[i].Lines.size()) + " " + 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); maxLogLinesSB->setValue(CurrentConfig.uiConfig.maximumLogLines);
// //
pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" + setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy);
QSTRN(pacPortSB->value()) + "/pac");
// //
finishedLoading = true; finishedLoading = true;
routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this); routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this);
@ -256,12 +263,6 @@ void PreferencesWindow::on_buttonBox_accepted()
ports << CurrentConfig.inboundConfig.socks_port; ports << CurrentConfig.inboundConfig.socks_port;
} }
if (CurrentConfig.inboundConfig.pacConfig.enablePAC)
{
size++;
ports << CurrentConfig.inboundConfig.pacConfig.port;
}
if (!StartupOption.noAPI) if (!StartupOption.noAPI)
{ {
size++; size++;
@ -932,84 +933,6 @@ void PreferencesWindow::on_darkTrayCB_stateChanged(int arg1)
CurrentConfig.uiConfig.useDarkTrayIcon = arg1 == Qt::Checked; 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) void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1)
{ {
LOADINGCHECK LOADINGCHECK
@ -1017,28 +940,12 @@ void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1)
CurrentConfig.inboundConfig.setSystemProxy = arg1 == Qt::Checked; 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() void PreferencesWindow::on_pushButton_clicked()
{ {
LOADINGCHECK LOADINGCHECK
QDesktopServices::openUrl(QUrl::fromUserInput(QV2RAY_RULES_DIR)); 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) void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString &arg1)
{ {
LOADINGCHECK if (arg1.isEmpty()) LOADINGCHECK if (arg1.isEmpty())
@ -1147,23 +1054,6 @@ void PreferencesWindow::on_fpPortSB_valueChanged(int arg1)
CurrentConfig.connectionConfig.forwardProxyConfig.port = 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() void PreferencesWindow::on_checkVCoreSettings_clicked()
{ {
auto vcorePath = vCorePathTxt->text(); auto vcorePath = vCorePathTxt->text();
@ -1196,20 +1086,6 @@ void PreferencesWindow::on_socksGroupBox_clicked(bool checked)
CurrentConfig.inboundConfig.useSocks = 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) void PreferencesWindow::on_fpGroupBox_clicked(bool checked)
{ {
LOADINGCHECK LOADINGCHECK
@ -1237,3 +1113,81 @@ void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index)
CurrentConfig.updateConfig.updateChannel = index; CurrentConfig.updateConfig.updateChannel = index;
CurrentConfig.updateConfig.ignoredVersion.clear(); 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;
}

View File

@ -118,18 +118,10 @@ class PreferencesWindow
void on_darkTrayCB_stateChanged(int arg1); void on_darkTrayCB_stateChanged(int arg1);
void on_pacGoBtn_clicked();
void on_pacPortSB_valueChanged(int arg1);
void on_setSysProxyCB_stateChanged(int arg1); void on_setSysProxyCB_stateChanged(int arg1);
void on_pacProxyCB_currentIndexChanged(int index);
void on_pushButton_clicked(); void on_pushButton_clicked();
void on_pacProxyTxt_textEdited(const QString &arg1);
void on_autoStartSubsCombo_currentIndexChanged(const QString &arg1); void on_autoStartSubsCombo_currentIndexChanged(const QString &arg1);
void on_autoStartConnCombo_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_fpPortSB_valueChanged(int arg1);
void on_pacProxyTxt_textChanged(const QString &arg1);
void on_checkVCoreSettings_clicked(); void on_checkVCoreSettings_clicked();
void on_httpGroupBox_clicked(bool checked); void on_httpGroupBox_clicked(bool checked);
void on_socksGroupBox_clicked(bool checked); void on_socksGroupBox_clicked(bool checked);
void on_pacGroupBox_clicked(bool checked);
void on_fpGroupBox_clicked(bool checked); void on_fpGroupBox_clicked(bool checked);
void on_maxLogLinesSB_valueChanged(int arg1); void on_maxLogLinesSB_valueChanged(int arg1);
@ -168,6 +156,28 @@ class PreferencesWindow
void on_updateChannelCombo_currentIndexChanged(int index); 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: private:
// //
RouteSettingsMatrixWidget *routeSettingsWidget; RouteSettingsMatrixWidget *routeSettingsWidget;

File diff suppressed because it is too large Load Diff

View File

@ -181,9 +181,6 @@
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QDoubleSpinBox" name="updateIntervalSB"> <widget class="QDoubleSpinBox" name="updateIntervalSB">
<property name="minimum">
<double>0.500000000000000</double>
</property>
<property name="maximum"> <property name="maximum">
<double>365.000000000000000</double> <double>365.000000000000000</double>
</property> </property>

View File

@ -144,6 +144,8 @@ void ConnectionItemWidget::CancelRename()
} }
void ConnectionItemWidget::BeginRename() void ConnectionItemWidget::BeginRename()
{
if (IsConnection())
{ {
stackedWidget->setCurrentIndex(1); stackedWidget->setCurrentIndex(1);
renameTxt->setStyle(QStyleFactory::create("Fusion")); renameTxt->setStyle(QStyleFactory::create("Fusion"));
@ -151,6 +153,7 @@ void ConnectionItemWidget::BeginRename()
renameTxt->setText(originalItemName); renameTxt->setText(originalItemName);
renameTxt->setFocus(); renameTxt->setFocus();
} }
}
ConnectionItemWidget::~ConnectionItemWidget() ConnectionItemWidget::~ConnectionItemWidget()
{ {

View File

@ -103,7 +103,7 @@ void RouteSettingsMatrixWidget::on_importSchemeBtn_clicked()
// read the file and parse back to struct. // read the file and parse back to struct.
// if error occurred on parsing, an exception will be thrown. // 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<Qv2rayRouteScheme>(content); auto scheme = StructFromJsonString<Qv2rayRouteScheme>(content);
// show the information of this scheme to user, // show the information of this scheme to user,
@ -175,7 +175,7 @@ void RouteSettingsMatrixWidget::on_exportSchemeBtn_clicked()
// serialize and write out // serialize and write out
auto content = StructToJsonString(scheme); auto content = StructToJsonString(scheme);
StringToFile(content, savePath.value()); StringToFile(content, ACCESS_OPTIONAL_VALUE(savePath));
// done // done
// TODO: Give some success as Notification // TODO: Give some success as Notification

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>600</width> <width>582</width>
<height>409</height> <height>404</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -44,12 +44,6 @@
</item> </item>
</layout> </layout>
</item> </item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Route Settings</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
@ -129,9 +123,6 @@
</item> </item>
</layout> </layout>
</item> </item>
</layout>
</widget>
</item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>

View File

@ -22,7 +22,7 @@ QvMessageBusSlotImpl(StreamSettingsWidget)
} }
} }
StreamSettingsObject StreamSettingsWidget::GetStreamSettings() StreamSettingsObject StreamSettingsWidget::GetStreamSettings() const
{ {
return stream; return stream;
} }
@ -36,6 +36,7 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso)
tlsCB->setChecked(stream.security == "tls"); tlsCB->setChecked(stream.security == "tls");
serverNameTxt->setText(stream.tlsSettings.serverName); serverNameTxt->setText(stream.tlsSettings.serverName);
allowInsecureCB->setChecked(stream.tlsSettings.allowInsecure); allowInsecureCB->setChecked(stream.tlsSettings.allowInsecure);
allowInsecureCiphersCB->setChecked(stream.tlsSettings.allowInsecureCiphers);
alpnTxt->setPlainText(stream.tlsSettings.alpn.join(NEWLINE)); alpnTxt->setPlainText(stream.tlsSettings.alpn.join(NEWLINE));
// TCP // TCP
tcpHeaderTypeCB->setCurrentText(stream.tcpSettings.header.type); tcpHeaderTypeCB->setCurrentText(stream.tcpSettings.header.type);
@ -47,9 +48,9 @@ void StreamSettingsWidget::SetStreamObject(const StreamSettingsObject &sso)
// WS // WS
wsPathTxt->setText(stream.wsSettings.path); wsPathTxt->setText(stream.wsSettings.path);
QString wsHeaders; 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); wsHeadersTxt->setPlainText(wsHeaders);
@ -284,3 +285,8 @@ void StreamSettingsWidget::on_alpnTxt_textChanged()
{ {
stream.tlsSettings.alpn = SplitLines(alpnTxt->toPlainText()); stream.tlsSettings.alpn = SplitLines(alpnTxt->toPlainText());
} }
void StreamSettingsWidget::on_allowInsecureCiphersCB_stateChanged(int arg1)
{
stream.tlsSettings.allowInsecureCiphers = arg1 == Qt::Checked;
}

View File

@ -14,7 +14,7 @@ class StreamSettingsWidget
public: public:
explicit StreamSettingsWidget(QWidget *parent = nullptr); explicit StreamSettingsWidget(QWidget *parent = nullptr);
void SetStreamObject(const StreamSettingsObject &sso); void SetStreamObject(const StreamSettingsObject &sso);
StreamSettingsObject GetStreamSettings(); StreamSettingsObject GetStreamSettings() const;
private slots: private slots:
void on_httpPathTxt_textEdited(const QString &arg1); void on_httpPathTxt_textEdited(const QString &arg1);
@ -77,6 +77,8 @@ class StreamSettingsWidget
void on_alpnTxt_textChanged(); void on_alpnTxt_textChanged();
void on_allowInsecureCiphersCB_stateChanged(int arg1);
private: private:
QvMessageBusSlotDecl; QvMessageBusSlotDecl;
StreamSettingsObject stream; StreamSettingsObject stream;

View File

@ -635,37 +635,37 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label_6"> <widget class="QLabel" name="label_6">
<property name="text"> <property name="text">
<string>Server</string> <string>Server</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="serverNameTxt"/> <widget class="QLineEdit" name="serverNameTxt"/>
</item> </item>
<item row="3" column="0"> <item row="4" column="0">
<widget class="QLabel" name="label_14"> <widget class="QLabel" name="label_14">
<property name="text"> <property name="text">
<string>ALPN</string> <string>ALPN</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="4" column="1">
<widget class="QPlainTextEdit" name="alpnTxt"/> <widget class="QPlainTextEdit" name="alpnTxt"/>
</item> </item>
<item row="0" column="0"> <item row="0" column="0" colspan="2">
<widget class="QLabel" name="label_13"> <widget class="QCheckBox" name="tlsCB">
<property name="text"> <property name="text">
<string>TLS</string> <string>Enable TLS</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="2" column="0" colspan="2">
<widget class="QCheckBox" name="tlsCB"> <widget class="QCheckBox" name="allowInsecureCiphersCB">
<property name="text"> <property name="text">
<string>Enabled</string> <string>Allow Insecure Ciphers</string>
</property> </property>
</widget> </widget>
</item> </item>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff