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

6
.gitmodules vendored
View File

@ -10,12 +10,12 @@
[submodule "libs/libqvb"]
path = libs/libqvb
url = https://github.com/Qv2ray/QvRPCBridge
[submodule "3rdparty/cpp-httplib"]
path = 3rdparty/cpp-httplib
url = https://github.com/yhirose/cpp-httplib
[submodule "libs/puresource"]
path = libs/puresource
url = https://github.com/Qv2ray/PureSource/
[submodule "3rdparty/zxing-cpp"]
path = 3rdparty/zxing-cpp
url = https://github.com/nu-book/zxing-cpp
[submodule "src/components/plugins/interface"]
path = src/components/plugins/interface
url = https://github.com/Qv2ray/QvPlugin-Interface/

View File

@ -1,17 +1,6 @@
language: shell
os: linux
dist: bionic
arch:
- amd64
- arm64
git:
depth: false
branches:
only:
- dev
- /^v\d+\.\d+(\.\d+)?(-\S*)?$/
env:
global:
@ -23,30 +12,22 @@ addons:
- name: snapcraft
channel: stable
confinement: classic
before_install:
- echo "deb http://ppa.launchpad.net/ymshenyu/qv2ray-deps/ubuntu bionic main" | sudo tee -a /etc/apt/sources.list
- echo "deb http://archive.neon.kde.org/unstable bionic main" | sudo tee -a /etc/apt/sources.list
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 281F24E574404629AA3BDA1A4F10C386C55CDB04
- sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E6D4736255751E5D
- sudo apt-get update -qq
- name: lxd
channel: stable
script:
- snapcraft --destructive-mode
- sudo apt-get autoremove lxd --purge
- sudo /snap/bin/lxd waitready
- sudo /snap/bin/lxd init --auto
- sudo snapcraft --use-lxd
after_failure:
- sudo journalctl -u snapd
deploy:
- provider: snap
snap: qv2ray_*.snap
channel: edge
skip_cleanup: true
- provider: launchpad
slug: "~ymshenyu/qv2ray/+git/trunk"
oauth_token: $LAUNCHPAD_OAUTH_TOKEN
oauth_token_secret: $LAUNCHPAD_OAUTH_TOKEN_SECRET
on:
branch: dev
- provider: snap
snap: qv2ray_*.snap
channel: beta
skip_cleanup: true
on:
branch: /^v\d+\.\d+(\.\d+)?(-\S*)?$/
all_branches: true

@ -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)
endif()
set(QVPLUGIN_INTERFACE_INCLUDE_DIR "src/components/plugins/interface")
include(src/components/plugins/interface/QvPluginInterface.cmake)
set(QV2RAY_SOURCES
${QVPLUGIN_INTERFACE_HEADERS}
3rdparty/libsemver/version.cpp
src/base/Qv2rayLog.cpp
src/common/CommandArgs.cpp
@ -161,11 +165,10 @@ set(QV2RAY_SOURCES
src/components/geosite/QvGeositeReader.cpp
src/components/latency/win/ICMPPinger.cpp
src/components/latency/QvTCPing.cpp
src/components/pac/QvGFWPACConverter.cpp
src/components/pac/QvPACHandler.cpp
src/components/plugins/toolbar/QvToolbar.cpp
src/components/plugins/toolbar/QvToolbar_linux.cpp
src/components/plugins/toolbar/QvToolbar_win.cpp
src/components/plugins/QvPluginHost.cpp
src/components/proxy/QvProxyConfigurator.cpp
src/components/route/RouteSchemeIO.cpp
src/components/speedchart/speedplotview.cpp
@ -180,9 +183,10 @@ set(QV2RAY_SOURCES
src/core/connection/Serialization_vmess.cpp
src/core/CoreUtils.cpp
src/core/handler/ConfigHandler.cpp
src/core/handler/V2rayInstanceHandler.cpp
src/core/handler/KernelInstanceHandler.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/settings/SettingsBackend.cpp
src/core/settings/SettingsUpgrade.cpp
@ -205,6 +209,7 @@ set(QV2RAY_SOURCES
src/ui/w_MainWindow.cpp
src/ui/w_MainWindow_extra.cpp
src/ui/w_PreferencesWindow.cpp
src/ui/w_PluginManager.cpp
src/ui/w_ScreenShot_Core.cpp
src/ui/w_SubscriptionManager.cpp
# ui files
@ -220,6 +225,7 @@ set(QV2RAY_SOURCES
src/ui/widgets/RouteSettingsMatrix.ui
src/ui/w_MainWindow.ui
src/ui/w_PreferencesWindow.ui
src/ui/w_PluginManager.ui
src/ui/w_ScreenShot_Core.ui
# headers
3rdparty/libsemver/version.hpp
@ -246,8 +252,8 @@ set(QV2RAY_SOURCES
src/components/geosite/QvGeositeReader.hpp
src/components/latency/win/ICMPPinger.hpp
src/components/latency/QvTCPing.hpp
src/components/pac/QvPACHandler.hpp
src/components/plugins/toolbar/QvToolbar.hpp
src/components/plugins/QvPluginHost.hpp
src/components/proxy/QvProxyConfigurator.hpp
src/components/route/RouteSchemeIO.hpp
src/components/route/presets/RouteScheme_V2rayN.hpp
@ -260,8 +266,10 @@ set(QV2RAY_SOURCES
src/core/CoreSafeTypes.hpp
src/core/CoreUtils.hpp
src/core/handler/ConfigHandler.hpp
src/core/handler/KernelInstanceHandler.hpp
src/core/kernel/APIBackend.hpp
src/core/kernel/KernelInteractions.hpp
src/core/kernel/V2rayKernelInteractions.hpp
src/core/kernel/PluginKernelInteractions.hpp
src/core/kernel/QvKernelABIChecker.hpp
src/core/settings/SettingsBackend.hpp
src/ui/editors/w_InboundEditor.hpp
@ -281,10 +289,11 @@ set(QV2RAY_SOURCES
src/ui/w_ImportConfig.hpp
src/ui/w_MainWindow.hpp
src/ui/w_PreferencesWindow.hpp
src/ui/w_PluginManager.hpp
src/ui/w_ScreenShot_Core.hpp
src/ui/w_SubscriptionManager.hpp
assets/qv2ray.rc
)
)
if(EMBED_TRANSLATIONS)
add_definitions(-DEMBED_TRANSLATIONS)
@ -305,7 +314,7 @@ set(QT_LIBRARY
Qt5::Gui
Qt5::Widgets
Qt5::Network
)
)
add_executable(${PROJECT_NAME}
${GUI_TYPE}
@ -320,7 +329,7 @@ add_executable(${PROJECT_NAME}
${API_PROTO_SRCS}
${QRC_RESOURCES}
${QM_FILES}
)
)
target_link_libraries(${PROJECT_NAME}
${QT_LIBRARY}
@ -328,7 +337,7 @@ target_link_libraries(${PROJECT_NAME}
${QV2RAY_BACKEND_LIBRARIES}
${QNODEEDITOR_LIBRARY}
${ZXING_LIBRARY}
)
)
target_include_directories(${PROJECT_NAME} PRIVATE
@ -341,7 +350,7 @@ target_include_directories(${PROJECT_NAME} PRIVATE
${SINGLEAPPLICATION_DIR}
${Protobuf_INCLUDE_DIRS}
${cpp-httplib_INCLUDE_DIRS}
)
)
if(APPLE)
find_package(Iconv REQUIRED)
@ -356,6 +365,7 @@ if(APPLE)
)
set(MACOSX_ICON "${CMAKE_SOURCE_DIR}/assets/icons/qv2ray.icns")
set(MACOSX_PLIST "${CMAKE_SOURCE_DIR}/assets/MacOSXBundleInfo.plist.in")
set_source_files_properties(${QM_FILES}
PROPERTIES
MACOSX_PACKAGE_LOCATION Resources/lang
@ -368,6 +378,7 @@ if(APPLE)
set_target_properties(${PROJECT_NAME}
PROPERTIES
MACOSX_BUNDLE TRUE
MACOSX_BUNDLE_INFO_PLIST ${MACOSX_PLIST}
MACOSX_BUNDLE_BUNDLE_NAME "Qv2ray"
MACOSX_BUNDLE_BUNDLE_VERSION ${QV2RAY_VERSION_STRING}
MACOSX_BUNDLE_COPYRIGHT "Copyright (c) 2019-2020 Qv2ray Development Group"

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)
set(CPACK_GENERATOR "DragNDrop")
if(DS_STORE_SCRIPT)
set(CPACK_DMG_DS_STORE_SETUP_SCRIPT "${CMAKE_SOURCE_DIR}/cmake/CMakeDMGSetup.scpt")
else()
set(CPACK_DMG_DS_STORE "${CMAKE_SOURCE_DIR}/assets/DS_Store")
endif()
set(CPACK_DMG_BACKGROUND_IMAGE "${CMAKE_SOURCE_DIR}/assets/CMakeDMGBackground.png")
configure_file("${CMAKE_SOURCE_DIR}/assets/package_dmg.json.in" "${CMAKE_SOURCE_DIR}/assets/package_dmg.json" @ONLY)
endif()
include(CPack)

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
* 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_dark/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>
</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
after:
- desktop-qt5
- ppa
desktop-qt5:
source: https://github.com/ubuntu/snapcraft-desktop-helpers.git
@ -108,8 +109,21 @@ parts:
- locales-all
- xdg-user-dirs
- fcitx-frontend-qt5
after:
- ppa
qt5-gtk-platform:
plugin: nil
stage-packages:
- qt5-gtk-platformtheme
after:
- ppa
ppa:
plugin: nil
build-packages:
- software-properties-common
- dirmngr
override-build: |
sudo add-apt-repository -y ppa:ymshenyu/qv2ray-deps
sudo apt-get dist-upgrade -y

View File

@ -46,6 +46,7 @@ using namespace Qv2ray::base::objects::transfer;
#define QV2RAY_CONFIG_FILE (QV2RAY_CONFIG_DIR + "Qv2ray.conf")
#define QV2RAY_CONNECTIONS_DIR (QV2RAY_CONFIG_DIR + "connections/")
#define QV2RAY_SUBSCRIPTION_DIR (QV2RAY_CONFIG_DIR + "subscriptions/")
#define QV2RAY_PLUGIN_SETTINGS_DIR (QV2RAY_CONFIG_DIR + "plugin_settings/")
// Get GFWList and PAC file path.
#define QV2RAY_RULES_DIR (QV2RAY_CONFIG_DIR + "rules/")
@ -90,9 +91,17 @@ using namespace Qv2ray::base::objects::transfer;
#define BLACK(obj) obj->setPalette(QWidget::palette());
#define QV2RAY_UI_COLORSCHEME_ROOT \
#ifdef Q_OS_MACOS
#define ACCESS_OPTIONAL_VALUE(obj) (*obj)
#else
#define ACCESS_OPTIONAL_VALUE(obj) (obj.value())
#endif
#define Q_TRAYICON(name) (QIcon(GlobalConfig.uiConfig.useDarkTrayIcon ? ":/assets/icons/ui_dark/" name : ":/assets/icons/ui_light/" name))
#define QV2RAY_COLORSCHEME_ROOT \
((GlobalConfig.uiConfig.useDarkTheme) ? QStringLiteral(":/assets/icons/ui_dark/") : QStringLiteral(":/assets/icons/ui_light/"))
#define QICON_R(file) QIcon(QV2RAY_UI_COLORSCHEME_ROOT + file)
#define QICON_R(file) QIcon(QV2RAY_COLORSCHEME_ROOT + file)
#define QSTRN(num) QString::number(num)
@ -127,4 +136,27 @@ namespace Qv2ray
isExiting = true;
QApplication::quit();
}
inline QStringList Qv2rayAssetsPaths(const QString &dirName)
{
// Configuration Path
QStringList list;
list << QV2RAY_CONFIG_DIR + dirName;
//
#ifdef Q_OS_LINUX
// Linux platform directories.
list << QString("/usr/share/qv2ray/" + dirName);
list << QString("/usr/local/share/qv2ray/" + dirName);
list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, dirName, QStandardPaths::LocateDirectory);
list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, dirName, QStandardPaths::LocateDirectory);
#elif defined(Q_OS_MAC)
// macOS platform directories.
list << QDir(QApplication::applicationDirPath() + "/../Resources/" + dirName).absolutePath();
#endif
// This is the default behavior on Windows
list << QApplication::applicationDirPath() + "/" + dirName;
list.removeDuplicates();
return list;
};
} // namespace Qv2ray

View File

@ -41,6 +41,7 @@ const inline QString MODULE_FILEIO = "COMMON-FILEIO";
//
const inline QString MODULE_PROXY = "COMPONENT-PROXY";
const inline QString MODULE_UPDATE = "COMPONENT-UPDATE";
const inline QString MODULE_PLUGIN = "COMPONENT-PLUGIN";
const inline QString MODULE_PLUGINHOST = "COMPONENT-PLUGINHOST";
const inline QString MODULE_PLUGINCLIENT = "PLUGIN-CLIENT";
// ================================================================
const inline QString MODULE_CORE_HANDLER = "QV2RAY-CORE";

View File

@ -257,13 +257,14 @@ namespace Qv2ray::base::objects
{
QString serverName;
bool allowInsecure;
bool allowInsecureCiphers;
QList<QString> alpn;
QList<CertificateObject> certificates;
bool disableSystemRoot;
TLSObject() : serverName(), allowInsecure(), certificates(), disableSystemRoot()
TLSObject() : serverName(), allowInsecure(), allowInsecureCiphers(), certificates(), disableSystemRoot()
{
}
XTOSTRUCT(O(serverName, allowInsecure, alpn, certificates, disableSystemRoot))
XTOSTRUCT(O(serverName, allowInsecure, allowInsecureCiphers, alpn, certificates, disableSystemRoot))
};
} // namespace transfer
//

View File

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

View File

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

View File

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

View File

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

View File

@ -16,112 +16,80 @@ namespace Qv2ray::common
accessManager.disconnect();
}
bool QvHttpRequestHelper::setUrl(const QString &url)
{
QUrl qUrl = QUrl(url);
if (!qUrl.isValid())
{
LOG(MODULE_NETWORK, "Provided URL is invalid: " + url)
return false;
}
request.setUrl(qUrl);
return true;
}
void QvHttpRequestHelper::setHeader(const QByteArray &key, const QByteArray &value)
{
DEBUG(MODULE_NETWORK, "Adding HTTP request header: " + key + ":" + value)
request.setRawHeader(key, value);
}
QByteArray QvHttpRequestHelper::syncget(const QString &url, bool useProxy)
QByteArray QvHttpRequestHelper::Get(const QString &url, bool useProxy)
{
this->setUrl(url);
request.setUrl({ url });
if (useProxy)
{
auto proxy = QNetworkProxyFactory::systemProxyForQuery();
accessManager.setProxy(proxy.first());
LOG(MODULE_NETWORK, "Sync get is using system proxy settings")
auto p = GlobalConfig.networkConfig.useCustomProxy ?
QNetworkProxy{
GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, //
GlobalConfig.networkConfig.address, //
quint16(GlobalConfig.networkConfig.port) //
} :
QNetworkProxyFactory::systemProxyForQuery().first();
accessManager.setProxy(p);
}
else
{
DEBUG(MODULE_NETWORK, "Get without proxy.")
accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy));
}
if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy)
{
DEBUG(MODULE_NETWORK, "Adding HostNameLookupCapability to proxy.")
accessManager.proxy().setCapabilities(accessManager.proxy().capabilities() | QNetworkProxy::HostNameLookupCapability);
}
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, "Mozilla/5.0 (rv:71.0) Gecko/20100101 Firefox/71.0");
request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, GlobalConfig.networkConfig.userAgent);
reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p);
//
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
//
// Data or timeout?
auto data = reply->readAll();
return data;
}
void QvHttpRequestHelper::get(const QString &url)
void QvHttpRequestHelper::AsyncGet(const QString &url)
{
this->setUrl(url);
// request.setRawHeader("Content-Type",
// "application/x-www-form-urlencoded");
request.setUrl({ url });
if (GlobalConfig.networkConfig.useCustomProxy)
{
QNetworkProxy p{
GlobalConfig.networkConfig.type == "http" ? QNetworkProxy::HttpProxy : QNetworkProxy::Socks5Proxy, //
GlobalConfig.networkConfig.address, //
quint16(GlobalConfig.networkConfig.port) //
};
accessManager.setProxy(p);
}
else
{
DEBUG(MODULE_NETWORK, "Get without proxy.")
accessManager.setProxy(QNetworkProxy(QNetworkProxy::ProxyType::NoProxy));
}
if (accessManager.proxy().type() == QNetworkProxy::Socks5Proxy)
{
DEBUG(MODULE_NETWORK, "Adding HostNameLookupCapability to proxy.")
accessManager.proxy().setCapabilities(accessManager.proxy().capabilities() | QNetworkProxy::HostNameLookupCapability);
}
request.setAttribute(QNetworkRequest::RedirectPolicyAttribute, QNetworkRequest::NoLessSafeRedirectPolicy);
request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true);
request.setHeader(QNetworkRequest::KnownHeaders::UserAgentHeader, GlobalConfig.networkConfig.userAgent);
reply = accessManager.get(request);
connect(reply, &QNetworkReply::finished, this, &QvHttpRequestHelper::onRequestFinished_p);
connect(reply, &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead);
connect(reply, &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead_p);
}
// void QvHttpRequestHelper::post(const QString &url, const QByteArray
// &data)
//{
// this->setUrl(url);
// request.setRawHeader("Content-Type", "application/json");
// reply = accessManager.post(request, data);
// connect(reply, &QNetworkReply::finished, this,
// &QvHttpRequestHelper::onRequestFinished); connect(reply,
// &QNetworkReply::readyRead, this, &QvHttpRequestHelper::onReadyRead);
//}
// void QvHttpRequestHelper::put(const QString &url, const QByteArray
// &data)
// {
// this->setUrl(url);
// request.setRawHeader("Content-Type", "application/json");
// reply = accessManager.put(request, data);
// connect(reply, &QNetworkReply::finished, this,
// &QvHttpRequestHelper::onRequestFinished); connect(reply,
// &QNetworkReply::readyRead, this,
// &QvHttpRequestHelper::onReadyRead);
// }
// void QvHttpRequestHelper::del(const QString &url)
// {
// this->setUrl(url);
// request.setRawHeader("Content-Type", "application/json");
// reply = accessManager.deleteResource(request);
// connect(reply, &QNetworkReply::finished, this,
// &QvHttpRequestHelper::onRequestFinished); connect(reply,
// &QNetworkReply::readyRead, this,
// &QvHttpRequestHelper::onReadyRead);
// }
// void QvHttpRequestHelper::login(const QString &url, const QByteArray
// &data)
// {
// this->setUrl(url);
// request.setRawHeader("Content-Type",
// "application/x-www-form-urlencoded"); reply =
// accessManager.post(request, data); connect(reply,
// &QNetworkReply::finished, this,
// &QvHttpRequestHelper::onRequestFinished); connect(reply,
// &QNetworkReply::readyRead, this,
// &QvHttpRequestHelper::onReadyRead);
// }
void QvHttpRequestHelper::onRequestFinished_p()
{
if (reply->attribute(QNetworkRequest::HTTP2WasUsedAttribute).toBool())
@ -134,15 +102,15 @@ namespace Qv2ray::common
QString error = QMetaEnum::fromType<QNetworkReply::NetworkError>().key(reply->error());
LOG(MODULE_NETWORK, "Network request error string: " + error)
QByteArray empty;
emit httpRequestFinished(empty);
emit OnRequestFinished(empty);
}
else
{
emit httpRequestFinished(this->data);
emit OnRequestFinished(this->data);
}
}
void QvHttpRequestHelper::onReadyRead()
void QvHttpRequestHelper::onReadyRead_p()
{
DEBUG(MODULE_NETWORK, "A request is now ready read")
this->data += reply->readAll();

View File

@ -31,26 +31,18 @@ namespace Qv2ray::common
public:
explicit QvHttpRequestHelper(QObject *parent = nullptr);
~QvHttpRequestHelper();
bool setUrl(const QString &url);
void setHeader(const QByteArray &key, const QByteArray &value);
// get
QByteArray syncget(const QString &url, bool useProxy);
void get(const QString &url);
//// insert
// void post(const QString &url, const QByteArray &data);
//// update
// void put(const QString &url, const QByteArray &data);
//// delete
// void del(const QString &url);
// void login(const QString &url, const QByteArray &data);
void AsyncGet(const QString &url);
QByteArray Get(const QString &url, bool useProxy);
signals:
void httpRequestFinished(QByteArray &data);
void OnRequestFinished(QByteArray &data);
private slots:
void onRequestFinished_p();
void onReadyRead();
void onReadyRead_p();
private:
void setHeader(const QByteArray &key, const QByteArray &value);
QByteArray data;
QUrl url;
QNetworkReply *reply;

View File

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

View File

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

View File

@ -17,33 +17,14 @@ using namespace Qv2ray::base;
QStringList getLanguageSearchPaths()
{
// Configuration Path
QStringList list;
list << QV2RAY_CONFIG_DIR + "lang";
//
QStringList list = Qv2rayAssetsPaths("lang");
#ifdef EMBED_TRANSLATIONS
// If the translations have been embedded.
list << QString(":/translations/");
#endif
//
//
#ifdef QV2RAY_TRANSLATION_PATH
// Platform-specific dir, if specified.
list << QString(QV2RAY_TRANSLATION_PATH);
#endif
//
//
#ifdef Q_OS_LINUX
// Linux platform directories.
list << QString("/usr/share/qv2ray/lang/");
list << QString("/usr/local/share/qv2ray/lang/");
list << QStandardPaths::locateAll(QStandardPaths::AppDataLocation, "lang", QStandardPaths::LocateDirectory);
list << QStandardPaths::locateAll(QStandardPaths::AppConfigLocation, "lang", QStandardPaths::LocateDirectory);
#elif defined(Q_OS_MAC)
// macOS platform directories.
list << QDir(QApplication::applicationDirPath() + "/../Resources/lang").absolutePath();
#else
// This is the default behavior on Windows
list << QApplication::applicationDirPath() + "/lang";
#endif
return list;
};

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

View File

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

View File

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

View File

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

View File

@ -5,7 +5,7 @@
namespace Qv2ray::components::proxy
{
void ClearSystemProxy();
void SetSystemProxy(const QString &address, int http_port, int socks_port, bool usePAC);
void SetSystemProxy(const QString &address, int http_port, int socks_port);
} // namespace Qv2ray::components::proxy
using namespace Qv2ray::components;

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> DomainsProxy{ "geosite:google", "geosite:github", "geosite:netflix", "geosite:steam",
"geosite:telegram", "geosite:tumblr", "geosite:speedtest", "geosite:bbc",
"domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org",
"domain:naver.com" };
"geosite:telegram", "geosite:tumblr", "domain:naver.com", "geosite:bbc",
"domain:gvt1.com", "domain:textnow.com", "domain:twitch.tv", "domain:wikileaks.org" };
const inline QList<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",

View File

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

View File

@ -56,7 +56,11 @@ namespace Qv2ray::core
}
else
{
return false;
bool status;
auto info = PluginHost->TryGetOutboundInfo(*protocol, out["settings"].toObject(), &status);
*host = info.hostName;
*port = info.port;
return status;
}
}
@ -159,4 +163,23 @@ namespace Qv2ray::core
return ConnectionManager->GetConnectionMetaObject(id).groupId;
}
const QMap<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

View File

@ -43,9 +43,11 @@ namespace Qv2ray::core
const QString GetDisplayName(const ConnectionId &id, int limit = -1);
const QString GetDisplayName(const GroupId &id, int limit = -1);
//
const GroupId GetConnectionGroupId(const ConnectionId &id);
//
const QMap<QString, int> GetConfigInboundPorts(const CONFIGROOT &root);
const QMap<QString, int> GetConfigInboundPorts(const ConnectionId &id);
//
} // 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())
{
INBOUNDS inboundsList;
QJsonObject sniffingObject{ { "enabled", false } };
// HTTP Inbound
if (GlobalConfig.inboundConfig.useHTTP)
{
@ -317,6 +317,7 @@ namespace Qv2ray::core::connection
httpInBoundObject.insert("port", GlobalConfig.inboundConfig.http_port);
httpInBoundObject.insert("protocol", "http");
httpInBoundObject.insert("tag", "http_IN");
httpInBoundObject.insert("sniffing", sniffingObject);
if (GlobalConfig.inboundConfig.http_useAuth)
{
@ -335,6 +336,7 @@ namespace Qv2ray::core::connection
socksInBoundObject.insert("port", GlobalConfig.inboundConfig.socks_port);
socksInBoundObject.insert("protocol", "socks");
socksInBoundObject.insert("tag", "socks_IN");
socksInBoundObject.insert("sniffing", sniffingObject);
auto socksInSettings = GenerateSocksIN(GlobalConfig.inboundConfig.socks_useAuth ? "password" : "noauth",
QList<AccountObject>() << GlobalConfig.inboundConfig.socksAccount,
GlobalConfig.inboundConfig.socksUDP, GlobalConfig.inboundConfig.socksLocalIP);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
#include "core/CoreSafeTypes.hpp"
#include "core/CoreUtils.hpp"
#include "core/connection/ConnectionIO.hpp"
#include "core/kernel/KernelInteractions.hpp"
#include "core/handler/KernelInstanceHandler.hpp"
#define CheckIdExistance(type, id, val) \
if (!type.contains(id)) \
@ -21,7 +21,6 @@
#define CheckConnectionExistance(id) CheckConnectionExistanceEx(id, tr("Connection does not exist"))
namespace Qv2ray::core::handlers
{
//
class QvConfigHandler : public QObject
{
Q_OBJECT
@ -30,11 +29,6 @@ namespace Qv2ray::core::handlers
~QvConfigHandler();
public slots:
//
inline const ConnectionId CurrentConnection() const
{
return currentConnectionId;
}
inline const QList<ConnectionId> Connections() const
{
return connections.keys();
@ -80,11 +74,13 @@ namespace Qv2ray::core::handlers
//
// Connection Operations.
bool UpdateConnection(const ConnectionId &id, const CONFIGROOT &root, bool skipRestart = false);
const optional<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> RenameConnection(const ConnectionId &id, const QString &newName);
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
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;
signals:
void OnCrashed();
void OnConnected(const ConnectionId &id);
void OnDisconnected(const ConnectionId &id);
void OnVCoreLogAvailable(const ConnectionId &id, const QString &log);
void OnKernelLogAvailable(const ConnectionId &id, const QString &log);
void OnStatsAvailable(const ConnectionId &id, const quint64 upS, const quint64 downS, const quint64 upD, const quint64 downD);
//
void OnConnectionCreated(const ConnectionId &id, const QString &displayName);
@ -127,20 +120,20 @@ namespace Qv2ray::core::handlers
void OnGroupDeleted(const GroupId &id, const QList<ConnectionId> &connections);
//
void OnSubscriptionUpdateFinished(const GroupId &id);
void OnConnected(const ConnectionId &id);
void OnDisconnected(const ConnectionId &id);
void OnKernelCrashed(const ConnectionId &id, const QString &errMessage);
//
private slots:
void OnStatsDataArrived(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed);
void OnVCoreCrashed(const ConnectionId &id);
void OnLatencyDataArrived(const QvTCPingResultObject &data);
void OnKernelCrashed_p(const ConnectionId &id, const QString &errMessage);
void OnLatencyDataArrived_p(const QvTCPingResultObject &data);
void OnStatsDataArrived_p(const ConnectionId &id, const quint64 uploadSpeed, const quint64 downloadSpeed);
protected:
void timerEvent(QTimerEvent *event) override;
private:
void CHSaveConfigData_p();
//
optional<QString> CHStartConnection_p(const ConnectionId &id, const CONFIGROOT &root);
void CHStopConnection_p();
bool CHUpdateSubscription_p(const GroupId &id, const QByteArray &subscriptionData);
private:
@ -155,13 +148,7 @@ namespace Qv2ray::core::handlers
QvHttpRequestHelper *httpHelper;
bool isHttpRequestInProgress = false;
QvTCPingHelper *tcpingHelper;
// We only support one cuncurrent connection currently.
#ifdef QV2RAY_MULTIPlE_ONNECTION
QHash<ConnectionId, *V2rayKernelInstance> kernelInstances;
#else
ConnectionId currentConnectionId = NullConnectionId;
V2rayKernelInstance *vCoreInstance = nullptr;
#endif
KernelInstanceHandler *kernelHandler;
};
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_down = 0;
for (auto tag : inboundTags)
for (const auto &tag : inboundTags)
{
value_up += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>uplink");
value_down += CallStatsAPIByName("inbound>>>" + tag + ">>>traffic>>>downlink");
@ -105,7 +105,6 @@ namespace Qv2ray::core::kernel
if (running)
{
apiFailedCounter = 0;
emit OnDataReady(value_up, value_down);
}
@ -169,6 +168,10 @@ namespace Qv2ray::core::kernel
LOG(MODULE_VCORE, "API call returns: " + QSTRN(status.error_code()) + " (" + QString::fromStdString(status.error_message()) + ")")
apiFailedCounter++;
}
else
{
apiFailedCounter = 0;
}
qint64 data = response.stat().value();
#else

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#include "w_MainWindow.hpp"
#include "components/pac/QvPACHandler.hpp"
#include "components/plugins/QvPluginHost.hpp"
#include "components/plugins/toolbar/QvToolbar.hpp"
#include "components/proxy/QvProxyConfigurator.hpp"
#include "components/update/UpdateChecker.hpp"
@ -9,6 +9,7 @@
#include "ui/editors/w_OutboundEditor.hpp"
#include "ui/editors/w_RoutesEditor.hpp"
#include "ui/w_ImportConfig.hpp"
#include "ui/w_PluginManager.hpp"
#include "ui/w_PreferencesWindow.hpp"
#include "ui/w_SubscriptionManager.hpp"
#include "ui/widgets/ConnectionInfoWidget.hpp"
@ -50,11 +51,11 @@ QvMessageBusSlotImpl(MainWindow)
void MainWindow::UpdateColorScheme()
{
hTray.setIcon(QIcon(GlobalConfig.uiConfig.useDarkTrayIcon ? ":/assets/icons/ui_dark/tray.png" : ":/assets/icons/ui_light/tray.png"));
hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png"));
//
importConfigButton->setIcon(QICON_R("import.png"));
updownImageBox->setStyleSheet("image: url(" + QV2RAY_UI_COLORSCHEME_ROOT + "netspeed_arrow.png)");
updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_UI_COLORSCHEME_ROOT + "netspeed_arrow.png)");
updownImageBox->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)");
updownImageBox_2->setStyleSheet("image: url(" + QV2RAY_COLORSCHEME_ROOT + "netspeed_arrow.png)");
//
tray_action_ShowHide->setIcon(this->windowIcon());
action_RCM_Start->setIcon(QICON_R("connect.png"));
@ -131,17 +132,17 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
UpdateColorScheme();
//
//
connect(ConnectionManager, &QvConfigHandler::OnCrashed, [&] {
connect(ConnectionManager, &QvConfigHandler::OnKernelCrashed, [&](const ConnectionId &, const QString &reason) {
this->show();
QvMessageBoxWarn(this, tr("V2ray vcore terminated."),
tr("V2ray vcore terminated unexpectedly.") + NEWLINE + NEWLINE +
tr("To solve the problem, read the V2ray log in the log text browser."));
QvMessageBoxWarn(this, tr("Kernel terminated."),
tr("The kernel terminated unexpectedly:") + NEWLINE + reason + NEWLINE + NEWLINE +
tr("To solve the problem, read the kernel log in the log text browser."));
});
//
connect(ConnectionManager, &QvConfigHandler::OnConnected, this, &MainWindow::OnConnected);
connect(ConnectionManager, &QvConfigHandler::OnDisconnected, this, &MainWindow::OnDisconnected);
connect(ConnectionManager, &QvConfigHandler::OnStatsAvailable, this, &MainWindow::OnStatsAvailable);
connect(ConnectionManager, &QvConfigHandler::OnVCoreLogAvailable, this, &MainWindow::OnVCoreLogAvailable);
connect(ConnectionManager, &QvConfigHandler::OnKernelLogAvailable, this, &MainWindow::OnVCoreLogAvailable);
//
connect(ConnectionManager, &QvConfigHandler::OnConnectionDeleted, this, &MainWindow::OnConnectionDeleted);
connect(ConnectionManager, &QvConfigHandler::OnConnectionCreated, this, &MainWindow::OnConnectionCreated);
@ -229,7 +230,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
connectionListRCM_Menu->addAction(action_RCM_Delete);
connect(action_RCM_Start, &QAction::triggered, this, &MainWindow::on_action_StartThis_triggered);
connect(action_RCM_SetAutoConnection, &QAction::triggered, this, &MainWindow::on_action_RCM_SetAutoConnection_triggered);
connect(action_RCM_Edit, &QAction::triggered, this, &MainWindow::on_action_RCM_EditThis_triggered);
connect(action_RCM_EditJson, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsJson_triggered);
connect(action_RCM_EditComplex, &QAction::triggered, this, &MainWindow::on_action_RCM_EditAsComplex_triggered);
@ -357,6 +357,13 @@ void MainWindow::keyPressEvent(QKeyEvent *e)
this->close();
}
}
else if (e->modifiers() & Qt::ControlModifier && e->key() == Qt::Key_Q)
{
if (QvMessageBoxAsk(this, tr("Quit Qv2ray"), tr("Are you sure to exit Qv2ray?"), QMessageBox::No) == QMessageBox::Yes)
{
ExitQv2ray();
}
}
}
void MainWindow::keyReleaseEvent(QKeyEvent *e)
@ -383,11 +390,6 @@ void MainWindow::on_action_StartThis_triggered()
MainWindow::~MainWindow()
{
if (GlobalConfig.inboundConfig.pacConfig.enablePAC && pacServer != nullptr && pacServer->isRunning())
{
// Wait for PAC server to finish.
pacServer->wait();
}
hTray.hide();
}
@ -478,11 +480,17 @@ void MainWindow::on_connectionListWidget_customContextMenuRequested(const QPoint
auto item = connectionListWidget->itemAt(connectionListWidget->mapFromGlobal(_pos));
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);
}
}
}
void MainWindow::on_action_RCM_DeleteThese_triggered()
@ -492,10 +500,17 @@ void MainWindow::on_action_RCM_DeleteThese_triggered()
for (auto item : connectionListWidget->selectedItems())
{
auto widget = GetItemWidget(item);
if (widget)
{
if (widget->IsConnection())
{
connlist.append(get<1>(widget->Identifier()));
}
else
{
connlist.append(ConnectionManager->GetGroupMetaObject(get<0>(widget->Identifier())).connections);
}
}
}
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)
{
Q_UNUSED(id)
hTray.setIcon(Q_TRAYICON("tray.png"));
tray_action_Start->setEnabled(true);
tray_action_Stop->setEnabled(false);
tray_action_Restart->setEnabled(false);
tray_SystemProxyMenu->setEnabled(false);
lastConnectedId = id;
locateBtn->setEnabled(false);
if (!GlobalConfig.uiConfig.quietMode)
{
this->hTray.showMessage("Qv2ray", tr("Disconnected from: ") + GetDisplayName(id), this->windowIcon());
}
hTray.setToolTip(TRAY_TOOLTIP_PREFIX);
netspeedLabel->setText("0.00 B/s" NEWLINE "0.00 B/s");
dataamountLabel->setText("0.00 B" NEWLINE "0.00 B");
@ -583,16 +602,12 @@ void MainWindow::OnDisconnected(const ConnectionId &id)
{
ClearSystemProxy();
}
if (GlobalConfig.inboundConfig.pacConfig.enablePAC)
{
pacServer->stopServer();
}
}
void MainWindow::OnConnected(const ConnectionId &id)
{
Q_UNUSED(id)
hTray.setIcon(Q_TRAYICON("tray-connected.png"));
tray_action_Start->setEnabled(false);
tray_action_Stop->setEnabled(true);
tray_action_Restart->setEnabled(true);
@ -601,70 +616,14 @@ void MainWindow::OnConnected(const ConnectionId &id)
locateBtn->setEnabled(true);
on_clearlogButton_clicked();
auto name = GetDisplayName(id);
if (!GlobalConfig.uiConfig.quietMode)
{
this->hTray.showMessage("Qv2ray", tr("Connected: ") + name, this->windowIcon());
}
hTray.setToolTip(TRAY_TOOLTIP_PREFIX NEWLINE + tr("Connected: ") + name);
connetionStatusLabel->setText(tr("Connected: ") + name);
//
ConnectionManager->StartLatencyTest(id);
bool usePAC = GlobalConfig.inboundConfig.pacConfig.enablePAC;
bool pacUseSocks = GlobalConfig.inboundConfig.pacConfig.useSocksProxy;
bool httpEnabled = GlobalConfig.inboundConfig.useHTTP;
bool socksEnabled = GlobalConfig.inboundConfig.useSocks;
if (usePAC)
{
bool canStartPAC = true;
QString pacProxyString; // Something like this --> SOCKS5 127.0.0.1:1080; SOCKS
// 127.0.0.1:1080; DIRECT; http://proxy:8080
auto pacIP = GlobalConfig.inboundConfig.pacConfig.localIP;
if (pacIP.isEmpty())
{
LOG(MODULE_PROXY, "PAC Local IP is empty, default to 127.0.0.1")
pacIP = "127.0.0.1";
}
if (pacUseSocks)
{
if (socksEnabled)
{
pacProxyString = "SOCKS5 " + pacIP + ":" + QSTRN(GlobalConfig.inboundConfig.socks_port);
}
else
{
LOG(MODULE_UI, "PAC is using SOCKS, but it is not enabled")
QvMessageBoxWarn(this, tr("Configuring PAC"),
tr("Could not start PAC server as it is configured to use SOCKS, but it is not enabled"));
canStartPAC = false;
}
}
else
{
if (httpEnabled)
{
pacProxyString = "PROXY " + pacIP + ":" + QSTRN(GlobalConfig.inboundConfig.http_port);
}
else
{
LOG(MODULE_UI, "PAC is using HTTP, but it is not enabled")
QvMessageBoxWarn(this, tr("Configuring PAC"),
tr("Could not start PAC server as it is configured to use HTTP, but it is not enabled"));
canStartPAC = false;
}
}
if (canStartPAC)
{
pacServer = new PACServer(this);
pacServer->setPACProxyString(pacProxyString);
pacServer->start();
}
else
{
LOG(MODULE_PROXY, "Not starting PAC due to previous error.")
}
}
if (GlobalConfig.inboundConfig.setSystemProxy)
{
MWSetSystemProxy();
@ -860,7 +819,7 @@ void MainWindow::OnGroupDeleted(const GroupId &id, const QList<ConnectionId> &co
void MainWindow::on_locateBtn_clicked()
{
auto id = ConnectionManager->CurrentConnection();
auto id = KernelInstance->CurrentConnection();
if (id != NullConnectionId)
{
connectionListWidget->setCurrentItem(connectionNodes.value(id).get());
@ -964,7 +923,10 @@ void MainWindow::on_action_RCM_SetAutoConnection_triggered()
auto widget = GetItemWidget(current);
auto &conn = get<1>(widget->Identifier());
GlobalConfig.autoStartId = conn.toString();
if (!GlobalConfig.uiConfig.quietMode)
{
hTray.showMessage(tr("Set auto connection"), tr("Set %1 as auto connect.").arg(GetDisplayName(conn)));
}
SaveGlobalSettings();
}
}
@ -975,6 +937,17 @@ void MainWindow::on_action_RCM_ClearUsage_triggered()
if (current != nullptr)
{
auto widget = GetItemWidget(current);
if (widget)
{
if (widget->IsConnection())
ConnectionManager->ClearConnectionUsage(get<1>(widget->Identifier()));
else
ConnectionManager->ClearGroupUsage(get<0>(widget->Identifier()));
}
}
}
void MainWindow::on_pluginsBtn_clicked()
{
PluginManageWindow(this).exec();
}

View File

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

View File

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

View File

@ -4,79 +4,39 @@
void MainWindow::MWSetSystemProxy()
{
bool usePAC = GlobalConfig.inboundConfig.pacConfig.enablePAC;
bool pacUseSocks = GlobalConfig.inboundConfig.pacConfig.useSocksProxy;
bool httpEnabled = GlobalConfig.inboundConfig.useHTTP;
bool socksEnabled = GlobalConfig.inboundConfig.useSocks;
//
bool isComplex = IsComplexConfig(ConnectionManager->CurrentConnection());
auto inboundPorts = KernelInstance->InboundPorts();
bool httpEnabled = inboundPorts.contains("http");
bool socksEnabled = inboundPorts.contains("socks");
auto httpPort = inboundPorts["http"];
auto socksPort = inboundPorts["socks"];
if (!isComplex)
{
// Is simple config and we will try to set system proxy.
LOG(MODULE_UI, "Preparing to set system proxy")
//
QString proxyAddress;
bool canSetSystemProxy = true;
if (usePAC)
{
if ((httpEnabled && !pacUseSocks) || (socksEnabled && pacUseSocks))
{
// If we use PAC and socks/http are properly configured for PAC
LOG(MODULE_PROXY, "System proxy uses PAC")
proxyAddress = "http://" + GlobalConfig.inboundConfig.listenip + ":" + QSTRN(GlobalConfig.inboundConfig.pacConfig.port) + "/pac";
}
else
{
// Not properly configured
LOG(MODULE_PROXY, "Failed to process pac due to following reasons:")
LOG(MODULE_PROXY, " --> PAC is configured to use socks but socks is not enabled.")
LOG(MODULE_PROXY, " --> PAC is configuted to use http but http is not enabled.")
QvMessageBoxWarn(this, tr("PAC Processing Failed"),
tr("HTTP or SOCKS inbound is not properly configured for PAC") + NEWLINE +
tr("Qv2ray will continue, but will not set system proxy."));
canSetSystemProxy = false;
}
}
else
{
// Not using PAC
if (httpEnabled || socksEnabled)
{
// Not use PAC, System proxy should use HTTP or SOCKS
LOG(MODULE_PROXY, "Setting up system proxy.")
// A 'proxy host' should be a host WITHOUT `http://` uri scheme
proxyAddress = "localhost";
}
else
proxyAddress = "127.0.0.1";
SetSystemProxy(proxyAddress, httpPort, socksPort);
hTray.setIcon(Q_TRAYICON("tray-systemproxy.png"));
if (!GlobalConfig.uiConfig.quietMode)
{
LOG(MODULE_PROXY, "Neither of HTTP nor SOCKS is enabled, cannot set system proxy.")
QvMessageBoxWarn(this, tr("Cannot set system proxy"), tr("Both HTTP and SOCKS inbounds are not enabled"));
canSetSystemProxy = false;
}
}
if (canSetSystemProxy)
{
LOG(MODULE_UI, "Setting system proxy for simple config.")
auto httpPort = GlobalConfig.inboundConfig.useHTTP ? GlobalConfig.inboundConfig.http_port : 0;
auto socksPort = GlobalConfig.inboundConfig.useSocks ? GlobalConfig.inboundConfig.socks_port : 0;
//
SetSystemProxy(proxyAddress, httpPort, socksPort, usePAC);
hTray.showMessage("Qv2ray", tr("System proxy configured."));
}
}
else
{
hTray.showMessage("Qv2ray", tr("Didn't set proxy for complex config."), windowIcon());
LOG(MODULE_PROXY, "Neither of HTTP nor SOCKS is enabled, cannot set system proxy.")
QvMessageBoxWarn(this, tr("Cannot set system proxy"), tr("Both HTTP and SOCKS inbounds are not enabled"));
}
}
void MainWindow::MWClearSystemProxy()
{
ClearSystemProxy();
hTray.setIcon(KernelInstance->CurrentConnection() == NullConnectionId ? Q_TRAYICON("tray.png") : Q_TRAYICON("tray-connected.png"));
if (!GlobalConfig.uiConfig.quietMode)
{
hTray.showMessage("Qv2ray", tr("System proxy removed."));
}
}
void MainWindow::CheckSubscriptionsUpdate()
@ -84,22 +44,26 @@ void MainWindow::CheckSubscriptionsUpdate()
QStringList updateList;
auto subscriptions = ConnectionManager->Subscriptions();
for (auto entry : subscriptions)
for (const auto &entry : subscriptions)
{
auto into = ConnectionManager->GetGroupMetaObject(entry);
const auto info = ConnectionManager->GetGroupMetaObject(entry);
//
auto lastRenewDate = QDateTime::fromTime_t(into.lastUpdated);
auto renewTime = lastRenewDate.addSecs(into.updateInterval * 86400);
// The update is ignored.
if (info.updateInterval == 0)
continue;
//
const auto &lastRenewDate = QDateTime::fromTime_t(info.lastUpdated);
const auto &renewTime = lastRenewDate.addSecs(info.updateInterval * 86400);
LOG(MODULE_SUBSCRIPTION, //
"Subscription \"" + entry.toString() + "\": " + //
"Subscription \"" + info.displayName + "\": " + //
NEWLINE + " --> Last renewal time: " + lastRenewDate.toString() + //
NEWLINE + " --> Renew interval: " + QSTRN(into.updateInterval) + //
NEWLINE + " --> Renew interval: " + QSTRN(info.updateInterval) + //
NEWLINE + " --> Ideal renew time: " + renewTime.toString()) //
if (renewTime <= QDateTime::currentDateTime())
{
LOG(MODULE_SUBSCRIPTION, "Subscription: " + entry.toString() + " needs to be updated.")
updateList.append(entry.toString());
LOG(MODULE_SUBSCRIPTION, "Subscription: " + info.displayName + " needs to be updated.")
updateList.append(info.displayName);
}
}

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/QvTranslator.hpp"
#include "components/autolaunch/QvAutoLaunch.hpp"
#include "components/plugins/interface/QvPluginInterface.hpp"
#include "components/plugins/toolbar/QvToolbar.hpp"
#include "core/connection/ConnectionIO.hpp"
#include "core/handler/ConfigHandler.hpp"
#include "core/kernel/KernelInteractions.hpp"
#include "core/kernel/V2rayKernelInteractions.hpp"
#include "core/settings/SettingsBackend.hpp"
#include "ui/widgets/RouteSettingsMatrix.hpp"
@ -69,6 +70,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
qvBuildInfo->setText(QV2RAY_BUILD_INFO);
qvBuildExInfo->setText(QV2RAY_BUILD_EXTRA_INFO);
qvBuildTime->setText(__DATE__ " " __TIME__);
qvPluginInterfaceVersionLabel->setText(tr("Version: %1").arg(QSTRN(QV2RAY_PLUGIN_INTERFACE_VERSION)));
//
// Deep copy
CurrentConfig = GlobalConfig;
@ -87,14 +89,6 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
//
//
listenIPTxt->setText(CurrentConfig.inboundConfig.listenip);
bool pacEnabled = CurrentConfig.inboundConfig.pacConfig.enablePAC;
pacGroupBox->setChecked(pacEnabled);
setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy);
//
// PAC
pacPortSB->setValue(CurrentConfig.inboundConfig.pacConfig.port);
pacProxyTxt->setText(CurrentConfig.inboundConfig.pacConfig.localIP);
pacProxyCB->setCurrentIndex(CurrentConfig.inboundConfig.pacConfig.useSocksProxy ? 1 : 0);
//
bool have_http = CurrentConfig.inboundConfig.useHTTP;
httpGroupBox->setChecked(have_http);
@ -134,12 +128,26 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
//
localDNSCb->setChecked(CurrentConfig.connectionConfig.withLocalDNS);
//
pluginKernelV2rayIntegrationCB->setChecked(CurrentConfig.pluginConfig.v2rayIntegration);
pluginKernelPortAllocateCB->setValue(CurrentConfig.pluginConfig.portAllocationStart);
//
qvProxyPortCB->setValue(CurrentConfig.networkConfig.port);
qvProxyAddressTxt->setText(CurrentConfig.networkConfig.address);
qvProxyTypeCombo->setCurrentText(CurrentConfig.networkConfig.type);
qvNetworkUATxt->setText(CurrentConfig.networkConfig.userAgent);
qvUseProxyCB->setChecked(CurrentConfig.networkConfig.useCustomProxy);
//
quietModeCB->setChecked(CurrentConfig.uiConfig.quietMode);
//
// Advanced config.
setAllowInsecureCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecure);
setAllowInsecureCiphersCB->setChecked(CurrentConfig.advancedConfig.setAllowInsecureCiphers);
setTestLatenctCB->setChecked(CurrentConfig.advancedConfig.testLatencyPeriodcally);
//
DNSListTxt->clear();
for (auto dnsStr : CurrentConfig.connectionConfig.dnsList)
{
auto str = dnsStr.trimmed();
if (!str.isEmpty())
{
DNSListTxt->appendPlainText(str);
@ -150,11 +158,11 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
updateSettingsGroupBox->setEnabled(false);
updateSettingsGroupBox->setToolTip(tr("Update is disabled by your vendor."));
#endif
//
updateChannelCombo->setCurrentIndex(CurrentConfig.updateConfig.updateChannel);
cancelIgnoreVersionBtn->setEnabled(!CurrentConfig.updateConfig.ignoredVersion.isEmpty());
ignoredNextVersion->setText(CurrentConfig.updateConfig.ignoredVersion);
//
for (auto i = 0; i < CurrentConfig.toolBarConfig.Pages.size(); i++)
{
nsBarPagesList->addItem(tr("Page") + QSTRN(i + 1) + ": " + QSTRN(CurrentConfig.toolBarConfig.Pages[i].Lines.size()) + " " +
@ -213,8 +221,7 @@ PreferencesWindow::PreferencesWindow(QWidget *parent) : QDialog(parent), Current
//
maxLogLinesSB->setValue(CurrentConfig.uiConfig.maximumLogLines);
//
pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" +
QSTRN(pacPortSB->value()) + "/pac");
setSysProxyCB->setChecked(CurrentConfig.inboundConfig.setSystemProxy);
//
finishedLoading = true;
routeSettingsWidget = new RouteSettingsMatrixWidget(CurrentConfig.kernelConfig.AssetsPath(), this);
@ -256,12 +263,6 @@ void PreferencesWindow::on_buttonBox_accepted()
ports << CurrentConfig.inboundConfig.socks_port;
}
if (CurrentConfig.inboundConfig.pacConfig.enablePAC)
{
size++;
ports << CurrentConfig.inboundConfig.pacConfig.port;
}
if (!StartupOption.noAPI)
{
size++;
@ -932,84 +933,6 @@ void PreferencesWindow::on_darkTrayCB_stateChanged(int arg1)
CurrentConfig.uiConfig.useDarkTrayIcon = arg1 == Qt::Checked;
}
void PreferencesWindow::on_pacGoBtn_clicked()
{
LOADINGCHECK
QString gfwLocation;
QString fileContent;
pacGoBtn->setEnabled(false);
gfwListCB->setEnabled(false);
QvHttpRequestHelper request;
LOG(MODULE_PROXY, "Downloading GFWList file.")
bool withProxy = getGFWListWithProxyCB->isChecked();
switch (gfwListCB->currentIndex())
{
case 0:
gfwLocation = "https://gitlab.com/gfwlist/gfwlist/raw/master/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 1:
gfwLocation = "https://pagure.io/gfwlist/raw/master/f/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 2:
gfwLocation = "http://repo.or.cz/gfwlist.git/blob_plain/HEAD:/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 3:
gfwLocation = "https://bitbucket.org/gfwlist/gfwlist/raw/HEAD/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 4:
gfwLocation = "https://raw.githubusercontent.com/gfwlist/gfwlist/master/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 5:
gfwLocation = "https://git.tuxfamily.org/gfwlist/gfwlist.git/plain/gfwlist.txt";
fileContent = QString::fromUtf8(request.syncget(gfwLocation, withProxy));
break;
case 6:
auto file = QFileDialog::getOpenFileName(this, tr("Select GFWList in base64"));
if (file.isEmpty())
{
QvMessageBoxWarn(this, tr("Download GFWList"), tr("Operation is cancelled."));
return;
}
fileContent = StringFromFile(file);
break;
}
LOG(MODULE_NETWORK, "Fetched: " + gfwLocation)
QvMessageBoxInfo(this, tr("Download GFWList"), tr("Successfully downloaded GFWList."));
pacGoBtn->setEnabled(true);
gfwListCB->setEnabled(true);
if (!QDir(QV2RAY_RULES_DIR).exists())
{
QDir(QV2RAY_RULES_DIR).mkpath(QV2RAY_RULES_DIR);
}
StringToFile(fileContent, QV2RAY_RULES_GFWLIST_PATH);
}
void PreferencesWindow::on_pacPortSB_valueChanged(int arg1)
{
LOADINGCHECK
NEEDRESTART
CurrentConfig.inboundConfig.pacConfig.port = arg1;
pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" +
QSTRN(pacPortSB->value()) + "/pac");
}
void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1)
{
LOADINGCHECK
@ -1017,28 +940,12 @@ void PreferencesWindow::on_setSysProxyCB_stateChanged(int arg1)
CurrentConfig.inboundConfig.setSystemProxy = arg1 == Qt::Checked;
}
void PreferencesWindow::on_pacProxyCB_currentIndexChanged(int index)
{
LOADINGCHECK
NEEDRESTART
// 0 -> http
// 1 -> socks
CurrentConfig.inboundConfig.pacConfig.useSocksProxy = index == 1;
}
void PreferencesWindow::on_pushButton_clicked()
{
LOADINGCHECK
QDesktopServices::openUrl(QUrl::fromUserInput(QV2RAY_RULES_DIR));
}
void PreferencesWindow::on_pacProxyTxt_textEdited(const QString &arg1)
{
LOADINGCHECK
NEEDRESTART
CurrentConfig.inboundConfig.pacConfig.localIP = arg1;
}
void PreferencesWindow::on_autoStartSubsCombo_currentIndexChanged(const QString &arg1)
{
LOADINGCHECK if (arg1.isEmpty())
@ -1147,23 +1054,6 @@ void PreferencesWindow::on_fpPortSB_valueChanged(int arg1)
CurrentConfig.connectionConfig.forwardProxyConfig.port = arg1;
}
void PreferencesWindow::on_pacProxyTxt_textChanged(const QString &arg1)
{
Q_UNUSED(arg1)
if (IsValidIPAddress(arg1))
{
BLACK(pacProxyTxt)
}
else
{
RED(pacProxyTxt)
}
pacListenAddrLabel->setText("http://" + (pacProxyTxt->text().isEmpty() ? "127.0.0.1" : pacProxyTxt->text()) + ":" +
QSTRN(pacPortSB->value()) + "/pac");
}
void PreferencesWindow::on_checkVCoreSettings_clicked()
{
auto vcorePath = vCorePathTxt->text();
@ -1196,20 +1086,6 @@ void PreferencesWindow::on_socksGroupBox_clicked(bool checked)
CurrentConfig.inboundConfig.useSocks = checked;
}
void PreferencesWindow::on_pacGroupBox_clicked(bool checked)
{
LOADINGCHECK
NEEDRESTART
CurrentConfig.inboundConfig.pacConfig.enablePAC = checked;
if (checked)
{
QvMessageBoxWarn(this, QObject::tr("Deprecated"),
QObject::tr("PAC is now deprecated and is not encouraged to be used anymore.") + NEWLINE +
QObject::tr("It will be removed or be provided as a plugin in the future.") + NEWLINE + NEWLINE +
QObject::tr("PAC will still work currently, but please switch to the V2ray built-in routing as soon as possible."));
}
}
void PreferencesWindow::on_fpGroupBox_clicked(bool checked)
{
LOADINGCHECK
@ -1237,3 +1113,81 @@ void PreferencesWindow::on_updateChannelCombo_currentIndexChanged(int index)
CurrentConfig.updateConfig.updateChannel = index;
CurrentConfig.updateConfig.ignoredVersion.clear();
}
void PreferencesWindow::on_pluginKernelV2rayIntegrationCB_stateChanged(int arg1)
{
LOADINGCHECK
CurrentConfig.pluginConfig.v2rayIntegration = arg1 == Qt::Checked;
}
void PreferencesWindow::on_pluginKernelPortAllocateCB_valueChanged(int arg1)
{
LOADINGCHECK
CurrentConfig.pluginConfig.portAllocationStart = arg1;
}
void PreferencesWindow::on_qvProxyAddressTxt_textEdited(const QString &arg1)
{
LOADINGCHECK
CurrentConfig.networkConfig.address = arg1;
}
void PreferencesWindow::on_qvProxyTypeCombo_currentTextChanged(const QString &arg1)
{
LOADINGCHECK
CurrentConfig.networkConfig.type = arg1.toLower();
}
void PreferencesWindow::on_qvProxyPortCB_valueChanged(int arg1)
{
LOADINGCHECK
CurrentConfig.networkConfig.port = arg1;
}
void PreferencesWindow::on_qvNetworkUATxt_textEdited(const QString &arg1)
{
LOADINGCHECK
CurrentConfig.networkConfig.userAgent = arg1;
}
void PreferencesWindow::on_qvUseProxyCB_stateChanged(int arg1)
{
LOADINGCHECK
CurrentConfig.networkConfig.useCustomProxy = arg1 == Qt::Checked;
}
void PreferencesWindow::on_setAllowInsecureCB_stateChanged(int arg1)
{
LOADINGCHECK
if (arg1 == Qt::Checked)
{
QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("You will lose the advantage of TLS and make your connection under MITM attack."));
}
CurrentConfig.advancedConfig.setAllowInsecure = arg1 == Qt::Checked;
}
void PreferencesWindow::on_setTestLatenctCB_stateChanged(int arg1)
{
LOADINGCHECK
if (arg1 == Qt::Checked)
{
QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("This will (probably) makes it easy to fingerprint your connection."));
}
CurrentConfig.advancedConfig.testLatencyPeriodcally = arg1 == Qt::Checked;
}
void PreferencesWindow::on_setAllowInsecureCiphersCB_stateChanged(int arg1)
{
LOADINGCHECK
if (arg1 == Qt::Checked)
{
QvMessageBoxWarn(this, tr("Dangerous Operation"), tr("You will lose the advantage of TLS and make your connection under MITM attack."));
}
CurrentConfig.advancedConfig.setAllowInsecureCiphers = arg1 == Qt::Checked;
}
void PreferencesWindow::on_quietModeCB_stateChanged(int arg1)
{
LOADINGCHECK
CurrentConfig.uiConfig.quietMode = arg1 == Qt::Checked;
}

View File

@ -118,18 +118,10 @@ class PreferencesWindow
void on_darkTrayCB_stateChanged(int arg1);
void on_pacGoBtn_clicked();
void on_pacPortSB_valueChanged(int arg1);
void on_setSysProxyCB_stateChanged(int arg1);
void on_pacProxyCB_currentIndexChanged(int index);
void on_pushButton_clicked();
void on_pacProxyTxt_textEdited(const QString &arg1);
void on_autoStartSubsCombo_currentIndexChanged(const QString &arg1);
void on_autoStartConnCombo_currentIndexChanged(const QString &arg1);
@ -148,16 +140,12 @@ class PreferencesWindow
void on_fpPortSB_valueChanged(int arg1);
void on_pacProxyTxt_textChanged(const QString &arg1);
void on_checkVCoreSettings_clicked();
void on_httpGroupBox_clicked(bool checked);
void on_socksGroupBox_clicked(bool checked);
void on_pacGroupBox_clicked(bool checked);
void on_fpGroupBox_clicked(bool checked);
void on_maxLogLinesSB_valueChanged(int arg1);
@ -168,6 +156,28 @@ class PreferencesWindow
void on_updateChannelCombo_currentIndexChanged(int index);
void on_pluginKernelV2rayIntegrationCB_stateChanged(int arg1);
void on_pluginKernelPortAllocateCB_valueChanged(int arg1);
void on_qvProxyAddressTxt_textEdited(const QString &arg1);
void on_qvProxyTypeCombo_currentTextChanged(const QString &arg1);
void on_qvProxyPortCB_valueChanged(int arg1);
void on_qvNetworkUATxt_textEdited(const QString &arg1);
void on_qvUseProxyCB_stateChanged(int arg1);
void on_setAllowInsecureCB_stateChanged(int arg1);
void on_setTestLatenctCB_stateChanged(int arg1);
void on_setAllowInsecureCiphersCB_stateChanged(int arg1);
void on_quietModeCB_stateChanged(int arg1);
private:
//
RouteSettingsMatrixWidget *routeSettingsWidget;

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -145,11 +145,14 @@ void ConnectionItemWidget::CancelRename()
void ConnectionItemWidget::BeginRename()
{
if (IsConnection())
{
stackedWidget->setCurrentIndex(1);
renameTxt->setStyle(QStyleFactory::create("Fusion"));
renameTxt->setStyleSheet("background-color: " + this->palette().color(this->backgroundRole()).name(QColor::HexRgb));
renameTxt->setText(originalItemName);
renameTxt->setFocus();
}
}
ConnectionItemWidget::~ConnectionItemWidget()

View File

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

View File

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

View File

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

View File

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

View File

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